[ty] Allow enum member accesses on self#25077
Merged
Merged
Conversation
Typing conformance resultsNo changes detected ✅Current numbersThe percentage of diagnostics emitted that were expected errors held steady at 89.36%. The percentage of expected errors that received a diagnostic held steady at 85.49%. The number of fully passing files held steady at 88/134. |
da87f2a to
d60347b
Compare
Memory usage reportSummary
Significant changesClick to expand detailed breakdownprefect
sphinx
trio
flake8
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
unresolved-attribute |
0 | 7 | 0 |
| Total | 0 | 7 | 0 |
Raw diff:
cloud-init (https://github.com/canonical/cloud-init)
- tests/integration_tests/instances.py:58:34 error[unresolved-attribute] Object of type `int` has no attribute `name`
- tests/integration_tests/instances.py:58:50 error[unresolved-attribute] Object of type `int` has no attribute `name`
mkdocs (https://github.com/mkdocs/mkdocs)
- mkdocs/structure/files.py:47:29 error[unresolved-attribute] Object of type `int` has no attribute `value`
- mkdocs/structure/files.py:50:30 error[unresolved-attribute] Object of type `int` has no attribute `value`
- mkdocs/structure/files.py:53:30 error[unresolved-attribute] Object of type `int` has no attribute `value`
- mkdocs/structure/files.py:56:29 error[unresolved-attribute] Object of type `int` has no attribute `value`
- mkdocs/structure/files.py:59:30 error[unresolved-attribute] Object of type `int` has no attribute `value`3e0357a to
781d3e9
Compare
781d3e9 to
34342b6
Compare
AlexWaygood
approved these changes
May 21, 2026
Member
There was a problem hiding this comment.
It looks like they already worked fine before this PR, but I think we should also add a test for attribute access on cls in classmethods, if we don't have one already:
from enum import Enum
class Foo(Enum):
A = 1
B = 2
@classmethod
def foo(cls, x: Foo):
reveal_type(cls.A) # revealed: Literal[Foo.A]
match x:
case cls.A:
reveal_type(x) # revealed: Literal[Foo.A]
case cls.B:
reveal_type(x) # revealed: Literal[Foo.A]
case _:
reveal_type(x) # revealed: Never
Comment on lines
3532
to
3544
| self.nominal_class(db).map(|class| class.class_literal(db)) | ||
| } | ||
| _ => None, | ||
| }; |
Member
There was a problem hiding this comment.
I think we can simplify the logic here: this is only complicated because there's a missing branch in Type::nominal_class()
diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs
index febf3e89fd..447b5c2478 100644
--- a/crates/ty_python_semantic/src/types.rs
+++ b/crates/ty_python_semantic/src/types.rs
@@ -1229,6 +1229,7 @@ impl<'db> Type<'db> {
};
bound.nominal_class(db)
}
+ Type::LiteralValue(literal) => literal.fallback_instance(db).nominal_class(db),
_ => None,
}
}
@@ -3531,19 +3532,7 @@ impl<'db> Type<'db> {
| Type::TypedDict(_) => {
// Enum members can be accessed through enum instances and other enum members,
// e.g. `answer.YES` or `Answer.YES.NO`.
- let enum_class = match self {
- Type::LiteralValue(literal) => literal
- .as_enum()
- .map(|enum_literal| enum_literal.enum_class(db)),
- // This includes `Self` typevars with an enum-class upper bound, which allows
- // enum methods to access members through `self`, e.g. `case self.YES:`.
- Type::NominalInstance(_) | Type::TypeVar(_) => {
- self.nominal_class(db).map(|class| class.class_literal(db))
- }
- _ => None,
- };
-
- if let Some(enum_class) = enum_class
+ if let Some(enum_class) = self.nominal_class(db).map(|cls| cls.class_literal(db))
&& let Some(metadata) = enum_metadata(db, enum_class)
&& let Some(resolved_name) = metadata.resolve_member(&name)
{34342b6 to
c236b92
Compare
Member
Author
|
Thank you! |
thejchap
pushed a commit
to thejchap/ruff
that referenced
this pull request
May 23, 2026
## Summary
This allows enum members to be resolved when accessed through a
`Self`-typed enum instance. Previously, enum member lookup handled
class-member patterns like `case Answer.YES:`, but not equivalent
self-member patterns like:
```py
class Answer(Enum):
NO = 0
YES = 1
def is_yes(self) -> bool:
match self:
case self.YES:
return True
```
The lookup reuses the receiver’s nominal class for nominal instances and
bounded `Self` typevars before resolving enum metadata, so `case
self.YES:` narrows the same way as `case Answer.YES:`.
anishgirianish
pushed a commit
to anishgirianish/ruff
that referenced
this pull request
May 28, 2026
## Summary
This allows enum members to be resolved when accessed through a
`Self`-typed enum instance. Previously, enum member lookup handled
class-member patterns like `case Answer.YES:`, but not equivalent
self-member patterns like:
```py
class Answer(Enum):
NO = 0
YES = 1
def is_yes(self) -> bool:
match self:
case self.YES:
return True
```
The lookup reuses the receiver’s nominal class for nominal instances and
bounded `Self` typevars before resolving enum metadata, so `case
self.YES:` narrows the same way as `case Answer.YES:`.
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
This allows enum members to be resolved when accessed through a
Self-typed enum instance. Previously, enum member lookup handled class-member patterns likecase Answer.YES:, but not equivalent self-member patterns like:The lookup reuses the receiver’s nominal class for nominal instances and bounded
Selftypevars before resolving enum metadata, socase self.YES:narrows the same way ascase Answer.YES:.