From 2ad51aab5d3164cc88d53c36ac832b932594550b Mon Sep 17 00:00:00 2001 From: Martin Imre Date: Sun, 13 Jul 2025 09:41:38 +0200 Subject: [PATCH 1/2] feat(overloads): Expand finite sum types into a union of possible types --- mypy/checkexpr.py | 10 +++++++ mypy/typeops.py | 5 ++++ test-data/unit/check-overloading.test | 41 +++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 8223ccfe4ca0..2cf9ae5d023f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2709,6 +2709,16 @@ def check_overload_call( # Normalize unpacked kwargs before checking the call. callee = callee.with_unpacked_kwargs() arg_types = self.infer_arg_types_in_empty_context(args) + + # Expand finite sum types into unions + # See https://github.com/python/mypy/issues/14764#issuecomment-3054510950 + # And https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion + arg_types = [ + try_expanding_sum_type_to_union(arg_type, arg_type.type.fullname) + if isinstance(arg_type, Instance) else arg_type + for arg_type in arg_types + ] + # Step 1: Filter call targets to remove ones where the argument counts don't match plausible_targets = self.plausible_overload_call_targets( arg_types, arg_kinds, arg_names, callee diff --git a/mypy/typeops.py b/mypy/typeops.py index 9aa08b40a991..b2174d28457d 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -1050,7 +1050,12 @@ class Status(Enum): ] return make_simplified_union(items, contract_literals=False) + if isinstance(typ, Instance) and typ.type.fullname == target_fullname: + if isinstance(typ.last_known_value, LiteralType): + # fallback for Literal[True] and Literal[False] + return typ + if typ.type.fullname == "builtins.bool": items = [LiteralType(True, typ), LiteralType(False, typ)] return make_simplified_union(items, contract_literals=False) diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index e427d5b21d40..21cf6bec5400 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5266,6 +5266,47 @@ tmp/lib.pyi:3: error: Name "func" already defined on line 1 tmp/lib.pyi:3: error: Name "overload" is not defined main:3: note: Revealed type is "Any" +[case testOverloadCheckExpandsBools] +from typing import Literal, overload, Union + +@overload +def foo(x: Literal[False]) -> None: ... +@overload +def foo(x: Literal[True]) -> int: ... + +def foo(x: bool) -> Union[None, int]: ... + +reveal_type(foo(True)) # N: Revealed type is "builtins.int" +reveal_type(foo(False)) # N: Revealed type is "None" +x: bool +reveal_type(foo(x)) # N: Revealed type is "Union[builtins.int, None]" + +[case testOverloadCheckExpandsEnums] +from typing import Literal, overload, Union +import enum + +class Color(enum.Enum): + RED = 1 + BLUE = 2 + YELLOW = 3 + +@overload +def foo(x: Literal[Color.RED]) -> None: ... +@overload +def foo(x: Literal[Color.BLUE]) -> int: ... +@overload +def foo(x: Literal[Color.YELLOW]) -> str: ... + +def foo(x: Color) -> Union[None, int, str]: ... + +reveal_type(foo(Color.RED)) # N: Revealed type is "None" +reveal_type(foo(Color.BLUE)) # N: Revealed type is "builtins.int" +reveal_type(foo(Color.YELLOW)) # N: Revealed type is "builtins.str" + +x: Color +reveal_type(foo(x)) # N: Revealed type is "Union[None, builtins.int, builtins.str]" +[builtins fixtures/tuple.pyi] + [case testLiteralSubtypeOverlap] from typing import Literal, overload From 752bbd289c5749582818b36eb13ce33b078df83a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 07:48:18 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/checkexpr.py | 11 +++++++---- mypy/typeops.py | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2cf9ae5d023f..616c47955468 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2714,10 +2714,13 @@ def check_overload_call( # See https://github.com/python/mypy/issues/14764#issuecomment-3054510950 # And https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion arg_types = [ - try_expanding_sum_type_to_union(arg_type, arg_type.type.fullname) - if isinstance(arg_type, Instance) else arg_type - for arg_type in arg_types - ] + ( + try_expanding_sum_type_to_union(arg_type, arg_type.type.fullname) + if isinstance(arg_type, Instance) + else arg_type + ) + for arg_type in arg_types + ] # Step 1: Filter call targets to remove ones where the argument counts don't match plausible_targets = self.plausible_overload_call_targets( diff --git a/mypy/typeops.py b/mypy/typeops.py index b2174d28457d..764357eff395 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -1050,7 +1050,6 @@ class Status(Enum): ] return make_simplified_union(items, contract_literals=False) - if isinstance(typ, Instance) and typ.type.fullname == target_fullname: if isinstance(typ.last_known_value, LiteralType): # fallback for Literal[True] and Literal[False]