[ty] Recognize inherited enum member constructors#26410
Merged
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe 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. |
Memory usage reportMemory usage unchanged ✅ |
|
carljm
approved these changes
Jun 26, 2026
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__")) |
Contributor
There was a problem hiding this comment.
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: AnyPyright does get this right; pyrefly and mypy do not.
91bff10 to
32c95fb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.valuetype.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:This is split out from #26349 so constructor discovery can land independently of built-in enum mixin normalization.