[ty] Treat assigned enum hooks conservatively#25958
Merged
Merged
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 94.36%. The percentage of expected errors that received a diagnostic held steady at 88.82%. The number of fully passing files held steady at 93/134. |
Memory usage reportMemory usage unchanged ✅ |
c2a4ca5 to
6428b17
Compare
|
5fdebb1 to
055291f
Compare
055291f to
2e0ccbb
Compare
## Summary
Enum metadata currently recognizes `__init__`, `__new__`, and `_generate_next_value_` only when the hook is a directly defined function. If a class assigns or reassigns one of these hooks, we can incorrectly fall through to a shadowed method and infer or validate member values against code that will not run.
```python
class Token(Enum):
_value_: int
def __new__(cls, value: int): ...
__new__ = cast(Any, external_new)
TEXT = "text"
reveal_type(Token.TEXT.value) # int
```
This resolves each hook from its effective final binding. When the final binding is a single function definition, we can inspect its parameters and return type. Other final bindings are opaque to this analysis, as with an assignment through `Any`, a descriptor, or multiple possible bindings. An opaque binding still shadows definitions later in the MRO, so we must not fall through to a function that will not run.
`__init__` and `__new__` are tracked independently: an opaque binding prevents validation against that hook and suppresses the raw `_value_` annotation fallback, while a separate function binding still validates the original member payload. Without an explicit `_value_` annotation, any opaque construction hook makes `.value` dynamic; with one, the annotation remains authoritative. An opaque `_generate_next_value_` binding affects only `auto()` members.
Custom metaclass `__prepare__` and assigned `__new__` hooks are now treated like a directly defined metaclass `__new__`, because they can rewrite the class namespace before enum members are constructed. Alias detection is also conservative whenever a construction hook can change stored values.
```python
class Kind(Enum):
_generate_next_value_ = cast(Any, external_generator)
A = auto()
B = auto()
EXPLICIT = 1
reveal_type(Kind.A.value) # Any
reveal_type(Kind.EXPLICIT.value) # Literal[1]
```
2e0ccbb to
9ae4931
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
Enum metadata currently recognizes
__init__,__new__, and_generate_next_value_only when the hook is defined as a function within the class body. If a class instead assigns or reassigns one of these hooks, we (incorrectly) fall through and infer / validate member values against a shadowed method:This PR resolves each hook from its final binding. When the binding is a single function definition, we can inspect its parameters and return type. Everything else is considered "opaque" (e.g., an assignment through
Any, a descriptor, or multiple possible bindings). An opaque binding prevents validation against a hook (like__new__) and suppresses the raw_value_annotation fallback, while a separate function binding still validates the original member payload. Without an explicit_value_annotation, any opaque construction hook makes.valuedynamic; with one, the annotation remains authoritative. An opaque_generate_next_value_binding affects onlyauto()members.Custom metaclass
__prepare__and assigned__new__hooks are both now treated like a directly defined metaclass__new__, because they can rewrite the class namespace before enum members are constructed. Alias detection is also conservative whenever a construction hook can change stored values: