Skip to content

Make IntEnum/StrEnum values passable to functions expecting literal ints or strs #19617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ambv
Copy link
Contributor

@ambv ambv commented Aug 8, 2025

Fixes #19616.

Some design considerations:

  1. This fixes the cases in the tests, i.e. passing a MyIntEnum.ONE where a Literal[1] is expected; this is because isinstance(MyIntEnum.ONE, int) == True.
  2. This does NOT alter the case where a 1 would be passed where a MyIntEnum argument is expected: this is because two distinct enums that are semantically imcompatible might share numerical values. We want to emit errors if the user misuses an enum.
  3. The fix targets Literals' subtyping specifically as Mypy already recognizes the types on both sides correctly.

Copy link
Contributor

github-actions bot commented Aug 8, 2025

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

Copy link
Collaborator

@sterliakov sterliakov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks! Left a couple of stylistic comments, LG overall. We definitely should consider enum literals subtypes of their corresponding value literals.

# This handles IntEnum, StrEnum, and custom (int, Enum) or (str, Enum) subclasses
if (
left.is_enum_literal()
and isinstance(left.value, str) # Enum literal values are member names
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better make this assert inside the if body: we want a hard failure if enum literal somehow ended up with a non-string value

Comment on lines +984 to +988
if isinstance(enum_type, Instance) and enum_type.last_known_value is not None:
# enum_type.last_known_value is the actual value for IntEnum/StrEnum
# members
if enum_type.last_known_value.value == self.right.value:
return True
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if isinstance(enum_type, Instance) and enum_type.last_known_value is not None:
# enum_type.last_known_value is the actual value for IntEnum/StrEnum
# members
if enum_type.last_known_value.value == self.right.value:
return True
if isinstance(enum_type, Instance) and enum_type.last_known_value == self.right.value:
# enum_type.last_known_value is the actual value for IntEnum/StrEnum
# members
return True

Let's collapse the ladder?

@sterliakov
Copy link
Collaborator

Ough, Eric raised a good point in the linked ticket. This is indeed unsafe, and probably mypy should retain its current behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MyIntEnum.ONE should be passable to functions expecting Literal[1] arguments
3 participants