Skip to content

Commit 96486b6

Browse files
committed
Make overload definitions always strict optional
1 parent 72d9ab6 commit 96486b6

File tree

3 files changed

+145
-47
lines changed

3 files changed

+145
-47
lines changed

mypy/checker.py

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -414,53 +414,56 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
414414
def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
415415
# At this point we should have set the impl already, and all remaining
416416
# items are decorators
417-
for i, item in enumerate(defn.items):
418-
# TODO overloads involving decorators
419-
assert isinstance(item, Decorator)
420-
sig1 = self.function_type(item.func)
417+
with experiments.strict_optional_set(True):
418+
for i, item in enumerate(defn.items):
419+
# TODO overloads involving decorators
420+
assert isinstance(item, Decorator)
421+
sig1 = self.function_type(item.func)
421422

422-
for j, item2 in enumerate(defn.items[i + 1:]):
423-
assert isinstance(item2, Decorator)
424-
sig2 = self.function_type(item2.func)
423+
for j, item2 in enumerate(defn.items[i + 1:]):
424+
assert isinstance(item2, Decorator)
425+
sig2 = self.function_type(item2.func)
425426

426-
assert isinstance(sig1, CallableType)
427-
assert isinstance(sig2, CallableType)
427+
assert isinstance(sig1, CallableType)
428+
assert isinstance(sig2, CallableType)
428429

429-
if not are_argument_counts_overlapping(sig1, sig2):
430-
continue
431-
432-
if if_overload_can_never_match(sig1, sig2):
433-
self.msg.overloaded_signature_will_never_match(i + 1, i + j + 2, item2.func)
434-
elif is_unsafe_overlapping_overload_signatures(sig1, sig2):
435-
self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, item.func)
436-
if defn.impl:
437-
if isinstance(defn.impl, FuncDef):
438-
impl_type = defn.impl.type
439-
elif isinstance(defn.impl, Decorator):
440-
impl_type = defn.impl.var.type
441-
else:
442-
assert False, "Impl isn't the right type"
443-
# This can happen if we've got an overload with a different
444-
# decorator too -- we gave up on the types.
445-
if impl_type is None or isinstance(impl_type, AnyType) or sig1 is None:
446-
return
430+
if not are_argument_counts_overlapping(sig1, sig2):
431+
continue
447432

448-
assert isinstance(impl_type, CallableType)
449-
assert isinstance(sig1, CallableType)
450-
if not is_callable_compatible(impl_type, sig1,
451-
is_compat=is_subtype, ignore_return=True):
452-
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
453-
impl_type_subst = impl_type
454-
if impl_type.variables:
455-
unified = unify_generic_callable(impl_type, sig1, ignore_return=False)
456-
if unified is None:
457-
self.fail("Type variable mismatch between " +
458-
"overload signature {} and implementation".format(i + 1),
459-
defn.impl)
433+
if overload_can_never_match(sig1, sig2):
434+
self.msg.overloaded_signature_will_never_match(
435+
i + 1, i + j + 2, item2.func)
436+
elif is_unsafe_overlapping_overload_signatures(sig1, sig2):
437+
self.msg.overloaded_signatures_overlap(
438+
i + 1, i + j + 2, item.func)
439+
if defn.impl:
440+
if isinstance(defn.impl, FuncDef):
441+
impl_type = defn.impl.type
442+
elif isinstance(defn.impl, Decorator):
443+
impl_type = defn.impl.var.type
444+
else:
445+
assert False, "Impl isn't the right type"
446+
# This can happen if we've got an overload with a different
447+
# decorator too -- we gave up on the types.
448+
if impl_type is None or isinstance(impl_type, AnyType) or sig1 is None:
460449
return
461-
impl_type_subst = unified
462-
if not is_subtype(sig1.ret_type, impl_type_subst.ret_type):
463-
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
450+
451+
assert isinstance(impl_type, CallableType)
452+
assert isinstance(sig1, CallableType)
453+
if not is_callable_compatible(impl_type, sig1,
454+
is_compat=is_subtype, ignore_return=True):
455+
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
456+
impl_type_subst = impl_type
457+
if impl_type.variables:
458+
unified = unify_generic_callable(impl_type, sig1, ignore_return=False)
459+
if unified is None:
460+
self.fail("Type variable mismatch between " +
461+
"overload signature {} and implementation".format(i + 1),
462+
defn.impl)
463+
return
464+
impl_type_subst = unified
465+
if not is_subtype(sig1.ret_type, impl_type_subst.ret_type):
466+
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
464467

465468
# Here's the scoop about generators and coroutines.
466469
#
@@ -3618,7 +3621,7 @@ def is_unsafe_overlapping_overload_signatures(signature: CallableType,
36183621
is_compat_return=lambda l, r: not is_subtype(r, l)))
36193622

36203623

