Skip to content

Commit bd9e93b

Browse files
Michael0x2ailevkivskyi
authored andcommitted
Force-enable strict optional only when checking for unsafe overlaps (#5252)
Resolves #5246 Previously, we force-enabled strict optional checks when performing all overload definition checks. This ended up causing mypy to miss some errors in definitions when `--no-strict-optional` mode is enabled. This commit will now force-enable strict optional only when checking if overloads are unsafely overlapping: we use the existing mode when checking if one variant completely eclipses another or when checking the implementation.
1 parent a75fa88 commit bd9e93b

File tree

2 files changed

+119
-74
lines changed

2 files changed

+119
-74
lines changed

mypy/checker.py

Lines changed: 73 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -414,82 +414,81 @@ 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-
#
418-
# Note: we force mypy to check overload signatures in strict-optional mode
419-
# so we don't incorrectly report errors when a user tries typing an overload
420-
# that happens to have a 'if the argument is None' fallback.
421-
#
422-
# For example, the following is fine in strict-optional mode but would throw
423-
# the unsafe overlap error when strict-optional is disabled:
424-
#
425-
# @overload
426-
# def foo(x: None) -> int: ...
427-
# @overload
428-
# def foo(x: str) -> str: ...
429-
#
430-
# See Python 2's map function for a concrete example of this kind of overload.
431-
with experiments.strict_optional_set(True):
432-
is_descriptor_get = defn.info is not None and defn.name() == "__get__"
433-
for i, item in enumerate(defn.items):
434-
# TODO overloads involving decorators
435-
assert isinstance(item, Decorator)
436-
sig1 = self.function_type(item.func)
437-
assert isinstance(sig1, CallableType)
438-
439-
for j, item2 in enumerate(defn.items[i + 1:]):
440-
assert isinstance(item2, Decorator)
441-
sig2 = self.function_type(item2.func)
442-
assert isinstance(sig2, CallableType)
443-
444-
if not are_argument_counts_overlapping(sig1, sig2):
445-
continue
417+
is_descriptor_get = defn.info is not None and defn.name() == "__get__"
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)
422+
assert isinstance(sig1, CallableType)
423+
424+
for j, item2 in enumerate(defn.items[i + 1:]):
425+
assert isinstance(item2, Decorator)
426+
sig2 = self.function_type(item2.func)
427+
assert isinstance(sig2, CallableType)
428+
429+
if not are_argument_counts_overlapping(sig1, sig2):
430+
continue
446431

447-
if overload_can_never_match(sig1, sig2):
448-
self.msg.overloaded_signature_will_never_match(
449-
i + 1, i + j + 2, item2.func)
450-
elif (not is_descriptor_get
451-
and is_unsafe_overlapping_overload_signatures(sig1, sig2)):
452-
self.msg.overloaded_signatures_overlap(
453-
i + 1, i + j + 2, item.func)
454-
455-
if defn.impl:
456-
if isinstance(defn.impl, FuncDef):
457-
impl_type = defn.impl.type
458-
elif isinstance(defn.impl, Decorator):
459-
impl_type = defn.impl.var.type
460-
else:
461-
assert False, "Impl isn't the right type"
432+
if overload_can_never_match(sig1, sig2):
433+
self.msg.overloaded_signature_will_never_match(
434+
i + 1, i + j + 2, item2.func)
435+
elif not is_descriptor_get:
436+
# Note: we force mypy to check overload signatures in strict-optional mode
437+
# so we don't incorrectly report errors when a user tries typing an overload
438+
# that happens to have a 'if the argument is None' fallback.
439+
#
440+
# For example, the following is fine in strict-optional mode but would throw
441+
# the unsafe overlap error when strict-optional is disabled:
442+
#
443+
# @overload
444+
# def foo(x: None) -> int: ...
445+
# @overload
446+
# def foo(x: str) -> str: ...
447+
#
448+
# See Python 2's map function for a concrete example of this kind of overload.
449+
with experiments.strict_optional_set(True):
450+
if is_unsafe_overlapping_overload_signatures(sig1, sig2):
451+
self.msg.overloaded_signatures_overlap(
452+
i + 1, i + j + 2, item.func)
453+
454+
if defn.impl:
455+
if isinstance(defn.impl, FuncDef):
456+
impl_type = defn.impl.type
457+
elif isinstance(defn.impl, Decorator):
458+
impl_type = defn.impl.var.type
459+
else:
460+
assert False, "Impl isn't the right type"
462461

