Skip to content

Commit 9b4bce9

Browse files
authored
Improve handling of overloads with ParamSpec (#12953)
1 parent c3e32e3 commit 9b4bce9

File tree

2 files changed

+44
-12
lines changed

2 files changed

+44
-12
lines changed

mypy/meet.py

+14-12
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType,
77
UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType,
88
ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardedType,
9-
ParamSpecType, Parameters, UnpackType, TypeVarTupleType,
9+
ParamSpecType, Parameters, UnpackType, TypeVarTupleType, TypeVarLikeType
1010
)
1111
from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype
1212
from mypy.erasetype import erase_type
@@ -117,8 +117,8 @@ def get_possible_variants(typ: Type) -> List[Type]:
117117
If this function receives any other type, we return a list containing just that
118118
original type. (E.g. pretend the type was contained within a singleton union).
119119
120-
The only exception is regular TypeVars: we return a list containing that TypeVar's
121-
upper bound.
120+
The only current exceptions are regular TypeVars and ParamSpecs. For these "TypeVarLike"s,
121+
we return a list containing that TypeVarLike's upper bound.
122122
123123
This function is useful primarily when checking to see if two types are overlapping:
124124
the algorithm to check if two unions are overlapping is fundamentally the same as
@@ -134,6 +134,8 @@ def get_possible_variants(typ: Type) -> List[Type]:
134134
return typ.values
135135
else:
136136
return [typ.upper_bound]
137+
elif isinstance(typ, ParamSpecType):
138+
return [typ.upper_bound]
137139
elif isinstance(typ, UnionType):
138140
return list(typ.items)
139141
elif isinstance(typ, Overloaded):
@@ -244,36 +246,36 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
244246
right_possible = get_possible_variants(right)
245247

246248
# We start by checking multi-variant types like Unions first. We also perform
247-
# the same logic if either type happens to be a TypeVar.
249+
# the same logic if either type happens to be a TypeVar/ParamSpec/TypeVarTuple.
248250
#
249-
# Handling the TypeVars now lets us simulate having them bind to the corresponding
251+
# Handling the TypeVarLikes now lets us simulate having them bind to the corresponding
250252
# type -- if we deferred these checks, the "return-early" logic of the other
251253
# checks will prevent us from detecting certain overlaps.
252254
#
253-
# If both types are singleton variants (and are not TypeVars), we've hit the base case:
255+
# If both types are singleton variants (and are not TypeVarLikes), we've hit the base case:
254256
# we skip these checks to avoid infinitely recursing.
255257

256-
def is_none_typevar_overlap(t1: Type, t2: Type) -> bool:
258+
def is_none_typevarlike_overlap(t1: Type, t2: Type) -> bool:
257259
t1, t2 = get_proper_types((t1, t2))
258-
return isinstance(t1, NoneType) and isinstance(t2, TypeVarType)
260+
return isinstance(t1, NoneType) and isinstance(t2, TypeVarLikeType)
259261

260262
if prohibit_none_typevar_overlap:
261-
if is_none_typevar_overlap(left, right) or is_none_typevar_overlap(right, left):
263+
if is_none_typevarlike_overlap(left, right) or is_none_typevarlike_overlap(right, left):
262264
return False
263265

264266
if (len(left_possible) > 1 or len(right_possible) > 1
265-
or isinstance(left, TypeVarType) or isinstance(right, TypeVarType)):
267+
or isinstance(left, TypeVarLikeType) or isinstance(right, TypeVarLikeType)):
266268
for l in left_possible:
267269
for r in right_possible:
268270
if _is_overlapping_types(l, r):
269271
return True
270272
return False
271273

272-
# Now that we've finished handling TypeVars, we're free to end early
274+
# Now that we've finished handling TypeVarLikes, we're free to end early
273275
# if one one of the types is None and we're running in strict-optional mode.
274276
# (None only overlaps with None in strict-optional mode).
275277
#
276-
# We must perform this check after the TypeVar checks because
278+
# We must perform this check after the TypeVarLike checks because
277279
# a TypeVar could be bound to None, for example.
278280

279281
if state.strict_optional and isinstance(left, NoneType) != isinstance(right, NoneType):

test-data/unit/check-overloading.test

+30
Original file line numberDiff line numberDiff line change
@@ -6506,3 +6506,33 @@ if True:
65066506
@overload
65076507
def f3(g: D) -> D: ...
65086508
def f3(g): ... # E: Name "f3" already defined on line 32
6509+
6510+
[case testOverloadingWithParamSpec]
6511+
from typing import TypeVar, Callable, Any, overload
6512+
from typing_extensions import ParamSpec, Concatenate
6513+
6514+
P = ParamSpec("P")
6515+
R = TypeVar("R")
6516+
6517+
@overload
6518+
def func(x: Callable[Concatenate[Any, P], R]) -> Callable[P, R]: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
6519+
@overload
6520+
def func(x: Callable[P, R]) -> Callable[Concatenate[str, P], R]: ...
6521+
def func(x: Callable[..., R]) -> Callable[..., R]: ...
6522+
6523+
def foo(arg1: str, arg2: int) -> bytes: ...
6524+
reveal_type(func(foo)) # N: Revealed type is "def (arg2: builtins.int) -> builtins.bytes"
6525+
6526+
def bar() -> int: ...
6527+
reveal_type(func(bar)) # N: Revealed type is "def (builtins.str) -> builtins.int"
6528+
6529+
baz: Callable[[str, str], str] = lambda x, y: 'baz'
6530+
reveal_type(func(baz)) # N: Revealed type is "def (builtins.str) -> builtins.str"
6531+
6532+
eggs = lambda: 'eggs'
6533+
reveal_type(func(eggs)) # N: Revealed type is "def (builtins.str) -> builtins.str"
6534+
6535+
spam: Callable[..., str] = lambda x, y: 'baz'
6536+
reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> Any"
6537+
6538+
[builtins fixtures/paramspec.pyi]

0 commit comments

Comments
 (0)