You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
mypy is unable to assign Callable taking Concatenate to Union of that Callable and another using the same ParamSpec #15177
mypy is unable to assign Callable[Concatenate[B, C], D] to Union[Callable[Concatenate[B, C], D], Callable[C, D].
To Reproduce
fromtypingimportTypeVar, ParamSpec, Concatenate, castfromcollections.abcimportCallableA=TypeVar("A")
B=TypeVar("B")
C=ParamSpec("C")
D=TypeVar("D")
defprecall_guard_none(
a: A, b: B|None, f: Callable[Concatenate[A, B, C], D]
) ->Callable[Concatenate[B, C], D] |Callable[C, D]:
r: Callable[Concatenate[B, C], D] |Callable[C, D]
ifbisNone:
defg(b: B, *args: C.args, **kwargs: C.kwargs) ->D:
returnf(a, b, *args, **kwargs)
r=g# This should be permitted because the type of g is one of the variants of the type of r, a Unionelse:
defh(*args: C.args, **kwargs: C.kwargs) ->D:
returnf(a, cast(B, b), *args, **kwargs) # This cast is surprisingly necessaryr=hreturnr
Expected Behavior
I expect no error.
Actual Behavior
mypy produces this error:
main.py:19: error: Incompatible types in assignment (expression has type "Callable[[B, **C], D]", variable has type "Union[Callable[[B, **C], D], Callable[C, D]]") [assignment]
changed the title [-]mypy is unable to unify Callable taking Concatenate with Union of that Callable and another using the same ParamSpec[/-][+]mypy is unable to assign Callable taking Concatenate to Union of that Callable and another using the same ParamSpec[/+]on May 3, 2023
because the two functions (func1 Callable[[str, str], Any] and Callable[[str, *Any, **Any], None]) are not one the subtypes of the other (I don't know if this is correct but it seems reasonable). In is_subtype the function is checked with
To sum up, func1 is checked using ConstraintBuilderVisitor while func2 is checked with SubtypeVisitor
I have tried with this change and it works good, but it seems like an hack to me.
diff --git a/mypy/constraints.py b/mypy/constraints.py
index 33230871b..2de4ea1e9 100644
--- a/mypy/constraints.py+++ b/mypy/constraints.py@@ -298,8 +298,10 @@ def infer_constraints_if_possible(
"""
if direction == SUBTYPE_OF and not mypy.subtypes.is_subtype(erase_typevars(template), actual):
return None
- if direction == SUPERTYPE_OF and not mypy.subtypes.is_subtype(- actual, erase_typevars(template)+ if (+ direction == SUPERTYPE_OF+ and not isinstance(get_proper_type(template), CallableType)+ and not mypy.subtypes.is_subtype(actual, erase_typevars(template))
):
return None
if (
Activity
[-]mypy is unable to unify Callable taking Concatenate with Union of that Callable and another using the same ParamSpec[/-][+]mypy is unable to assign Callable taking Concatenate to Union of that Callable and another using the same ParamSpec[/+]nikochiko commentedon May 3, 2023
The problem seems to be with using
Unions inside some other type.I ran into the same issue, but with Dict.
JelleZijlstra commentedon May 3, 2023
@nikochiko that's an unrelated issue. Mypy is correct here; see https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance. Let's keep this issue focused on the ParamSpec problem.
nikochiko commentedon May 4, 2023
Ah, TIL. Thanks for the pointer.
matperc commentedon May 29, 2023
I may have the same issue but with
Optionalfunc1works,func2not.matperc commentedon May 29, 2023
I have done some research, those are my findings.
The issue seems to be originated here
mypy/mypy/constraints.py
Lines 256 to 288 in 7fe1fdd
While for
func1it checks constraints withmypy/mypy/constraints.py
Line 288 in 7fe1fdd
For
func2it enters heremypy/mypy/constraints.py
Lines 270 to 285 in 7fe1fdd
infer_constraints_if_possible, heremypy/mypy/constraints.py
Lines 301 to 304 in 7fe1fdd
func1 Callable[[str, str], Any]andCallable[[str, *Any, **Any], None]) are not one the subtypes of the other (I don't know if this is correct but it seems reasonable). Inis_subtypethe function is checked withmypy/mypy/subtypes.py
Line 332 in 7fe1fdd
To sum up,
func1is checked usingConstraintBuilderVisitorwhilefunc2is checked withSubtypeVisitorI have tried with this change and it works good, but it seems like an hack to me.
I hope that this would be useful for someone
ilevkivskyi commentedon Aug 16, 2023
The original example works on master, likely fixed by #15837