Incorrect type inference after type narrowing using isinstance with protocols
- Dominant language
- Python
- Stars
- 15.6k
- Forks
- 1.8k
- Avg merge
- 12h 13m
- Merged PRs (30d)
- 52
Description
**Describe the bug**
Type inference does not change when narrowing to a union of concrete and protocol types that define identical method signatures.
**Code or Screenshots**
```python
import typing
@typing.runtime_checkable
class Foo(typing.Protocol):
def foo(self) -> str: ...
@typing.runtime_checkable
class Bar(typing.Protocol):
def foo(self) -> str: ...
def func(x: typing.Any) -> None:
if (is_foo_bar := isinstance(x, Foo | Bar)) or isinstance(x, str):
typing.assert_type(x.foo() if is_foo_bar else x, str) # (reportAssertTypeFailure)
```
However, if we change the return type of the method in one of the other classes, everything works correctly:
```python
import typing
@typing.runtime_checkable
class Foo(typing.Protocol):
def foo(self) -> str: ...
@typing.runtime_checkable
class Bar(typing.Protocol):
def foo(self) -> int: ...
def func(x: typing.Any) -> None:
if (is_foo_bar := isinstance(x, Foo | Bar)) or isinstance(x, str):
typing.assert_type(x.foo() if is_foo_bar else x, str | int) # OK
```
It seems this issue only occurs with protocols; everything works as expected with non-protocol classes
[pyright playground](https://pyright-play.net/?pythonVersion=3.13&strict=true&code=JYWwDg9gTgLgBDAnmYA7A5gKGwASSjAOigFdUZQBTAfQGMALS2gawEMAjAG0s1s9YDOAuADEIEABT406QgAUoEGBFoROASgBcmOLrgATSgDM4R8RIGVOR9XAC0APjgCYUTXEKfsmPMhnEyChAaBiY2Lh4%2BQWEAIVYoKT8iBSUVNS0dPUMTM0lLa1tHZ1d3T0JvbNMyWgkAD3dpIgBBVERCpwA5CFRKbT04YBMJYAFqXOp2eLhNAF4BgTQXVlRaSjqAGlFxOAAfODiodVtoecWYZdWN4sO%2B-r1G2WjKWGp8NdrCXIlbQfmx8QmUysljgtU2LkOugAxHAJFBKJBYE0hM8YAAVZCUESsYCcEjw9RAA)
Contributor guide
Research direction
Start by reproducing the example in the linked Pyright playground and inspect the type-narrowing path for isinstance checks involving runtime-checkable protocols. There is no repository file or test path in the report; done means the first example infers str for x.foo() while preserving the second example's str | int result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- compilers, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 42/100