Skip to content

Commit 5039074

Browse files
committed
Make IntEnum/StrEnum values passable to functions expecting literal ints or strs
Fixes #19616.
1 parent 0d791b2 commit 5039074

File tree

2 files changed

+141
-1
lines changed

2 files changed

+141
-1
lines changed

mypy/subtypes.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,29 @@ def visit_typeddict_type(self, left: TypedDictType) -> bool:
964964

965965
def visit_literal_type(self, left: LiteralType) -> bool:
966966
if isinstance(self.right, LiteralType):
967-
return left == self.right
967+
if left == self.right:
968+
return True
969+
# Special case: IntEnum/StrEnum literals are subtypes of int/str literals with
970+
# the same value, e.g.: Literal[MyIntEnum.ONE] is a subtype of Literal[1]
971+
# Literal[MyStrEnum.RED] is a subtype of Literal["red"]
972+
# This handles IntEnum, StrEnum, and custom (int, Enum) or (str, Enum) subclasses
973+
if (
974+
left.is_enum_literal()
975+
and isinstance(left.value, str) # Enum literal values are member names
976+
and self._is_subtype(left.fallback, self.right.fallback)
977+
):
978+
# For IntEnum/StrEnum, check if the enum's actual value matches the literal
979+
# The enum literal's value is the member name (e.g., "ONE" or "RED")
980+
# We need to get the actual value from the enum
981+
enum_value = left.fallback.type.get(left.value)
982+
if enum_value is not None:
983+
enum_type = get_proper_type(enum_value.type)
984+
if isinstance(enum_type, Instance) and enum_type.last_known_value is not None:
985+
# enum_type.last_known_value is the actual value for IntEnum/StrEnum
986+
# members
987+
if enum_type.last_known_value.value == self.right.value:
988+
return True
989+
return False
968990
else:
969991
return self._is_subtype(left.fallback, self.right)
970992

