From 4252609a78604deaeeb983fbf1c441b0e452e271 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Tue, 7 Jun 2022 21:18:04 +0100 Subject: [PATCH 1/2] Improve handling of overloads with `ParamSpec` --- mypy/meet.py | 8 ++++--- test-data/unit/check-overloading.test | 30 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 583503bdf614..43247a59e937 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -6,7 +6,7 @@ TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType, UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType, ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardedType, - ParamSpecType, Parameters, UnpackType, TypeVarTupleType, + ParamSpecType, Parameters, UnpackType, TypeVarTupleType, TypeVarLikeType ) from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype from mypy.erasetype import erase_type @@ -134,6 +134,8 @@ def get_possible_variants(typ: Type) -> List[Type]: return typ.values else: return [typ.upper_bound] + elif isinstance(typ, ParamSpecType): + return [typ.upper_bound] elif isinstance(typ, UnionType): return list(typ.items) elif isinstance(typ, Overloaded): @@ -255,14 +257,14 @@ def _is_overlapping_types(left: Type, right: Type) -> bool: def is_none_typevar_overlap(t1: Type, t2: Type) -> bool: t1, t2 = get_proper_types((t1, t2)) - return isinstance(t1, NoneType) and isinstance(t2, TypeVarType) + return isinstance(t1, NoneType) and isinstance(t2, TypeVarLikeType) if prohibit_none_typevar_overlap: if is_none_typevar_overlap(left, right) or is_none_typevar_overlap(right, left): return False if (len(left_possible) > 1 or len(right_possible) > 1 - or isinstance(left, TypeVarType) or isinstance(right, TypeVarType)): + or isinstance(left, TypeVarLikeType) or isinstance(right, TypeVarLikeType)): for l in left_possible: for r in right_possible: if _is_overlapping_types(l, r): diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 8259f2754bce..312d7a6cc7ae 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -6506,3 +6506,33 @@ if True: @overload def f3(g: D) -> D: ... def f3(g): ... # E: Name "f3" already defined on line 32 + +[case testOverloadingWithParamSpec] +from typing import TypeVar, Callable, Any, overload +from typing_extensions import ParamSpec, Concatenate + +P = ParamSpec("P") +R = TypeVar("R") + +@overload +def func(x: Callable[Concatenate[Any, P], R]) -> Callable[P, R]: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def func(x: Callable[P, R]) -> Callable[Concatenate[str, P], R]: ... +def func(x: Callable[..., R]) -> Callable[..., R]: ... + +def foo(arg1: str, arg2: int) -> bytes: ... +reveal_type(func(foo)) # N: Revealed type is "def (arg2: builtins.int) -> builtins.bytes" + +def bar() -> int: ... +reveal_type(func(bar)) # N: Revealed type is "def (builtins.str) -> builtins.int" + +baz: Callable[[str, str], str] = lambda x, y: 'baz' +reveal_type(func(baz)) # N: Revealed type is "def (builtins.str) -> builtins.str" + +eggs = lambda: 'eggs' +reveal_type(func(eggs)) # N: Revealed type is "def (builtins.str) -> builtins.str" + +spam: Callable[..., str] = lambda x, y: 'baz' +reveal_type(func(spam)) # N: Revealed type is "def (*Any, **Any) -> Any" + +[builtins fixtures/paramspec.pyi] From 6647d2a4b7c43289c6b7590c009c3e34389c105e Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Tue, 7 Jun 2022 22:48:19 +0100 Subject: [PATCH 2/2] Fix hauntsaninja's nits --- mypy/meet.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 43247a59e937..ebaf0f675ef1 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -117,8 +117,8 @@ def get_possible_variants(typ: Type) -> List[Type]: If this function receives any other type, we return a list containing just that original type. (E.g. pretend the type was contained within a singleton union). - The only exception is regular TypeVars: we return a list containing that TypeVar's - upper bound. + The only current exceptions are regular TypeVars and ParamSpecs. For these "TypeVarLike"s, + we return a list containing that TypeVarLike's upper bound. This function is useful primarily when checking to see if two types are overlapping: the algorithm to check if two unions are overlapping is fundamentally the same as @@ -246,21 +246,21 @@ def _is_overlapping_types(left: Type, right: Type) -> bool: right_possible = get_possible_variants(right) # We start by checking multi-variant types like Unions first. We also perform - # the same logic if either type happens to be a TypeVar. + # the same logic if either type happens to be a TypeVar/ParamSpec/TypeVarTuple. # - # Handling the TypeVars now lets us simulate having them bind to the corresponding + # Handling the TypeVarLikes now lets us simulate having them bind to the corresponding # type -- if we deferred these checks, the "return-early" logic of the other # checks will prevent us from detecting certain overlaps. # - # If both types are singleton variants (and are not TypeVars), we've hit the base case: + # If both types are singleton variants (and are not TypeVarLikes), we've hit the base case: # we skip these checks to avoid infinitely recursing. - def is_none_typevar_overlap(t1: Type, t2: Type) -> bool: + def is_none_typevarlike_overlap(t1: Type, t2: Type) -> bool: t1, t2 = get_proper_types((t1, t2)) return isinstance(t1, NoneType) and isinstance(t2, TypeVarLikeType) if prohibit_none_typevar_overlap: - if is_none_typevar_overlap(left, right) or is_none_typevar_overlap(right, left): + if is_none_typevarlike_overlap(left, right) or is_none_typevarlike_overlap(right, left): return False if (len(left_possible) > 1 or len(right_possible) > 1 @@ -271,11 +271,11 @@ def is_none_typevar_overlap(t1: Type, t2: Type) -> bool: return True return False - # Now that we've finished handling TypeVars, we're free to end early + # Now that we've finished handling TypeVarLikes, we're free to end early # if one one of the types is None and we're running in strict-optional mode. # (None only overlaps with None in strict-optional mode). # - # We must perform this check after the TypeVar checks because + # We must perform this check after the TypeVarLike checks because # a TypeVar could be bound to None, for example. if state.strict_optional and isinstance(left, NoneType) != isinstance(right, NoneType):