microsoft / microsoft/pyright

Incorrect type inference for recursive usage hidden behind generic star args

Open
#11,535 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Python
Stars
15.6k
Forks
1.8k
Avg merge
12h 13m
Merged PRs (30d)
52

Description

**Describe the bug**
On the code provided, pyright should see that inferring the type of `member` is recursive, and cut it off with `Any`/`Unknown`.

Instead, pyright gives an incorrect fully static type that does not account for the recursiveness, resulting in a type error if you try to use that type as a hint.

**Code or Screenshots**
Code sample in [pyright playground](https://pyright-play.net/?code=GYJw9gtgBAxmA28CmMAuBLMA7AzgOgEMAjGKdCABzBFSgGEDFjkAaKAZSQEcBXJLGEgBQoSFFQBPCuiwBzMpWq0AMulRIQjNiCQA3JIwD6kisKEmkUAAoEQODQCUkOHvFQBtAPIBdKAF5xHgpkLzYZVG9zKUsbOw0AMR4BdwBJNh9-ekZ4ZiR3d05efkFU7zCsCLZY%2BxAnFzcvb0ihGBycHGtbGtT0qoAuKAAKVXVNeHcAFRA%2BMqgRjUZ3eMZ7bwBKbz6hKB2oABMkYChDQxhsk8H7eGByih5UAcK%2BATyU2ZkDgA8B8LWoAFoAHydOK1ZyuDw%2BAZ4GFCba7A5HVAAC34Xk86SsA2GagW4ymMzY8zGSxWSHW3kG8N2uyuN2OYBRGgG1Q0PSgngxHKskRpfyBIO6aUCwTyXM5sx50NhLTaHXi1AA7rY9k5gKyQOyfIMNVqibiSQTyestjTEcdDDI1Bc6WxgEkYAMGEwiCF3JKugkHVqmvzgQA5bBIU002lIa54Qz2gRO7K5fIe0GJZLCny%2BALRmBwoTmmDIsBgew%2BwYAKkMFE9dhZlb1cwNiyNvoBwN1qf1owb02N0rwOcOUAAVjwcB43oNLeoIAMUn7BWzhcLiZ2ZpsoDDe0IIEgIEQNB0AkOR4MAOQARmPazwTKwVJpeYL9kGCpAypAqsOGsGOR3ewIAy3O57msbCHqggwAEQAL7gWsaxCHBADEUAAKKfKYaBIHs4jRNWoLuCOIBsKgQQhARbAAIJYBIvgAD5QGRdYdviXZNEISHUPRkCWIyqIgNhpjiMiBC0PYziCZYOgwDwdiYFgUAEFgWFSagHRqFAYDAEcipqMiUCURIbFQE4%2BiMJh-HBoZEzROpRzgQBu52OBZAdOBuoMcRor4aghEiqR3lsARtH0d5sxLsxK7gUIOgmfAxjRIM9lAUIQA)

```python
from collections.abc import Callable, Sequence
from typing import Literal, reveal_type

type ParserResult[O] = tuple[O, int]
type ParserFunc[I, O] = Callable[[Sequence[I], int], ParserResult[O]]

class Parser[I, O, P: (Literal[True], Literal[False])]:
def __call__(self, input: Sequence[I], index: int) -> ParserResult[O]: ...

def then[OO, OP: (Literal[True], Literal[False])](
self, _other: Parser[I, OO, OP]
) -> Parser[I, tuple[O, OO], P]: ...

class ForwardRefParser[I, O](Parser[I, O, Literal[True]]):
def __init__(self, func: Callable[[], ParserFunc[I, O]]) -> None:
self._func: Callable[[], ParserFunc[I, O]] = func

def choose[I, O](*_parsers: Parser[I, O, Literal[True]]) -> Parser[I, O, Literal[True]]: ...
def just[I](_item: I) -> Parser[I, I, Literal[True]]: ...

members = just('1').then(
choose(ForwardRefParser(lambda: members), just("}"))
)
# Expected type: Parser[str, tuple[str, Any] | str, Literal[True]]
# or some other type that sees the recursion and cuts it off with Any/Unknown
# Revealed type:
# Type of "members" is "Parser[str, tuple[str, tuple[str, str] | str], Literal[True]]"
reveal_type(members)
```

**VS Code extension or command-line**
Found in VSC extension, reproduces with CLI and playground, all running latest (`1.1.411`)

Contributor guide

Open the contributing guide

Research direction

Start by running the linked Pyright playground example and reproducing it with the CLI at version 1.1.411. Trace generic type inference for the recursive ForwardRefParser usage hidden behind choose's star arguments. Done means reveal_type reports a recursion-bounded Any or Unknown rather than the incorrect fully static recursive type, without introducing the reported type error.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.