Skip to content

Fix/protocol explicit self - #5091

Draft
vgvr0 wants to merge 3 commits into
facebook:mainfrom
vgvr0:fix/protocol-explicit-self
Draft

vgvr0 wants to merge 3 commits into
facebook:mainfrom
vgvr0:fix/protocol-explicit-self

Conversation

@vgvr0

@vgvr0 vgvr0 commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #5089

Protocol matching could incorrectly accept bound methods whose explicit self: annotation was incompatible with the concrete receiver.

The issue occurred because bound-method matching strips the first parameter before callable comparison, so an explicit receiver constraint such as self: ElementOpsMixin[int] could be lost. This allowed cases like Series[bool] to satisfy a protocol through normal bool <: int argument compatibility even though ElementOpsMixin[bool] is not compatible with invariant ElementOpsMixin[int].

This change:

  • checks explicit self: compatibility before Protocol callable matching discards the receiver parameter;

  • reuses the same compatibility logic for single functions and overloaded methods;

  • distinguishes unchanged, partially filtered, and fully incompatible overload sets;

  • rejects Protocol matching when every overload has an incompatible explicit receiver;

  • preserves the existing recursion guard for protocol-typed self: annotations and recursive Protocol conformance;

  • leaves ordinary bool <: int callable argument compatibility unchanged.

  • I am a human, and I fully understand the code I am submitting.

Test Plan

Added regression coverage for:

  • the original Series[bool]() - True false negative;
  • valid Series[int]() - True;
  • direct bound-method calls with an incompatible explicit self:;
  • valid and invalid Protocol conformance;
  • overloaded methods where every explicit receiver is incompatible;
  • existing partial/all-compatible overload behavior;
  • recursive and mutually recursive Protocols;
  • generic explicit self: handling;
  • ordinary bool <: int argument compatibility.

Validation:

cargo test -p pyrefly protocol
183 passed

cargo test -p pyrefly class_subtyping
29 passed

cargo test -p pyrefly callable
284 passed

cargo fmt --all -- --check
passed

git diff --check
passed

@github-actions

This comment has been minimized.

@vgvr0
vgvr0 force-pushed the fix/protocol-explicit-self branch from 5625cfc to fd2a584 Compare October 3, 2026 13:33
@github-actions github-actions Bot added size/l and removed size/xl labels Oct 3, 2026
@meta-codesync

meta-codesync Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D123197668. (Because this pull request was imported automatically, there will not be any future comments.)

@github-actions

This comment has been minimized.

@github-actions github-actions Bot added size/l and removed size/l labels Oct 3, 2026
@github-actions

This comment has been minimized.

@vgvr0
vgvr0 marked this pull request as draft October 3, 2026 16:18
@github-actions github-actions Bot added size/xl and removed size/l labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown

Diff from mypy_primer, showing the effect of this PR on open source code:

============================================================
SUMMARY
============================================================
Total: +37 new errors, -25 fixed errors
By preset: +36/-10 (default), +36/-11 (strict)

Projects with changes (6):
  scikit-learn: +0 -1
  pandas-stubs: +12 -0
  hydpy: +1 -0
  pandas: +23 -22
  static-frame: +0 -1
  numpy-stl: +1 -1
============================================================

FULL DIFF DETAILS
------------------------------------------------------------

