python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.5k stars 2.83k forks source link

PEP 612: Does not correctly infer behavioral subtype for callables with conflicting positional-or-keyword parameters. #12591

Open PIG208 opened 2 years ago

PIG208 commented 2 years ago

Bug Report

When two callables have a conflicting positional-or-keyword parameter, Mypy gives an error even though they have the same behavioral supertype.

To Reproduce

P = ParamSpec("P")

def foo(x: Callable[P, int], y: Callable[P, int]) -> Callable[P, bool]: ...

def x_y(x: int, y: str) -> int: ...
def y_x(y: int, x: str) -> int: ...

foo(x_y, y_x)  # Argument 1 to "foo" has incompatible type "Callable[[Arg(int, 'x'), Arg(str, 'y')], int]"; expected "Callable[[Arg(int, 'y'), Arg(str, 'x')], int]"

The example is taken from the PEP.

Expected Behavior

Note that

[A] user may include the same ParamSpec multiple times in the arguments of the same function, to indicate a dependency between multiple arguments. In these cases a type checker may choose to solve to a common behavioral supertype (i.e. a set of parameters for which all of the valid calls are valid in both of the subtypes), but is not obligated to do so.

Still, at least there should be no false positives here.

Actual Behavior

Argument 1 to "foo" has incompatible type "Callable[[Arg(int, 'x'), Arg(str, 'y')], int]"; expected "Callable[[Arg(int, 'y'), Arg(str, 'x')], int]"

Your Environment

AlexWaygood commented 2 years ago

So to be clear, the issue is that mypy should be looking for a common supertype of the two parameter specifications, and using that common supertype to solve the parameter-specification variable.

Given the two parameter specifications:

(x: int, y: str)
(y: int, x: str)

Mypy should be able to recognise that these two parameter specifications have a common "supertype". Using PEP-570 syntax, it would be something like:

(x: int, y: str, /)

And mypy should use that "common supertype" to solve the parameter-specification variable, rather than emitting an error. This means that the inferred return type of the call foo(x_y, y_x) in your example should be something like (x: int, y: str, /) -> bool.

Did I get that right?

PIG208 commented 2 years ago

Yes, that's correct.