Skip to content

Commit 3eea8d1

Browse files
author
Ivan Levkivskyi
committed
Allow None vs TypeVar overlap for overloads
1 parent 47f2487 commit 3eea8d1

File tree

3 files changed

+47
-11
lines changed

3 files changed

+47
-11
lines changed

mypy/checker.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7216,22 +7216,32 @@ def is_unsafe_overlapping_overload_signatures(
72167216
#
72177217
# This discrepancy is unfortunately difficult to get rid of, so we repeat the
72187218
# checks twice in both directions for now.
7219+
#
7220+
# Note that we ignore possible overlap between type variables and None. This
7221+
# is technically unsafe, but unsafety is tiny and this prevents some common
7222+
# use cases like:
7223+
# @overload
7224+
# def foo(x: None) -> None: ..
7225+
# @overload
7226+
# def foo(x: T) -> Foo[T]: ...
72197227
return is_callable_compatible(
72207228
signature,
72217229
other,
7222-
is_compat=is_overlapping_types_no_promote_no_uninhabited,
7230+
is_compat=is_overlapping_types_no_promote_no_uninhabited_no_none,
72237231
is_compat_return=lambda l, r: not is_subtype_no_promote(l, r),
72247232
ignore_return=False,
72257233
check_args_covariantly=True,
72267234
allow_partial_overlap=True,
7235+
no_unify_none=True,
72277236
) or is_callable_compatible(
72287237
other,
72297238
signature,
7230-
is_compat=is_overlapping_types_no_promote_no_uninhabited,
7239+
is_compat=is_overlapping_types_no_promote_no_uninhabited_no_none,
72317240
is_compat_return=lambda l, r: not is_subtype_no_promote(r, l),
72327241
ignore_return=False,
72337242
check_args_covariantly=False,
72347243
allow_partial_overlap=True,
7244+
no_unify_none=True,
72357245
)
72367246

72377247

@@ -7717,12 +7727,18 @@ def is_subtype_no_promote(left: Type, right: Type) -> bool:
77177727
return is_subtype(left, right, ignore_promotions=True)
77187728

77197729

7720-
def is_overlapping_types_no_promote_no_uninhabited(left: Type, right: Type) -> bool:
7730+
def is_overlapping_types_no_promote_no_uninhabited_no_none(left: Type, right: Type) -> bool:
77217731
# For the purpose of unsafe overload checks we consider list[<nothing>] and list[int]
77227732
# non-overlapping. This is consistent with how we treat list[int] and list[str] as
77237733
# non-overlapping, despite [] belongs to both. Also this will prevent false positives
77247734
# for failed type inference during unification.
7725-
return is_overlapping_types(left, right, ignore_promotions=True, ignore_uninhabited=True)
7735+
return is_overlapping_types(
7736+
left,
7737+
right,
7738+
ignore_promotions=True,
7739+
ignore_uninhabited=True,
7740+
prohibit_none_typevar_overlap=True,
7741+
)
77267742

77277743

77287744
def is_private(node_name: str) -> bool:

mypy/subtypes.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,7 @@ def is_callable_compatible(
12991299
check_args_covariantly: bool = False,
13001300
allow_partial_overlap: bool = False,
13011301
strict_concatenate: bool = False,
1302+
no_unify_none: bool = False,
13021303
) -> bool:
13031304
"""Is the left compatible with the right, using the provided compatibility check?
13041305
@@ -1415,7 +1416,9 @@ def g(x: int) -> int: ...
14151416
# (below) treats type variables on the two sides as independent.
14161417
if left.variables:
14171418
# Apply generic type variables away in left via type inference.
1418-
unified = unify_generic_callable(left, right, ignore_return=ignore_return)
1419+
unified = unify_generic_callable(
1420+
left, right, ignore_return=ignore_return, no_unify_none=True
1421+
)
14191422
if unified is None:
14201423
return False
14211424
left = unified
@@ -1427,7 +1430,9 @@ def g(x: int) -> int: ...
14271430
# So, we repeat the above checks in the opposite direction. This also
14281431
# lets us preserve the 'symmetry' property of allow_partial_overlap.
14291432
if allow_partial_overlap and right.variables:
1430-
unified = unify_generic_callable(right, left, ignore_return=ignore_return)
1433+
unified = unify_generic_callable(
1434+
right, left, ignore_return=ignore_return, no_unify_none=True
1435+
)
14311436
if unified is not None:
14321437
right = unified
14331438

@@ -1687,6 +1692,8 @@ def unify_generic_callable(
16871692
target: NormalizedCallableType,
16881693
ignore_return: bool,
16891694
return_constraint_direction: int | None = None,
1695+
*,
1696+
no_unify_none: bool = False,
16901697
) -> NormalizedCallableType | None:
16911698
"""Try to unify a generic callable type with another callable type.
16921699
@@ -1708,6 +1715,10 @@ def unify_generic_callable(
17081715
type.ret_type, target.ret_type, return_constraint_direction
17091716
)
17101717
constraints.extend(c)
1718+
if no_unify_none:
1719+
constraints = [
1720+
c for c in constraints if not isinstance(get_proper_type(c.target), NoneType)
1721+
]
17111722
inferred_vars, _ = mypy.solve.solve_constraints(type.variables, constraints)
17121723
if None in inferred_vars:
17131724
return None

test-data/unit/check-overloading.test

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2188,33 +2188,42 @@ def bar2(*x: int) -> int: ...
21882188
from typing import overload, TypeVar, Generic
21892189

21902190
T = TypeVar('T')
2191+
# The examples below are unsafe, but it is a quite common pattern
2192+
# so we ignore the possibility of type variables taking value `None`
2193+
# for the purpose of overload overlap checks.
21912194

21922195
@overload
2193-
def foo(x: None, y: None) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
2196+
def foo(x: None, y: None) -> str: ...
21942197
@overload
21952198
def foo(x: T, y: T) -> int: ...
21962199
def foo(x): ...
21972200

21982201
# What if 'T' is 'object'?
21992202
@overload
2200-
def bar(x: None, y: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
2203+
def bar(x: None, y: int) -> str: ...
22012204
@overload
22022205
def bar(x: T, y: T) -> int: ...
22032206
def bar(x, y): ...
22042207

22052208
class Wrapper(Generic[T]):
22062209
@overload
2207-
def foo(self, x: None, y: None) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
2210+
def foo(self, x: None, y: None) -> str: ...
22082211
@overload
22092212
def foo(self, x: T, y: None) -> int: ...
22102213
def foo(self, x): ...
22112214

22122215
@overload
2213-
def bar(self, x: None, y: int) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
2216+
def bar(self, x: None, y: int) -> str: ...
22142217
@overload
22152218
def bar(self, x: T, y: T) -> int: ...
22162219
def bar(self, x, y): ...
22172220

2221+
@overload
2222+
def baz(x: str, y: str) -> str: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
2223+
@overload
2224+
def baz(x: T, y: T) -> int: ...
2225+
def baz(x): ...
2226+
22182227
[case testOverloadFlagsPossibleMatches]
22192228
from wrapper import *
22202229
[file wrapper.pyi]
@@ -3996,7 +4005,7 @@ T = TypeVar('T')
39964005

39974006
class FakeAttribute(Generic[T]):
39984007
@overload
3999-
def dummy(self, instance: None, owner: Type[T]) -> 'FakeAttribute[T]': ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
4008+
def dummy(self, instance: None, owner: Type[T]) -> 'FakeAttribute[T]': ...
40004009
@overload
40014010
def dummy(self, instance: T, owner: Type[T]) -> int: ...
40024011
def dummy(self, instance: Optional[T], owner: Type[T]) -> Union['FakeAttribute[T]', int]: ...

0 commit comments

Comments
 (0)