diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a4289804acea..f8f3b4fcd183 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2784,6 +2784,9 @@ def overload_arg_similarity(actual: Type, formal: Type) -> int: The distinction is important in cases where multiple overload items match. We want give priority to higher similarity matches. + + If strict-optional is enabled, we count (None -> object) as a promotion, even though it's + treated as a subtype elsewhere, to enable overloads between None and object. """ # Replace type variables with their upper bounds. Overloading # resolution is based on runtime behavior which erases type @@ -2809,9 +2812,12 @@ def overload_arg_similarity(actual: Type, formal: Type) -> int: # NoneTyp matches anything if we're not doing strict Optional checking return 2 else: - # NoneType is a subtype of object + # HACK: As a special case, we don't consider NoneType as a subtype of object here, as + # otherwise you wouldn't be able to define overloads between object and None. This is + # important e.g. for typing descriptors, which get an object when acting as an instance + # property and None when acting as a class property. if isinstance(formal, Instance) and formal.type.fullname() == "builtins.object": - return 2 + return 1 if isinstance(actual, UnionType): return max(overload_arg_similarity(item, formal) for item in actual.relevant_items()) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index b7f6b047bb02..61fee56e15e1 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -238,6 +238,17 @@ def f(x: int) -> int: pass reveal_type(f(None)) # E: Revealed type is 'builtins.str' reveal_type(f(0)) # E: Revealed type is 'builtins.int' +[case testOverloadWithObject] +from foo import * +[file foo.pyi] +from typing import overload +@overload +def f(x: None) -> str: pass +@overload +def f(x: object) -> int: pass +reveal_type(f(None)) # E: Revealed type is 'builtins.str' +reveal_type(f(0)) # E: Revealed type is 'builtins.int' + [case testOptionalTypeOrTypePlain] from typing import Optional def f(a: Optional[int]) -> int: