Spec/Conformance: subtyping callables with non-constant parameter mapping.
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 条评论