From 74371dffd6f0351f2d9a678367cd6ad79bef768b Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Wed, 16 May 2018 19:49:43 -0400 Subject: [PATCH] Relax overload checks to allow return types to be regular subtypes (#5064) This pull request relaxes the overlapping overload checks so that if the parameter types of an alternative are a proper subtype of another, the return type needs to only be a regular subtype, not a proper subtype. The net effect is that the return type of the first alternative is allowed to be 'Any'. This *does* make overloads slightly less safe, but 'Any' was always meant to be an escape hatch, so I'm figuring this is fine. **Rationale**: My [proposed changes on overhauling overloads][0] require a few changes to typeshed -- [relevant pull request here][1]. Rather then trying to change both at the same time, I want to get typeshed working first. This was pretty easy to do, apart from the attrs stubs. The issue basically boils down to this: # Alternative 1 @overload def attrib(x: Optional[T]) -> T: ... # Alternative 2 @overload def attrib(x: None = None) -> Any: ... This code typechecks under the current system because alternative 1 completely shadows alternative 2. It fails to typecheck under the new system for the exact same reason. If we swap the two alternatives, it fails under the current system because 'Any' is not a proper subtype of 'T'. It *is*, however, regular subtype of 'T' -- hence this change. [0]: https://github.com/python/typing/issues/253#issuecomment-389262904 [1]: https://github.com/python/typeshed/pull/2138 --- mypy/checker.py | 2 +- test-data/unit/check-overloading.test | 14 ++++++++++++++ test-data/unit/lib-stub/attr.pyi | 28 +++++++++++++-------------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6cee4a633baf..8b17360028f6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3557,7 +3557,7 @@ def is_unsafe_overlapping_signatures(signature: Type, other: Type) -> bool: # Special case: all args are subtypes, and returns are subtypes if (all(is_proper_subtype(s, o) for (s, o) in zip(signature.arg_types, other.arg_types)) and - is_proper_subtype(signature.ret_type, other.ret_type)): + is_subtype(signature.ret_type, other.ret_type)): return False return not is_more_precise_signature(signature, other) return True diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 88eabcaf4a61..8450aedcec23 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1490,3 +1490,17 @@ class Child4(ParentWithDynamicImpl): [builtins fixtures/tuple.pyi] +[case testOverloadAnyIsConsideredValidReturnSubtype] +from typing import Any, overload, Optional + +@overload +def foo(x: None) -> Any: ... +@overload +def foo(x: Optional[str]) -> str: ... +def foo(x): pass + +@overload +def bar(x: None) -> object: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types +@overload +def bar(x: Optional[str]) -> str: ... +def bar(x): pass diff --git a/test-data/unit/lib-stub/attr.pyi b/test-data/unit/lib-stub/attr.pyi index 8118743f8234..cc4047ccfb94 100644 --- a/test-data/unit/lib-stub/attr.pyi +++ b/test-data/unit/lib-stub/attr.pyi @@ -8,6 +8,20 @@ _ConverterType = Callable[[Any], _T] _FilterType = Callable[[Any, Any], bool] _ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] +# This form catches explicit None or no default but with no other arguments returns Any. +@overload +def attrib(default: None = ..., + validator: None = ..., + repr: bool = ..., + cmp: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + convert: None = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: None = ..., + converter: None = ..., + factory: None = ..., + ) -> Any: ... # This form catches an explicit None or no default and infers the type from the other arguments. @overload def attrib(default: None = ..., @@ -36,20 +50,6 @@ def attrib(default: _T, converter: Optional[_ConverterType[_T]] = ..., factory: Optional[Callable[[], _T]] = ..., ) -> _T: ... -# This form catches explicit None or no default but with no other arguments returns Any. -@overload -def attrib(default: None = ..., - validator: None = ..., - repr: bool = ..., - cmp: bool = ..., - hash: Optional[bool] = ..., - init: bool = ..., - convert: None = ..., - metadata: Optional[Mapping[Any, Any]] = ..., - type: None = ..., - converter: None = ..., - factory: None = ..., - ) -> Any: ... # This form covers type=non-Type: e.g. forward references (str), Any @overload def attrib(default: Optional[_T] = ...,