Repository navigation
Order-dependent overload resolution failure due to memorised Protocol conformance check on generic class #11644
Description
Related to #10607, now stand-alone example.
Describe the bug
When an operation evaluates whether a generic class matches an @overload conditioned on a Protocol parameter and fails (e.g. Series[A] does not implement Supports_ProtoAdd[B, A], see the example below), pyright appears to memorise or cache a negative conformance result for the generic class against that subsequent expressions on valid specializations (such as Series[bool] with True, where Series[bool] correctly implements Supports_ProtoAdd[bool, bool]) erroneously bypass the protocol overload and fall through to fallback overloads (e.g., matching Series[bool] + int because bool is a subclass of int), returning the wrong type.
If the prior failing operation is removed or moved after the valid operation, both expressions type-check correctly with 0 errors.
Code or Screenshots
from typing import TYPE_CHECKING, Generic, Protocol, TypeVar, assert_type, overload
T_contra = TypeVar("T_contra", contravariant=True)
T = TypeVar("T")
S = TypeVar("S")
S_contra = TypeVar("S_contra", contravariant=True)
class ElementOpsMixin(Generic[S]):
@overload
def _proto_add(self: "ElementOpsMixin[bool]", other: bool, /) -> "ElementOpsMixin[bool]": ...
@overload
def _proto_add(self: "ElementOpsMixin[int]", other: int, /) -> "ElementOpsMixin[int]": ...
def _proto_add(self, other: object, /) -> object:
return self
class Supports_ProtoAdd(Protocol[T_contra, T]):
def _proto_add(self, other: T_contra, /) -> ElementOpsMixin[T]: ...
class Series(ElementOpsMixin[S], Generic[S]):
@overload
def __add__(self: Supports_ProtoAdd[S_contra, S], other: S_contra, /) -> "Series[S]": ...
@overload
def __add__(self: "Series[bool]", other: int, /) -> "Series[int]": ...
def __add__(self, other: object, /) -> object:
return self
class A: ...
class B: ...
# Step 1: Evaluate mismatched operation on Series[A] + B
# Series[A] does NOT implement _proto_add for B.
s_a: Series[A] = Series()
b: B = B()
if TYPE_CHECKING:
_ = s_a + b # type: ignore[operator] # pyright: ignore[reportOperatorIssue,reportUnknownVariableType] # pyrefly: ignore[unsupported-operation] # ty: ignore[unsupported-operator]
# Step 2: Now evaluate valid operation Series[bool] + True
s_bool: Series[bool] = Series()
# EXPECTED: Series[bool] (via Supports_ProtoAdd[bool, bool])
# ACTUAL: Series[int] (Supports_ProtoAdd was cached as invalid, falling back to Series[bool] + int because bool is a subtype of int)
res = s_bool + True
assert_type(res, Series[bool]) # only pyright raises hereIf your code relies on symbols that are imported from a third-party library, include the associated import statements and specify which versions of those libraries you have installed.
VS code or command line
playground
Co-authored by Gemini 3.7 Flash
Activity
microsoft/pyright#11644 microsoft/pyright#10607
cmp0xff commentedon Aug 23, 2026
Hi Rich Chiodo (@rchiodo), thank you for your #11649! I see you've reopened the issue. Is it fixed?
rchiodo commentedon Aug 24, 2026
It should be fixed when we ship the next release. That's why I added the 'addressed in next version' label.
cmp0xff commentedon Aug 24, 2026
Thank you! I know the publishing of
pyrighttopypiis managed by a third party, but apparently they are inactive recently (e.g. RobertCraigie/pyright-python#365), so users have troubles obtaining new versions. It would be great if there could be some work arounds or other forms of help.rchiodo commentedon Aug 24, 2026
I believe the Pyright PyPI package has flags that lets you specify the version. I did this:
And it used 1.1.413
CLN: #1836 merge `pyrefly_strict` into `pyrefly`, make `ty_all` and `…
CLN: pandas-dev#1836 merge `pyrefly_strict` into `pyrefly`, make `ty_…
Merge tag '1.1.414' into merge-1.1.414