Incorrect type inference for recursive usage hidden behind generic star args
- 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
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