3621-
def if_overload_can_never_match(signature: CallableType, other: CallableType) -> bool:
3624+
def overload_can_never_match(signature: CallableType, other: CallableType) -> bool:
36223625
"""Check if the 'other' method can never be matched due to 'signature'.
36233626
36243627
This can happen if signature's parameters are all strictly broader then

mypy/checkexpr.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,9 +1152,6 @@ def check_overload_call(self,
11521152
# TODO: Adjust the error message here to make it clear there was no match.
11531153
target = erased_targets[0]
11541154

1155-
'''target = self.overload_call_target(args, arg_types, arg_kinds, arg_names,
1156-
callee, context,
1157-
messages=arg_messages)'''
11581155
return self.check_call(target, args, arg_kinds, context, arg_names,
11591156
arg_messages=arg_messages,
11601157
callable_name=callable_name,
@@ -1209,7 +1206,6 @@ def infer_overload_return_type(self,
12091206
try:
12101207
# Passing `overload_messages` as the `arg_messages` parameter doesn't
12111208
# seem to reliably catch all possible errors.
1212-
#
12131209
# TODO: Figure out why
12141210
result = self.check_call(
12151211
callee=typ,

test-data/unit/check-overloading.test

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,3 +1848,102 @@ reveal_type(f(obj1)) # E: Revealed type is '__main__.A'
18481848
obj2: Union[Wrapper1[A], Wrapper2[B]]
18491849
reveal_type(f(obj2)) # E: Revealed type is 'Union[__main__.A, __main__.B]'
18501850

1851+
[case testOverloadsAndNoneWithoutStrictOptional]
1852+
# flags: --no-strict-optional
1853+
from typing import overload, Optional
1854+
1855+
@overload
1856+
def f(x: None) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
1857+
@overload
1858+
def f(x: object) -> str: ...
1859+
def f(x): ...
1860+
1861+
# We pretend strict-optional is enabled for overload definitions,
1862+
# even in non-strict optional mode
1863+
@overload
1864+
def g(x: None) -> int: ...
1865+
@overload
1866+
def g(x: int) -> str: ...
1867+
def g(x): ...
1868+
1869+
# Calls are still checked normally though
1870+
a: None
1871+
b: int
1872+
c: Optional[int]
1873+
reveal_type(g(a)) # E: Revealed type is 'builtins.int'
1874+
reveal_type(g(b)) # E: Revealed type is 'builtins.str'
1875+
reveal_type(g(c)) # E: Revealed type is 'builtins.str'
1876+
1877+
[case testOverloadsAndNoneWithStrictOptional]
1878+
# flags: --strict-optional
1879+
from typing import overload, Optional
1880+
1881+
@overload
1882+
def f(x: None) -> int: ... # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
1883+
@overload
1884+
def f(x: object) -> str: ...
1885+
def f(x): ...
1886+
1887+
@overload
1888+
def g(x: None) -> int: ...
1889+
@overload
1890+
def g(x: int) -> str: ...
1891+
def g(x): ...
1892+
1893+
a: None
1894+
b: int
1895+
c: Optional[int]
1896+
reveal_type(g(a)) # E: Revealed type is 'builtins.int'
1897+
reveal_type(g(b)) # E: Revealed type is 'builtins.str'
1898+
reveal_type(g(c)) # E: Revealed type is 'Union[builtins.int, builtins.str]'
1899+
1900+
[case testOverloadsNoneAndTypeVarsWithNoStrictOptional]
1901+
# flags: --no-strict-optional
1902+
from typing import Callable, Iterable, TypeVar, overload, Optional
1903+
1904+
T = TypeVar('T')
1905+
S = TypeVar('S')
1906+
1907+
@overload
1908+
def mymap(func: None, seq: Iterable[T]) -> Iterable[T]: ...
1909+
@overload
1910+
def mymap(func: Callable[[T], S], seq: Iterable[T]) -> Iterable[S]: ...
1911+
def mymap(*args): ...
1912+
1913+
seq = [1, 2, 3]
1914+
f1: Callable[[int], str]
1915+
f2: None
1916+
f3: Optional[Callable[[int], str]]
1917+
1918+
reveal_type(mymap(f1, seq)) # E: Revealed type is 'typing.Iterable[builtins.str*]'
1919+
reveal_type(mymap(f2, seq)) # E: Revealed type is 'typing.Iterable[builtins.int*]'
1920+
reveal_type(mymap(f3, seq)) # E: Revealed type is 'typing.Iterable[builtins.str*]'
1921+
1922+
[builtins fixtures/list.pyi]
1923+
[typing fixtures/typing-full.pyi]
1924+
1925+
[case testOverloadsNoneAndTypeVarsWithStrictOptional]
1926+
# flags: --strict-optional
1927+
from typing import Callable, Iterable, TypeVar, overload, Optional
1928+
1929+
T = TypeVar('T')
1930+
S = TypeVar('S')
1931+
1932+
@overload
1933+
def mymap(func: None, seq: Iterable[T]) -> Iterable[T]: ...
1934+
@overload
1935+
def mymap(func: Callable[[T], S], seq: Iterable[T]) -> Iterable[S]: ...
1936+
def mymap(*args): ...
1937+
1938+
seq = [1, 2, 3]
1939+
f1: Callable[[int], str]
1940+
f2: None
1941+
f3: Optional[Callable[[int], str]]
1942+
1943+
reveal_type(mymap(f1, seq)) # E: Revealed type is 'typing.Iterable[builtins.str*]'
1944+
reveal_type(mymap(f2, seq)) # E: Revealed type is 'typing.Iterable[builtins.int*]'
1945+
reveal_type(mymap(f3, seq)) # E: Revealed type is 'Union[typing.Iterable[builtins.int], typing.Iterable[builtins.str]]'
1946+
1947+
[builtins fixtures/list.pyi]
1948+
[typing fixtures/typing-full.pyi]
1949+

0 commit comments

Comments
 (0)