-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Prefer declared base class attribute over inferred attribute on subclass #20764
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
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
unsupported-operator |
0 | 0 | 514 |
possibly-missing-attribute |
13 | 10 | 11 |
invalid-argument-type |
5 | 11 | 2 |
unresolved-attribute |
9 | 0 | 0 |
invalid-assignment |
2 | 0 | 1 |
non-subscriptable |
2 | 0 | 0 |
not-iterable |
2 | 0 | 0 |
| Total | 33 | 21 | 528 |
79fbd20 to
961c77c
Compare
961c77c to
8c87e6f
Compare
|
aea57b1 to
ab3f52d
Compare
| scope: ScopeId<'db>, | ||
| name: &str, | ||
| ) -> PlaceAndQualifiers<'db> { | ||
| pub(crate) fn class_member<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Member<'db> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could think about moving this to the newly created member module...
ab3f52d to
f51819b
Compare
| /// about the type, type qualifiers, boundness/declaredness, and additional | ||
| /// metadata (e.g. whether or not the member was declared) | ||
| #[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)] | ||
| pub(crate) struct Member<'db> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love the fact that we now have this Member(PlaceAndQualifiers(Place(Type))) onion structure, but each single layer has its own dedicated purpose and is used in a lot of places.
I considered adding is_declared to PlaceAndQualifiers somehow, as this flag might be relevant in other areas as well. This would be a huge refactoring, but more importantly, I think that we probably want to add new flags to Member soon (or use bitflags) in order to convey even more metadata (e.g. is this an implicit instance attribute? is the attribute a data/non-data descriptor?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like it intersects with discussions we've had a long time ago already (and several TODO comments we have) about how the Boundness part of Place currently conflates both "boundness" and "declaredness" in a way that isn't always consistent. I think we always vaguely had the intention of cleaning that up, but it never became a priority. it seems like this new flag is providing the same metadata that would be provided by that refactor, but at a new outer layer, mostly in order to avoid the need for a big refactor of Place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opened astral-sh/ty#1341 to track this.
f51819b to
05df818
Compare
| if boundness == Boundness::Bound { | ||
| if union.is_empty() { | ||
| // Short-circuit, no need to allocate inside the union builder | ||
| if is_declared { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (and the few lines below) is the key change. Everything else is just refactoring (and recording whether or not a particular Place is declared or inferred).
That's a weird diagnostic. Shouldn't |
I stumbled over the exact same thing and assumed it was just me. But we should definitely improve the diagnostic message here*. The problem is not the type of the object that we're trying to assign. The problem is in the type of the object that we're acessing the attribute on (
* ideally by adding a subdiagnostic that explains the underlying reason why the assignment to the attribute on that union type has failed (because the assignment to that attribute on one particular element failed). |
|
Ahhh, I see. Thanks! In that case, the type of the object we're trying to assign to the |
That would be one way to improve the diagnostic, yes. The other thing that I proposed would also split up that union type and add to yours: "… because it can not be set on union element |
|
yes -- both sound good ;) but not for this PR, of course. |
carljm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thank you! I definitely think we should go ahead with this, as the impact is all positive. The Member struct seems like it could still make sense for other things we want to add to it, though it does seem to me like ideally bound-vs-declared would be recorded with better precision all the way down in Place instead.
| /// about the type, type qualifiers, boundness/declaredness, and additional | ||
| /// metadata (e.g. whether or not the member was declared) | ||
| #[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)] | ||
| pub(crate) struct Member<'db> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like it intersects with discussions we've had a long time ago already (and several TODO comments we have) about how the Boundness part of Place currently conflates both "boundness" and "declaredness" in a way that isn't always consistent. I think we always vaguely had the intention of cleaning that up, but it never became a priority. it seems like this new flag is providing the same metadata that would be provided by that refactor, but at a new outer layer, mostly in order to avoid the need for a big refactor of Place?
…tity * origin/main: (24 commits) Update Python compatibility from 3.13 to 3.14 in README.md (#20852) [syntax-errors]: break outside loop F701 (#20556) [ty] Treat `Callable`s as bound-method descriptors in special cases (#20802) [ty] Do not bind self to non-positional parameters (#20850) Fix syntax error false positives on parenthesized context managers (#20846) [ty] Remove 'pre-release software' warning (#20817) Render unsupported syntax errors in formatter tests (#20777) [ty] Treat functions, methods, and dynamic types as function-like `Callable`s (#20842) [ty] Move logic for `super()` inference to a new `types::bound_super` submodule (#20840) [ty] Fix false-positive diagnostics on `super()` calls (#20814) [ty] Move `class_member` to `member` module (#20837) [`ruff`] Use DiagnosticTag for more flake8 and numpy rules (#20758) [ty] Prefer declared base class attribute over inferred attribute on subclass (#20764) [ty] Log files that are slow to type check (#20836) Update cargo-bins/cargo-binstall action to v1.15.7 (#20827) Update CodSpeedHQ/action action to v4.1.1 (#20828) Update Rust crate pyproject-toml to v0.13.7 (#20835) Update Rust crate anstream to v0.6.21 (#20829) Update Rust crate libc to v0.2.177 (#20832) Update Rust crate memchr to v2.7.6 (#20834) ...
* main: (25 commits) [ty] Diagnostic for generic classes that reference typevars in enclosing scope (#20822) Update Python compatibility from 3.13 to 3.14 in README.md (#20852) [syntax-errors]: break outside loop F701 (#20556) [ty] Treat `Callable`s as bound-method descriptors in special cases (#20802) [ty] Do not bind self to non-positional parameters (#20850) Fix syntax error false positives on parenthesized context managers (#20846) [ty] Remove 'pre-release software' warning (#20817) Render unsupported syntax errors in formatter tests (#20777) [ty] Treat functions, methods, and dynamic types as function-like `Callable`s (#20842) [ty] Move logic for `super()` inference to a new `types::bound_super` submodule (#20840) [ty] Fix false-positive diagnostics on `super()` calls (#20814) [ty] Move `class_member` to `member` module (#20837) [`ruff`] Use DiagnosticTag for more flake8 and numpy rules (#20758) [ty] Prefer declared base class attribute over inferred attribute on subclass (#20764) [ty] Log files that are slow to type check (#20836) Update cargo-bins/cargo-binstall action to v1.15.7 (#20827) Update CodSpeedHQ/action action to v4.1.1 (#20828) Update Rust crate pyproject-toml to v0.13.7 (#20835) Update Rust crate anstream to v0.6.21 (#20829) Update Rust crate libc to v0.2.177 (#20832) ...
…rable * origin/main: (26 commits) [ty] Add separate type for typevar "identity" (#20813) [ty] Diagnostic for generic classes that reference typevars in enclosing scope (#20822) Update Python compatibility from 3.13 to 3.14 in README.md (#20852) [syntax-errors]: break outside loop F701 (#20556) [ty] Treat `Callable`s as bound-method descriptors in special cases (#20802) [ty] Do not bind self to non-positional parameters (#20850) Fix syntax error false positives on parenthesized context managers (#20846) [ty] Remove 'pre-release software' warning (#20817) Render unsupported syntax errors in formatter tests (#20777) [ty] Treat functions, methods, and dynamic types as function-like `Callable`s (#20842) [ty] Move logic for `super()` inference to a new `types::bound_super` submodule (#20840) [ty] Fix false-positive diagnostics on `super()` calls (#20814) [ty] Move `class_member` to `member` module (#20837) [`ruff`] Use DiagnosticTag for more flake8 and numpy rules (#20758) [ty] Prefer declared base class attribute over inferred attribute on subclass (#20764) [ty] Log files that are slow to type check (#20836) Update cargo-bins/cargo-binstall action to v1.15.7 (#20827) Update CodSpeedHQ/action action to v4.1.1 (#20828) Update Rust crate pyproject-toml to v0.13.7 (#20835) Update Rust crate anstream to v0.6.21 (#20829) ...
Summary
When accessing an (instance) attribute on a given class, we were previously traversing its MRO, and building a union of types (if the attribute was available on multiple classes in the MRO) until we found a definitely bound symbol. The idea was that possibly unbound symbols in a subclass might only partially shadow the underlying base class attribute.
This behavior was problematic for two reasons:
self.x = None), we would have stopped iterating, even if there might be ax: str | Nonedeclaration in a base class (the bug reported in Prefer declared attribute on base class over inferred attribute on subclass ty#1067).self.x = 1in methodSub.foo), we might stop looking and miss another implicit instance attribute assignment in a base class method (e.g.self.x = 2in methodBase.bar).With this fix, we still iterate the MRO of the class, but we only stop iterating if we find a definitely declared symbol. In this case, we only return the declared attribute type. Otherwise, we keep building a union of inferred attribute types.
The implementation here seemed to be the easiest fix for astral-sh/ty#1067 that also kept the ecosystem impact low (the changes that I see all look correct). However, as the Markdown tests show, there are other things to fix in this area. For example, we should do a similar thing for class attributes. This is more involved, though (affects many different areas and probably involves a change to our descriptor protocol implementation), so I'd like to postpone this to a follow-up.
closes astral-sh/ty#1067
Test Plan
Updated Markdown tests, including a regression test for astral-sh/ty#1067.