diff --git a/mypy/checker.py b/mypy/checker.py index 0ca6cef7cabba..c55305530ccc6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3772,7 +3772,17 @@ def overload_can_never_match(signature: CallableType, other: CallableType) -> bo Assumes that both signatures have overlapping argument counts. """ - return is_callable_compatible(signature, other, + # The extra erasure is needed to prevent spurious errors + # in situations where an `Any` overload is used as a fallback + # for an overload with type variables. The spurious error appears + # because the type variables turn into `Any` during unification in + # the below subtype check and (surprisingly?) `is_proper_subtype(Any, Any)` + # returns `True`. + # TODO: find a cleaner solution instead of this ad-hoc erasure. + exp_signature = expand_type(signature, {tvar.id: tvar.erase_to_union_or_bound() + for tvar in signature.variables}) + assert isinstance(exp_signature, CallableType) + return is_callable_compatible(exp_signature, other, is_compat=is_more_precise, ignore_return=True) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index a3fb582cb4327..3032aff9d061e 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1111,8 +1111,8 @@ def check_argument(leftarg: Type, rightarg: Type, variance: int) -> bool: def visit_type_var(self, left: TypeVarType) -> bool: if isinstance(self.right, TypeVarType) and left.id == self.right.id: return True - if left.values and is_subtype(UnionType.make_simplified_union(left.values), self.right, - ignore_promotions=self.ignore_promotions): + if left.values and self._is_proper_subtype(UnionType.make_simplified_union(left.values), + self.right): return True return self._is_proper_subtype(left.upper_bound, self.right) diff --git a/mypy/types.py b/mypy/types.py index 19a8d9274d0ff..8e8f950b22760 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -156,6 +156,12 @@ def new_unification_variable(old: 'TypeVarDef') -> 'TypeVarDef': return TypeVarDef(old.name, old.fullname, new_id, old.values, old.upper_bound, old.variance, old.line, old.column) + def erase_to_union_or_bound(self) -> Type: + if self.values: + return UnionType.make_simplified_union(self.values) + else: + return self.upper_bound + def __repr__(self) -> str: if self.values: return '{} in {}'.format(self.name, tuple(self.values)) diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index ae31f7304df9c..473633bc97f81 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -4310,3 +4310,67 @@ def f() -> None: [builtins fixtures/dict.pyi] [out] + +[case testOverloadConstrainedTypevarNotShadowingAny] +from lib import attr +from typing import Any + +reveal_type(attr(1)) # E: Revealed type is 'builtins.int*' +reveal_type(attr("hi")) # E: Revealed type is 'builtins.int' +x: Any +reveal_type(attr(x)) # E: Revealed type is 'Any' +attr("hi", 1) # E: No overload variant of "attr" matches argument types "str", "int" \ + # N: Possible overload variant: \ + # N: def [T in (int, float)] attr(default: T = ..., blah: int = ...) -> T \ + # N: <1 more non-matching overload not shown> +[file lib.pyi] +from typing import overload, Any, TypeVar + +T = TypeVar('T', int, float) + +@overload +def attr(default: T = ..., blah: int = ...) -> T: ... +@overload +def attr(default: Any = ...) -> int: ... +[out] + +[case testOverloadBoundedTypevarNotShadowingAny] +from lib import attr +from typing import Any + +reveal_type(attr(1)) # E: Revealed type is 'builtins.int*' +reveal_type(attr("hi")) # E: Revealed type is 'builtins.int' +x: Any +reveal_type(attr(x)) # E: Revealed type is 'Any' +attr("hi", 1) # E: No overload variant of "attr" matches argument types "str", "int" \ + # N: Possible overload variant: \ + # N: def [T <: int] attr(default: T = ..., blah: int = ...) -> T \ + # N: <1 more non-matching overload not shown> +[file lib.pyi] +from typing import overload, TypeVar, Any + +T = TypeVar('T', bound=int) + +@overload +def attr(default: T = ..., blah: int = ...) -> T: ... +@overload +def attr(default: Any = ...) -> int: ... +[out] + +[case testAnyIsOKAsFallbackInOverloads] +import stub +[file stub.pyi] +from typing import TypeVar, Any, overload + +T = TypeVar('T') + +@overload +def foo(x: T) -> T: ... +@overload +def foo(x: Any) -> Any: ... + +@overload +def bar(x: T) -> T: ... +@overload +def bar(x: Any) -> int: ... +[out]