scikit-learn (https://github.com/scikit-learn/scikit-learn)
- ERROR sklearn/inspection/tests/test_partial_dependence.py:769:16-25: The type of this argument is unknown [unknown-argument-type]

pandas-stubs (https://github.com/pandas-dev/pandas-stubs)
+ ERROR tests/indexes/bool/test_truediv.py:30:15-23: `/` is not supported between `Index[bool]` and `Literal[True]` [unsupported-operation]
+ ERROR tests/indexes/bool/test_truediv.py:37:15-23: `/` is not supported between `Literal[True]` and `Index[bool]` [unsupported-operation]
+ ERROR tests/indexes/bool/test_truediv.py:49:15-23: `/` is not supported between `Index[bool]` and `list[bool]` [unsupported-operation]
+ ERROR tests/indexes/bool/test_truediv.py:56:15-23: `/` is not supported between `list[bool]` and `Index[bool]` [unsupported-operation]
+ ERROR tests/series/bool/test_truediv.py:30:15-23: `/` is not supported between `Series[bool]` and `Literal[True]` [unsupported-operation]
+ ERROR tests/series/bool/test_truediv.py:37:15-23: `/` is not supported between `Literal[True]` and `Series[bool]` [unsupported-operation]
+ ERROR tests/series/bool/test_truediv.py:44:21-24: No matching overload found for function `pandas.core.series.Series.truediv` called with arguments: (Literal[True]) [no-matching-overload]
+ ERROR tests/series/bool/test_truediv.py:55:17-20: No matching overload found for function `pandas.core.series.Series.truediv` called with arguments: (Literal[True]) [no-matching-overload]
+ ERROR tests/series/bool/test_truediv.py:62:22-25: No matching overload found for function `pandas.core.series.Series.rtruediv` called with arguments: (Literal[True]) [no-matching-overload]
+ ERROR tests/series/bool/test_truediv.py:73:18-21: No matching overload found for function `pandas.core.series.Series.rtruediv` called with arguments: (Literal[True]) [no-matching-overload]
+ ERROR tests/series/bool/test_truediv.py:87:15-23: `/` is not supported between `Series[bool]` and `list[bool]` [unsupported-operation]
+ ERROR tests/series/bool/test_truediv.py:94:15-23: `/` is not supported between `list[bool]` and `Series[bool]` [unsupported-operation]

hydpy (https://github.com/hydpy-dev/hydpy)
+ ERROR hydpy/auxs/iuhtools.py:516:13-20: `ndarray[tuple[Any, ...], dtype[float64]] | Unknown` is not assignable to variable `t` with type `VectorFloatFlex` [bad-assignment]

pandas (https://github.com/pandas-dev/pandas)
- ERROR pandas/core/arrays/sparse/array.py:1440:17-29: The type of this argument is unknown [unknown-argument-type]
- ERROR pandas/core/dtypes/missing.py:653:16-31: Returning implicit Any from function declared to return "_ArrLikeT" [no-any-return-implicit]
+ ERROR pandas/core/dtypes/missing.py:653:16-31: Returned type `Index | ndarray | Unknown` is not assignable to declared return type `_ArrLikeT` [bad-return]
- ERROR pandas/core/dtypes/missing.py:655:16-43: Returned type `ndarray | Unknown` is not assignable to declared return type `_ArrLikeT` [bad-return]
+ ERROR pandas/core/dtypes/missing.py:655:16-43: Returned type `Index | ndarray | Unknown` is not assignable to declared return type `_ArrLikeT` [bad-return]
+ ERROR pandas/core/frame.py:2267:42-51: Cannot index into `list[ArrayLike]` [bad-index]
+ ERROR pandas/core/frame.py:2267:42-51: Cannot index into `list[Unknown]` [bad-index]
+ ERROR pandas/core/frame.py:2272:35-69: Cannot index into `list[ArrayLike]` [bad-index]
+ ERROR pandas/core/frame.py:2272:35-69: Cannot index into `list[Unknown]` [bad-index]
- ERROR pandas/core/indexes/base.py:6113:16-25: Returning Any from function declared to return "Hashable" [no-any-return-explicit]
+ ERROR pandas/core/indexes/base.py:6113:16-25: Returned type `Index | Any` is not assignable to declared return type `Hashable` [bad-return]
- ERROR pandas/core/internals/construction.py:414:16-48: Implicit conversion of `Literal[False] | ndarray[tuple[Any, ...], dtype[numpy.bool]] | Any` to `bool` is not allowed [implicit-bool]
+ ERROR pandas/core/internals/construction.py:412:21-37: Cannot index into `list[Any]` [bad-index]
+ ERROR pandas/core/resample.py:2878:14-23: Object of class `Index` has no attribute `asi8` [missing-attribute]
+ ERROR pandas/core/resample.py:2889:33-36: Expected 0 positional arguments, got 1 in function `object.__init__` [bad-argument-count]
+ ERROR pandas/core/resample.py:2889:38-43: Unexpected keyword argument `dtype` in function `object.__init__` [unexpected-keyword]
+ ERROR pandas/core/resample.py:2889:44-54: Argument `ExtensionDtype | PeriodDtype | dtype` is not assignable to parameter `dtype` with type `_HasDType[dtype] | _HasNumPyDType[dtype] | dtype | list[Any] | str | _DTypeDict | tuple[Any, Any] | type[Any] | None` in function `numpy.ndarray.__new__` [bad-argument-type]
- ERROR pandas/core/reshape/concat.py:756:23-69: The type of `overlap` is unknown; it is inferred as an implicit `Any` [unknown-variable-type]
- ERROR pandas/core/reshape/concat.py:808:19-65: The type of `overlap` is unknown; it is inferred as an implicit `Any` [unknown-variable-type]
- ERROR pandas/core/reshape/merge.py:3375:12-14: Cannot infer type of empty container; it will be treated as containing `Any` [implicit-any-empty-container]
- ERROR pandas/core/reshape/merge.py:3385:8-12: Implicit conversion of `list[Unknown]` to `bool` is not allowed [implicit-bool]
+ ERROR pandas/core/reshape/merge.py:3385:8-12: Implicit conversion of `list[Any]` to `bool` is not allowed [implicit-bool]
- ERROR pandas/io/stata.py:1286:14-22: Fixpoint iteration did not converge. Inferred result `list[LiteralString | int] | list[int | list[LiteralString | int] | list[LiteralString | int | list[LiteralString | int] | list[LiteralString | int | list[LiteralString | int] | list[Unknown] | Unknown] | list[Unknown] | Unknown] | list[LiteralString | int | list[LiteralString | int] | list[Unknown] | Unknown] | list[int | list[LiteralString | int] | list[LiteralString | int | list[LiteralString | int] | list[LiteralString | int | list[LiteralString | int] | list[Unknown] | Unknown] | list[Unknown] | Unknown] | list[LiteralString | int | list[LiteralString | int] | list[Unknown] | Unknown] | list[int | str] | list[Unknown] | str | Unknown] | list[int | str] | list[Unknown] | str | Unknown] | list[int | str] (instance attribute)`. Adding annotations may help. [non-convergent-recursion]
- ERROR pandas/io/stata.py:1286:29-38: Fixpoint iteration did not converge. Inferred result `list[dtype | list[dtype | list[dtype | list[dtype | list[dtype] | list[Unknown] | Unknown] | list[dtype] | list[Unknown] | Unknown] | list[dtype | list[dtype] | list[Unknown] | Unknown] | list[dtype | str] | list[dtype] | list[Unknown] | str | Unknown] | list[dtype | list[dtype | list[dtype] | list[Unknown] | Unknown] | list[dtype] | list[Unknown] | Unknown] | list[dtype | list[dtype] | list[Unknown] | Unknown] | list[dtype | str] | list[dtype] | list[Unknown] | str | Unknown] | list[dtype | str] | list[dtype] (instance attribute)`. Adding annotations may help. [non-convergent-recursion]
- ERROR pandas/io/stata.py:1295:14-22: Fixpoint iteration did not converge. Inferred result `list[list[list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[str] (instance attribute)`. Adding annotations may help. [non-convergent-recursion]
- ERROR pandas/io/stata.py:1298:14-22: Fixpoint iteration did not converge. Inferred result `list[list[list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[str] (instance attribute)`. Adding annotations may help. [non-convergent-recursion]
- ERROR pandas/io/stata.py:1498:16-19: The type of this argument is unknown [unknown-argument-type]
- ERROR pandas/io/stata.py:1806:24-38: Object of class `list` has no attribute `startswith` [missing-attribute]
- ERROR pandas/io/stata.py:1808:81-84: Argument `list[list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown` is not assignable to parameter `fmt` with type `str` in function `_stata_elapsed_date_to_datetime_vec` [bad-argument-type]
- ERROR pandas/io/stata.py:1813:47-60: Argument `list[list[list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[str]` is not assignable to parameter `lbllist` with type `Sequence[str]` in function `StataReader._do_convert_categoricals` [bad-argument-type]
- ERROR pandas/io/stata.py:1855:20-23: The type of this argument is unknown [unknown-argument-type]
- ERROR pandas/io/stata.py:1860:20-23: The type of this argument is unknown [unknown-argument-type]
+ ERROR pandas/io/stata.py:1930:33-50: Cannot index into `list[dtype | str]` [bad-index]
+ ERROR pandas/io/stata.py:1930:33-50: Cannot index into `list[dtype]` [bad-index]
+ ERROR pandas/io/stata.py:1930:33-50: Cannot index into `list[Unknown]` [bad-index]
+ ERROR pandas/io/stata.py:1931:32-48: Cannot index into `list[LiteralString | int]` [bad-index]
+ ERROR pandas/io/stata.py:1931:32-48: Cannot index into `list[int | str]` [bad-index]
+ ERROR pandas/io/stata.py:1931:32-48: Cannot index into `list[Unknown]` [bad-index]
+ ERROR pandas/io/stata.py:1932:32-48: Cannot index into `list[str]` [bad-index]
+ ERROR pandas/io/stata.py:1932:32-48: Cannot index into `list[Unknown]` [bad-index]
+ ERROR pandas/io/stata.py:1933:32-48: Cannot index into `list[str]` [bad-index]
+ ERROR pandas/io/stata.py:1933:32-48: Cannot index into `list[Unknown]` [bad-index]
- ERROR pandas/plotting/_matplotlib/hist.py:102:18-39: The type of `values` is unknown; it is inferred as an implicit `Any` [unknown-variable-type]
- ERROR pandas/plotting/_matplotlib/hist.py:104:39-45: The type of this argument is unknown [unknown-argument-type]
- ERROR pandas/tests/io/test_stata.py:875:49-58: Argument `list[list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown] | list[list[str] | list[Unknown] | str | Unknown] | list[str] | list[Unknown] | str | Unknown` is not assignable to parameter `x` with type `Buffer | SupportsIndex | SupportsInt | SupportsTrunc | str` in function `int.__new__` [bad-argument-type]

static-frame (https://github.com/static-frame/static-frame)
- ERROR static_frame/core/bus.py:1086:33-47: Argument `IndexBase | Iterable[TLabel]` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]

numpy-stl (https://github.com/WoLpH/numpy-stl)
- ERROR stl/base.py:763:13-34: The type of this argument is unknown [unknown-argument-type]
+ ERROR stl/base.py:1092:9-17: Class member `BaseMesh.__iter__` overrides parent class `Mapping` in an inconsistent manner [bad-override]

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

self typed as a Protocol accepts an invariant bool operand via bool <: int

1 participant