You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
(🐞) overloads that exhaust finite Literals(bool/Enum) not treated as exhaustive #14764
@overloaddeffoo(a: Literal[True]) ->int: ...
@overloaddeffoo(a: Literal[False]) ->str: ...
deffoo(a: bool) ->object: ...
a: boolreveal_type(foo(a)) # error: No overload variant of "foo" matches argument type "bool"
@overloaddeffoo(a: Literal[True]) ->int: ...
@overloaddeffoo(a: Literal[False]) ->str: ...
@overloaddeffoo(a: bool) ->object: ... # no error regarding impossible to match overloada: boolreveal_type(foo(a)) # object
@overloaddeffoo(a: Literal[True, False]) ->int|str: ...
@overloaddeffoo(a: bool) ->object: ... # error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broaderdeffoo(a: bool) ->object: ...
a: boolreveal_type(foo(a)) # int | str
Here mypy incorrectly forces us to implement a completely redundant overload for the non Literal case when it is already exhaustively covered by both literals. It is only when the literals are in the same overload (useless in practice, but just for demonstration) that mypy correctly handles this case.
Mypy should be doing 'union math' (or what ever it's called) to apply both literal overloads at once.
This also affects all other exhaustible Literals such as Enums.
This example is pulled directly from the docs, so I think they should be updated as well to an example that doesn't contain this confusing defect.
You are correct that in this example, mypy is not able to correctly handle the case where the argument a is a boolean value and not a Literal. This is because the Literal types in the overloads are not being unioned with the bool type in the function definition.
To address this issue, you can modify the function definition to include a union type that includes all possible values of a:
In this version, we have modified the foo function definition to include a union type that includes all possible values of a. We have also modified the return type to be a union of the possible return types from the two overload functions.
Now, mypy is able to correctly handle the case where a is a boolean value and not a Literal.
changed the title [-](🐞) `Literal` `True` and `False` not an exhaustive `bool` when in `overload`s[/-][+](🐞) `Literal` overloads not treated as exhaustive[/+]on Feb 23, 2023
changed the title [-](🐞) `Literal` overloads not treated as exhaustive[/-][+](🐞) `overload`s that exhaust finite `Literal`s(`bool`/`Enum`) not treated as exhaustive[/+]on Feb 23, 2023
I might not be following entirely. Isn't this correct behavior? There are two cases to be handled for all non-literal types - the value is known statically, and the value is not known statically. Covering the two literal cases with overloads could only ever be used for the former, and not for the latter. The bool case is necessary for when the value is not known.
If instead of taking a bool, you accepted a Literal[True, False], that should work, because you are properly communicating that all callers must be using a variable that can be statically narrowed to a specific value (or an actual literal).
@gandhis1 That's not how finite types work, and Literal types are not 'literal', they are value/singleton inhabitant types. The type bool is exactly the same as the type True | False in the same way that a union of all derived types within a sealed hierarchy is the same type as the root of that hierarchy.
I've created a draft PR here with some small change set that gets the job done for bools and enums: #19431
Note: this would be my first contribution to mypy and thus I will certainly need to do some more reading into the code base and contribution guidelines to see if this is a sound contribution.
That said, early feedback is welcome 🙂
Activity
Zeckie commentedon Feb 23, 2023
Is this issue really specific to
bool, or does it also occur with enums and unions?yehoshuaviles commentedon Feb 23, 2023
You are correct that in this example, mypy is not able to correctly handle the case where the argument a is a boolean value and not a Literal. This is because the Literal types in the overloads are not being unioned with the bool type in the function definition.
To address this issue, you can modify the function definition to include a union type that includes all possible values of a:
In this version, we have modified the foo function definition to include a union type that includes all possible values of a. We have also modified the return type to be a union of the possible return types from the two overload functions.
Now, mypy is able to correctly handle the case where a is a boolean value and not a Literal.
[-](🐞) `Literal` `True` and `False` not an exhaustive `bool` when in `overload`s[/-][+](🐞) `Literal` overloads not treated as exhaustive[/+]KotlinIsland commentedon Feb 23, 2023
It works correctly with unions, but not other exhaustible
Literals likeEnums.[-](🐞) `Literal` overloads not treated as exhaustive[/-][+](🐞) `overload`s that exhaust finite `Literal`s(`bool`/`Enum`) not treated as exhaustive[/+]KotlinIsland commentedon Feb 24, 2023
topic-overloads & topic-literal-types ?
mimre25 commentedon May 16, 2023
I also stumbled upon this. There is also a problem with type inference or so:
this yields no error, but as soon as we use
test_overload, we will get an error:What could go wrong here? The only way to get out of this situation is to add an overload that access
booland returns the union a la:gandhis1 commentedon May 16, 2023
I might not be following entirely. Isn't this correct behavior? There are two cases to be handled for all non-literal types - the value is known statically, and the value is not known statically. Covering the two literal cases with overloads could only ever be used for the former, and not for the latter. The bool case is necessary for when the value is not known.
If instead of taking a
bool, you accepted aLiteral[True, False], that should work, because you are properly communicating that all callers must be using a variable that can be statically narrowed to a specific value (or an actual literal).KotlinIsland commentedon May 16, 2023
@gandhis1 That's not how finite types work, and
Literaltypes are not 'literal', they are value/singleton inhabitant types. The typeboolis exactly the same as the typeTrue | Falsein the same way that a union of all derived types within a sealed hierarchy is the same type as the root of that hierarchy.penguinolog commentedon Jul 5, 2023
Looks like the same issue in pyright, but for them it's regression now: microsoft/pyright#5421
finite-state-machine commentedon Apr 22, 2024
Likely related: #15456
flying-sheep commentedon Feb 28, 2025
This leads to false positives:
→
https://mypy-play.net/?mypy=latest&python=3.13&gist=3947314f9a3a3016e4e5b0dc82a696f8&flags=strict
erikbwu commentedon May 23, 2025
I am facing the same problem. Any fixes yet?
srittau commentedon Jul 10, 2025
The typing spec now also agrees that the current behavior is wrong: https://typing.python.org/en/latest/spec/overload.html#argument-type-expansion
mimre25 commentedon Jul 13, 2025
I've created a draft PR here with some small change set that gets the job done for bools and enums:
#19431
Note: this would be my first contribution to mypy and thus I will certainly need to do some more reading into the code base and contribution guidelines to see if this is a sound contribution.
That said, early feedback is welcome 🙂
truenicoco commentedon Dec 19, 2025
I am unsure if this is the same issue or not, but I have this similar issue, a MWE is worth a thousand words, isn't it?
Should I open a new issue or is it covered here?