Skip to content

[ty] Recognize inherited enum member constructors#26410

Merged
charliermarsh merged 2 commits into
mainfrom
charlie/detect-inherited-enum-new-member
Jun 27, 2026
Merged

[ty] Recognize inherited enum member constructors#26410
charliermarsh merged 2 commits into
mainfrom
charlie/detect-inherited-enum-new-member

Conversation

@charliermarsh

Copy link
Copy Markdown
Member

Summary

When creating an enum class, CPython checks an inherited __new_member__ before falling back to an inherited __new__ or the data-type constructor. We previously skipped that lookup, so a parent enum could replace _value_ while we continued to expose the member's declared right-hand side as its precise .value type.

This change includes inherited __new_member__ in enum constructor discovery. When that hook is user-defined, member values now use the same conservative inference as other custom construction paths:

from enum import Enum

class Base(Enum):
    def __new_member__(cls: type["Base"], value: int) -> "Base":
        obj = object.__new__(cls)
        obj._value_ = str(value)
        return obj

class Child(Base):
    VALUE = 1

reveal_type(Child.VALUE.value)  # Any

This is split out from #26349 so constructor discovery can land independently of built-in enum mixin normalization.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Jun 26, 2026
@charliermarsh charliermarsh marked this pull request as ready for review June 26, 2026 15:08
@charliermarsh charliermarsh requested a review from a team as a code owner June 26, 2026 15:08
@astral-sh-bot astral-sh-bot Bot requested a review from carljm June 26, 2026 15:08
@astral-sh-bot

astral-sh-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 94.47%. The percentage of expected errors that received a diagnostic held steady at 89.19%. The number of fully passing files held steady at 95/134.

@astral-sh-bot

astral-sh-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Full report with detailed diff (timing results)

Comment on lines 878 to 879
.or_else(|| inherited_user_defined_enum_method(db, class, "__new_member__"))
.or_else(|| inherited_user_defined_enum_method(db, class, "__new__"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This doesn't match the runtime logic, in that the enum metaclass always saves __new__ as __new_member__, so it should be "walk the MRO and look for __new_member__ and then __new__ on each class encountered", not "first walk the MRO looking for __new_member__, then walk it looking for __new__".

Test case (I verified that at runtime it is always Parent.__new__ that is called, never Grandparent.__new_member__):

`EnumType` saves an enum's user-defined `__new__` as that class's `__new_member__`. The
immediate parent's `__new__` therefore takes precedence over an explicit `__new_member__`
in a grandparent:

```py
from enum import Enum

class Grandparent(Enum):
    def __new_member__(
        cls: type["Grandparent"], value: str
    ) -> "Grandparent":
        obj = object.__new__(cls)
        obj._value_ = value
        return obj

class Parent(Grandparent):
    def __new__(cls, value: int) -> "Parent":
        obj = object.__new__(cls)
        obj._value_ = value
        return obj

class Child(Parent):
    VALID = 1
    INVALID = "not an int"  # error: [invalid-assignment]

reveal_type(Child.VALID.value)  # revealed: Any

Pyright does get this right; pyrefly and mypy do not.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks!

@charliermarsh charliermarsh force-pushed the charlie/detect-inherited-enum-new-member branch from 91bff10 to 32c95fb Compare June 27, 2026 02:41
@charliermarsh charliermarsh merged commit df383e1 into main Jun 27, 2026
62 checks passed
@charliermarsh charliermarsh deleted the charlie/detect-inherited-enum-new-member branch June 27, 2026 02:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants