[ty] Allow compatible function reassignment - #27329
Draft
charliermarsh wants to merge 2 commits into
Draft
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 96.84%. The percentage of expected errors that received a diagnostic held steady at 92.11%. The number of fully passing files held steady at 99/133. |
Memory usage reportMemory usage unchanged ✅ |
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-assignment |
2 | 19 | 0 |
invalid-method-override |
7 | 0 | 0 |
possibly-missing-attribute |
6 | 0 | 0 |
missing-argument |
1 | 0 | 0 |
unresolved-attribute |
0 | 1 | 0 |
| Total | 16 | 20 | 0 |
Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.
Raw diff (36 changes)
colour (https://github.com/colour-science/colour)
- colour/difference/cam16_ucs.py:165:18 error[invalid-assignment] Object of type `(...) -> Unknown` is not assignable to `Overload[(Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, coefficients: Coefficients_UCS_Luo2006, *, additional_data: Literal[False] = False) -> ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]], (Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, coefficients: Coefficients_UCS_Luo2006, *, additional_data: Literal[True]) -> DeltaE_Specification_Luo2006]`
- colour/difference/cam16_ucs.py:168:20 error[invalid-assignment] Object of type `(...) -> Unknown` is not assignable to `Overload[(Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, *, additional_data: Literal[False] = False) -> ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]], (Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, *, additional_data: Literal[True]) -> DeltaE_Specification_Luo2006]`
- colour/difference/cam16_ucs.py:173:20 error[invalid-assignment] Object of type `(...) -> Unknown` is not assignable to `Overload[(Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, *, additional_data: Literal[False] = False) -> ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]], (Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, *, additional_data: Literal[True]) -> DeltaE_Specification_Luo2006]`
- colour/difference/cam16_ucs.py:178:20 error[invalid-assignment] Object of type `(...) -> Unknown` is not assignable to `Overload[(Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, *, additional_data: Literal[False] = False) -> ndarray[tuple[Any, ...], dtype[floating[_16Bit] | floating[_32Bit] | float64]], (Jpapbp_1: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, Jpapbp_2: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | ... omitted 5 union elements, *, additional_data: Literal[True]) -> DeltaE_Specification_Luo2006]`
dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ tests/testing/internal/pytest/test_plugin.py:76:18 warning[possibly-missing-attribute] Attribute `pytest_runtest_protocol_wrapper` may be missing on object of type `TestOptPlugin`
+ tests/testing/internal/pytest/test_plugin.py:110:18 warning[possibly-missing-attribute] Attribute `pytest_runtest_protocol_wrapper` may be missing on object of type `TestOptPlugin`
+ tests/testing/internal/pytest/test_plugin.py:154:18 warning[possibly-missing-attribute] Attribute `pytest_runtest_protocol_wrapper` may be missing on object of type `TestOptPlugin`
+ tests/testing/internal/pytest/test_plugin.py:188:18 warning[possibly-missing-attribute] Attribute `pytest_runtest_protocol_wrapper` may be missing on object of type `TestOptPlugin`
+ tests/testing/internal/pytest/test_plugin.py:583:18 warning[possibly-missing-attribute] Attribute `pytest_runtest_protocol_wrapper` may be missing on object of type `TestOptPlugin`
+ tests/testing/internal/pytest/test_plugin.py:610:18 warning[possibly-missing-attribute] Attribute `pytest_runtest_protocol_wrapper` may be missing on object of type `TestOptPlugin`
ibis (https://github.com/ibis-project/ibis)
+ ibis/examples/gen_registry.py:342:21 error[invalid-assignment] Object of type `partial[(path: Path, *, board: Unknown = ..., metadata: dict[str, dict[str, str] | None] = ..., bar: Unknown = ...) -> None]` is not assignable to `def write_pin(path: Path, *, board: Unknown, metadata: dict[str, dict[str, str] | None], bar: Unknown) -> None`
+ ibis/examples/gen_registry.py:351:17 error[missing-argument] No arguments provided for required parameters `board`, `metadata`, `bar` of bound method `Executor.submit`
pip (https://github.com/pypa/pip)
- src/pip/_vendor/urllib3/util/wait.py:107:27 error[invalid-assignment] Object of type `def poll_wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool` is not assignable to `def wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool`
- src/pip/_vendor/urllib3/util/wait.py:109:27 error[invalid-assignment] Object of type `def select_wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool` is not assignable to `def wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool`
pwndbg (https://github.com/pwndbg/pwndbg)
- pwndbg/gdblib/tui/context.py:220:24 error[invalid-assignment] Object of type `(...) -> str` is not assignable to `def _ansi_substr(self, line: str, start_char: int, end_char: int) -> str`
pytest (https://github.com/pytest-dev/pytest)
- src/_pytest/junitxml.py:370:23 error[invalid-assignment] Object of type `bound method LogXML.add_global_property(name: str, value: object) -> None` is not assignable to `def record_func(name: str, value: object) -> None`
scipy (https://github.com/scipy/scipy)
- benchmarks/benchmarks/fft_basic.py:121:9 error[unresolved-attribute] Function `next_fast_len` has no attribute `__wrapped__`
sympy (https://github.com/sympy/sympy)
+ sympy/multipledispatch/tests/test_core.py:12:12 error[invalid-assignment] Object of type `partial[(*types, *, namespace=..., on_ambiguity=...) -> Unknown]` is not assignable to `def dispatch(*types, *, namespace=..., on_ambiguity=...) -> Unknown`
- sympy/sets/tests/test_sets.py:59:9 error[invalid-assignment] Object of type `(x) -> cos` is not assignable to `def f(x) -> Unknown`
tornado (https://github.com/tornadoweb/tornado)
- tornado/util.py:430:27 error[invalid-assignment] Object of type `def _websocket_mask_python(mask: bytes, data: bytes) -> bytes` is not assignable to `def websocket_mask(mask: bytes, data: bytes) -> bytes`
trio (https://github.com/python-trio/trio)
- src/trio/_socket.py:1123:12 error[invalid-assignment] Object of type `(_SocketType, bufsize: int, flags: int = 0, /) -> Awaitable[bytes]` is not assignable to `def recv(self, buflen: int, flags: int = 0, /) -> Awaitable[bytes]`
- src/trio/_socket.py:1142:17 error[invalid-assignment] Object of type `(_SocketType, /, buffer: Buffer, nbytes: int = 0, flags: int = 0) -> Awaitable[int]` is not assignable to `def recv_into(self, /, buffer: Buffer, nbytes: int = 0, flags: int = 0) -> Awaitable[int]`
- src/trio/_socket.py:1160:16 error[invalid-assignment] Object of type `(_SocketType, bufsize: int, flags: int = 0, /) -> Awaitable[tuple[bytes, Any]]` is not assignable to `def recvfrom(self, bufsize: int, flags: int = 0, /) -> Awaitable[tuple[bytes, Any]]`
- src/trio/_socket.py:1179:21 error[invalid-assignment] Object of type `(_SocketType, /, buffer: Buffer, nbytes: int = 0, flags: int = 0) -> Awaitable[tuple[int, Any]]` is not assignable to `def recvfrom_into(self, /, buffer: Buffer, nbytes: int = 0, flags: int = 0) -> Awaitable[tuple[int, Any]]`
- src/trio/_socket.py:1201:19 error[invalid-assignment] Object of type `(_SocketType, bufsize: int, ancbufsize: int = 0, flags: int = 0, /) -> Awaitable[tuple[bytes, list[tuple[int, int, bytes]], int, Any]]` is not assignable to `def recvmsg(self, bufsize: int, ancbufsize: int = 0, flags: int = 0, /) -> Awaitable[tuple[bytes, list[tuple[int, int, bytes]], int, object]]`
- src/trio/_socket.py:1224:24 error[invalid-assignment] Object of type `(_SocketType, buffers: Iterable[Buffer], ancbufsize: int = 0, flags: int = 0, /) -> Awaitable[tuple[int, list[tuple[int, int, bytes]], int, Any]]` is not assignable to `def recvmsg_into(self, buffers: Iterable[Buffer], ancbufsize: int = 0, flags: int = 0, /) -> Awaitable[tuple[int, list[tuple[int, int, bytes]], int, object]]`
- src/trio/_socket.py:1238:12 error[invalid-assignment] Object of type `(_SocketType, data: Buffer, flags: int = 0, /) -> Awaitable[int]` is not assignable to `def send(self, bytes: Buffer, flags: int = 0, /) -> Awaitable[int]`
+ src/trio/_socket.py:1121:13 error[invalid-method-override] Invalid override of method `recv`: Definition is incompatible with `SocketType.recv`
+ src/trio/_socket.py:1134:13 error[invalid-method-override] Invalid override of method `recv_into`: Definition is incompatible with `SocketType.recv_into`
+ src/trio/_socket.py:1153:13 error[invalid-method-override] Invalid override of method `recvfrom`: Definition is incompatible with `SocketType.recvfrom`
+ src/trio/_socket.py:1171:13 error[invalid-method-override] Invalid override of method `recvfrom_into`: Definition is incompatible with `SocketType.recvfrom_into`
+ src/trio/_socket.py:1193:17 error[invalid-method-override] Invalid override of method `recvmsg`: Definition is incompatible with `SocketType.recvmsg`
+ src/trio/_socket.py:1216:17 error[invalid-method-override] Invalid override of method `recvmsg_into`: Definition is incompatible with `SocketType.recvmsg_into`
+ src/trio/_socket.py:1236:13 error[invalid-method-override] Invalid override of method `send`: Definition is incompatible with `SocketType.send`
urllib3 (https://github.com/urllib3/urllib3)
- src/urllib3/util/wait.py:107:27 error[invalid-assignment] Object of type `def poll_wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool` is not assignable to `def wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool`
- src/urllib3/util/wait.py:109:27 error[invalid-assignment] Object of type `def select_wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool` is not assignable to `def wait_for_socket(sock: socket, read: bool = False, write: bool = False, timeout: int | float | None = None) -> bool`
charliermarsh
force-pushed
the
charlie/allow-compatible-function-reassignment
branch
from
July 30, 2026 17:32
5bd6729 to
83e8a7b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a function is defined, we currently treat its name as being declared with the type of that exact function object. This makes an ordinary reassignment fail even when the replacement accepts the same arguments and returns the same type:
We should allow this assignment:
originaldoes not need to keep pointing to the same function forever. But checking only that the replacement is callable would be too permissive.Why callable signatures are not enough
Functions are descriptors. When a function is placed on a class, accessing it through an instance automatically supplies that instance as the first argument. Ordinary callable objects and class objects do not generally behave this way.
Even though
callbackwas originally defined at module scope, it can later become a class attribute. Replacing it with an arbitrary callable would let us incorrectly assume that the replacement still binds an instance.Resulting model
We separate the type of the current value from the contract used to check reassignment:
Therefore compatible functions are accepted, while callable objects, class objects, incompatible signatures, and mismatched keyword parameter names remain rejected. This is the same descriptor-preserving assignment model used for compatible method replacements in #26158.
Type-checking-only declarations
A function defined only under
TYPE_CHECKINGnever creates a function object at runtime, so it does not promise descriptor behavior:Here the definition is just a convenient way to declare a callable signature. A compatible callable object remains valid, and imports see the actual callable object instead of incorrectly treating it as a function descriptor.