ITADN

Spec/Conformance: subtyping callables with non-constant parameter mapping.

#2224Openrandolf-scholz 创建于 2026-03-22
R
randolf-scholzcommented
Consider the example below, which errors with all tested type checkers (mypy/pyright/ty/pyrefly): ```python from typing import Protocol class Interval: ... class Make(Protocol): def __call__(self, /, lower: float, upper: float) -> Interval: ... def make_impl( string_or_lower: str | float | None = None, /, lower: float | None = None, upper: float | None = None, ) -> Interval: ... def test() -> None: _f: Make = make_impl # ❌️ type checkers error here. ``` I believe from a pure type theory POV, this assignment should be legal, because all legal arguments to `Make` are also legal arguments to `make_impl`. The spec phrases it in the same spirit: > A callable type B is [assignable](https://typing.python.org/en/latest/spec/glossary.html#term-assignable) to a callable type A if the return type of B is assignable to the return type of A and the input signature of B accepts all possible combinations of arguments that the input signature of A accepts. All of the specific assignability rules described below derive from this general rule. And I couldn't find anything else in https://typing.python.org/en/latest/spec/callables.html#assignability-rules-for-callables that would disallow this assignment. It seems the type-checkers try to match the `KEYWORD_OR_POSITIONAL` parameters by name, which is incorrect. `Make` has 3 legal call signatures: 1. `Make(float, float)` 2. `Make(float, upper=float)` 3. `Make(lower=float, upper=float)` and all these 3 call signatures are supported by `make_impl`, but the parameter mapping is not constant: 1. `make_impl(float, float)` -> `{lower:string_or_lower, upper:lower}` 2. `Make(float, upper=float)` -> `{lower:string_or_lower, upper:upper}` 3. `Make(lower=float, upper=float)` -> `{lower:lower, upper:upper}` So either the spec should demand a constant parameter mapping, or this example should be added to the conformance tests.
0 条评论