microsoft / microsoft/pyright

Incorrect type inference after type narrowing using isinstance with protocols

Open
#10,270 3 comments 1 reaction 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**
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.