463-
# This can happen if we've got an overload with a different
464-
# decorator too -- we gave up on the types.
465-
if impl_type is None or isinstance(impl_type, AnyType):
466-
return
467-
assert isinstance(impl_type, CallableType)
468-
469-
# Is the overload alternative's arguments subtypes of the implementation's?
470-
if not is_callable_compatible(impl_type, sig1,
471-
is_compat=is_subtype,
472-
ignore_return=True):
473-
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
474-
475-
# Repeat the same unification process 'is_callable_compatible'
476-
# internally performs so we can examine the return type separately.
477-
if impl_type.variables:
478-
# Note: we set 'ignore_return=True' because 'unify_generic_callable'
479-
# normally checks the arguments and return types with differing variance.
480-
#
481-
# This is normally what we want, but for checking the validity of overload
482-
# implementations, we actually want to use the same variance for both.
483-
#
484-
# TODO: Patch 'is_callable_compatible' and 'unify_generic_callable'?
485-
# somehow so we can customize the variance in all different sorts
486-
# of ways? This would let us infer more constraints, letting us
487-
# infer more precise types.
488-
impl_type = unify_generic_callable(impl_type, sig1, ignore_return=True)
489-
490-
# Is the overload alternative's return type a subtype of the implementation's?
491-
if impl_type is not None and not is_subtype(sig1.ret_type, impl_type.ret_type):
492-
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
462+
# This can happen if we've got an overload with a different
463+
# decorator too -- we gave up on the types.
464+
if impl_type is None or isinstance(impl_type, AnyType):
465+
return
466+
assert isinstance(impl_type, CallableType)
467+
468+
# Is the overload alternative's arguments subtypes of the implementation's?
469+
if not is_callable_compatible(impl_type, sig1,
470+
is_compat=is_subtype,
471+
ignore_return=True):
472+
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
473+
474+
# Repeat the same unification process 'is_callable_compatible'
475+
# internally performs so we can examine the return type separately.
476+
if impl_type.variables:
477+
# Note: we set 'ignore_return=True' because 'unify_generic_callable'
478+
# normally checks the arguments and return types with differing variance.
479+
#
480+
# This is normally what we want, but for checking the validity of overload
481+
# implementations, we actually want to use the same variance for both.
482+
#
483+
# TODO: Patch 'is_callable_compatible' and 'unify_generic_callable'?
484+
# somehow so we can customize the variance in all different sorts
485+
# of ways? This would let us infer more constraints, letting us
486+
# infer more precise types.
487+
impl_type = unify_generic_callable(impl_type, sig1, ignore_return=True)
488+
489+
# Is the overload alternative's return type a subtype of the implementation's?
490+
if impl_type is not None and not is_subtype(sig1.ret_type, impl_type.ret_type):
491+
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
493492

494493
# Here's the scoop about generators and coroutines.
495494
#

test-data/unit/check-overloading.test

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3797,3 +3797,49 @@ def relpath(path: str) -> str: ...
37973797
@overload
37983798
def relpath(path: unicode) -> unicode: ...
37993799
[out]
3800+
3801+
[case testOverloadsWithNoneComingSecondAreAlwaysFlaggedInNoStrictOptional]
3802+
# flags: --no-strict-optional
3803+
from typing import overload
3804+
3805+
@overload
3806+
def none_first(x: None) -> None: ...
3807+
@overload
3808+
def none_first(x: int) -> int: ...
3809+
def none_first(x: int) -> int:
3810+
return x
3811+
3812+
@overload
3813+
def none_second(x: int) -> int: ...
3814+
@overload
3815+
def none_second(x: None) -> None: ... # E: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader
3816+
def none_second(x: int) -> int:
3817+
return x
3818+
3819+
[case testOverloadsWithNoneComingSecondIsOkInStrictOptional]
3820+
# flags: --strict-optional
3821+
from typing import overload, Optional
3822+
3823+
@overload
3824+
def none_first(x: None) -> None: ...
3825+
@overload
3826+
def none_first(x: int) -> int: ...
3827+
def none_first(x: Optional[int]) -> Optional[int]:
3828+
return x
3829+
3830+
@overload
3831+
def none_second(x: int) -> int: ...
3832+
@overload
3833+
def none_second(x: None) -> None: ...
3834+
def none_second(x: Optional[int]) -> Optional[int]:
3835+
return x
3836+
3837+
@overload
3838+
def none_loose_impl(x: None) -> None: ...
3839+
@overload
3840+
def none_loose_impl(x: int) -> int: ...
3841+
def none_loose_impl(x: int) -> int:
3842+
return x
3843+
[out]
3844+
main:22: error: Overloaded function implementation does not accept all possible arguments of signature 1
3845+
main:22: error: Overloaded function implementation cannot produce return type of signature 1

0 commit comments

Comments
 (0)