Skip to content

Commit

Permalink
Relax overload checks to allow return types to be regular subtypes
Browse files Browse the repository at this point in the history
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]: python/typing#253 (comment)

  [1]: python/typeshed#2138
  • Loading branch information
Michael0x2a committed May 16, 2018
1 parent 25f3778 commit 5b723e6
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 15 deletions.
2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 14 additions & 14 deletions test-data/unit/lib-stub/attr.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ...,
Expand Down Expand Up @@ -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] = ...,
Expand Down

0 comments on commit 5b723e6

Please sign in to comment.