test-data/unit/check-literal.test

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,3 +2995,121 @@ def check(obj: A[Literal[1]]) -> None:
29952995
reveal_type(g('', obj)) # E: Cannot infer value of type parameter "T" of "g" \
29962996
# N: Revealed type is "Any"
29972997
[builtins fixtures/tuple.pyi]
2998+
2999+
[case testIntEnumLiteralSubtypeOfIntLiteral]
3000+
from typing import Literal
3001+
from enum import IntEnum, Enum
3002+
3003+
class MyIntEnum(IntEnum):
3004+
ONE = 1
3005+
TWO = 2
3006+
THREE = 3
3007+
3008+
class MyCustomIntEnum(int, Enum):
3009+
ONE = 1
3010+
TWO = 2
3011+
THREE = 3
3012+
3013+
class MyEnum(Enum):
3014+
ONE = 1
3015+
TWO = 2
3016+
THREE = 3
3017+
3018+
def takes_int_literal_1(x: Literal[1]) -> None: ...
3019+
def takes_int_literal_1_2_3(x: Literal[1, 2, 3]) -> None: ...
3020+
3021+
# IntEnum literals should be accepted where int literals are expected
3022+
takes_int_literal_1(MyIntEnum.ONE) # OK
3023+
takes_int_literal_1_2_3(MyIntEnum.TWO) # OK
3024+
3025+
# Custom (int, Enum) literals should also be accepted
3026+
takes_int_literal_1(MyCustomIntEnum.ONE) # OK
3027+
takes_int_literal_1_2_3(MyCustomIntEnum.TWO) # OK
3028+
3029+
# Regular Enum literals should not be accepted
3030+
takes_int_literal_1(MyEnum.ONE) # E: Argument 1 to "takes_int_literal_1" has incompatible type "Literal[MyEnum.ONE]"; expected "Literal[1]"
3031+
takes_int_literal_1_2_3(MyEnum.TWO) # E: Argument 1 to "takes_int_literal_1_2_3" has incompatible type "Literal[MyEnum.TWO]"; expected "Literal[1, 2, 3]"
3032+
3033+
# Test assignments
3034+
x: Literal[1] = MyIntEnum.ONE # OK
3035+
y: Literal[1, 2, 3] = MyIntEnum.THREE # OK
3036+
x2: Literal[1] = MyCustomIntEnum.ONE # OK
3037+
y2: Literal[1, 2, 3] = MyCustomIntEnum.THREE # OK
3038+
z: Literal[1] = MyEnum.ONE # E: Incompatible types in assignment (expression has type "Literal[MyEnum.ONE]", variable has type "Literal[1]")
3039+
3040+
# Test wrong values
3041+
takes_int_literal_1(MyIntEnum.TWO) # E: Argument 1 to "takes_int_literal_1" has incompatible type "Literal[MyIntEnum.TWO]"; expected "Literal[1]"
3042+
takes_int_literal_1(MyCustomIntEnum.TWO) # E: Argument 1 to "takes_int_literal_1" has incompatible type "Literal[MyCustomIntEnum.TWO]"; expected "Literal[1]"
3043+
w: Literal[1] = MyIntEnum.THREE # E: Incompatible types in assignment (expression has type "Literal[MyIntEnum.THREE]", variable has type "Literal[1]")
3044+
w2: Literal[1] = MyCustomIntEnum.THREE # E: Incompatible types in assignment (expression has type "Literal[MyCustomIntEnum.THREE]", variable has type "Literal[1]")
3045+
3046+
# Test reverse direction - literal ints should NOT be accepted where enum is expected
3047+
def takes_int_enum(x: MyIntEnum) -> None: ...
3048+
def takes_custom_int_enum(x: MyCustomIntEnum) -> None: ...
3049+
3050+
takes_int_enum(1) # E: Argument 1 to "takes_int_enum" has incompatible type "int"; expected "MyIntEnum"
3051+
takes_custom_int_enum(1) # E: Argument 1 to "takes_custom_int_enum" has incompatible type "int"; expected "MyCustomIntEnum"
3052+
3053+
e1: MyIntEnum = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "MyIntEnum")
3054+
e2: MyCustomIntEnum = 1 # E: Incompatible types in assignment (expression has type "int", variable has type "MyCustomIntEnum")
3055+
3056+
[builtins fixtures/enum.pyi]
3057+
3058+
[case testStrEnumLiteralSubtypeOfStrLiteral]
3059+
from typing import Literal
3060+
from enum import StrEnum, Enum
3061+
3062+
class MyStrEnum(StrEnum):
3063+
RED = "red"
3064+
GREEN = "green"
3065+
BLUE = "blue"
3066+
3067+
class MyCustomStrEnum(str, Enum):
3068+
RED = "red"
3069+
GREEN = "green"
3070+
BLUE = "blue"
3071+
3072+
class MyEnum(Enum):
3073+
RED = "red"
3074+
GREEN = "green"
3075+
BLUE = "blue"
3076+
3077+
def takes_str_literal_red(x: Literal["red"]) -> None: ...
3078+
def takes_str_literal_colors(x: Literal["red", "green", "blue"]) -> None: ...
3079+
3080+
# StrEnum literals should be accepted where str literals are expected
3081+
takes_str_literal_red(MyStrEnum.RED) # OK
3082+
takes_str_literal_colors(MyStrEnum.GREEN) # OK
3083+
3084+
# Custom (str, Enum) literals should also be accepted
3085+
takes_str_literal_red(MyCustomStrEnum.RED) # OK
3086+
takes_str_literal_colors(MyCustomStrEnum.GREEN) # OK
3087+
3088+
# Regular Enum literals should not be accepted
3089+
takes_str_literal_red(MyEnum.RED) # E: Argument 1 to "takes_str_literal_red" has incompatible type "Literal[MyEnum.RED]"; expected "Literal['red']"
3090+
takes_str_literal_colors(MyEnum.GREEN) # E: Argument 1 to "takes_str_literal_colors" has incompatible type "Literal[MyEnum.GREEN]"; expected "Literal['red', 'green', 'blue']"
3091+
3092+
# Test assignments
3093+
x: Literal["red"] = MyStrEnum.RED # OK
3094+
y: Literal["red", "green", "blue"] = MyStrEnum.BLUE # OK
3095+
x2: Literal["red"] = MyCustomStrEnum.RED # OK
3096+
y2: Literal["red", "green", "blue"] = MyCustomStrEnum.BLUE # OK
3097+
z: Literal["red"] = MyEnum.RED # E: Incompatible types in assignment (expression has type "Literal[MyEnum.RED]", variable has type "Literal['red']")
3098+
3099+
# Test wrong values
3100+
takes_str_literal_red(MyStrEnum.GREEN) # E: Argument 1 to "takes_str_literal_red" has incompatible type "Literal[MyStrEnum.GREEN]"; expected "Literal['red']"
3101+
takes_str_literal_red(MyCustomStrEnum.GREEN) # E: Argument 1 to "takes_str_literal_red" has incompatible type "Literal[MyCustomStrEnum.GREEN]"; expected "Literal['red']"
3102+
w: Literal["red"] = MyStrEnum.BLUE # E: Incompatible types in assignment (expression has type "Literal[MyStrEnum.BLUE]", variable has type "Literal['red']")
3103+
w2: Literal["red"] = MyCustomStrEnum.BLUE # E: Incompatible types in assignment (expression has type "Literal[MyCustomStrEnum.BLUE]", variable has type "Literal['red']")
3104+
3105+
# Test reverse direction - literal strings should NOT be accepted where enum is expected
3106+
def takes_str_enum(x: MyStrEnum) -> None: ...
3107+
def takes_custom_str_enum(x: MyCustomStrEnum) -> None: ...
3108+
3109+
takes_str_enum("red") # E: Argument 1 to "takes_str_enum" has incompatible type "str"; expected "MyStrEnum"
3110+
takes_custom_str_enum("red") # E: Argument 1 to "takes_custom_str_enum" has incompatible type "str"; expected "MyCustomStrEnum"
3111+
3112+
e1: MyStrEnum = "red" # E: Incompatible types in assignment (expression has type "str", variable has type "MyStrEnum")
3113+
e2: MyCustomStrEnum = "red" # E: Incompatible types in assignment (expression has type "str", variable has type "MyCustomStrEnum")
3114+
3115+
[builtins fixtures/enum.pyi]

0 commit comments

Comments
 (0)