-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[red-knot] Assignments to attributes #16705
Conversation
|
89f6ae6
to
8d95905
Compare
8d95905
to
9842f5c
Compare
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 great!
|
||
# error: [invalid-assignment] "Object of type `Literal[11]` is not assignable to attribute `ten` of type `Literal[10]`" | ||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `ten` on type `C` with custom `__set__` method" |
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 will want this diagnostic (and others below) to name the expected type, but this is fine to leave as follow up for when we have the new diagnostics system.
I think "data descriptor attribute... with custom __set__
method" is perhaps redundant? But also fine for now.
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 will want this diagnostic (and others below) to name the expected type
Yes, absolutely. I didn't attempt to do this here, as I think it would naturally fall out of a sub-diagnostic that would include the call binding error.
@@ -1131,21 +1222,33 @@ def _(flag: bool): | |||
def inner3(a_and_b: Intersection[A3, B3]): | |||
# error: [possibly-unbound-attribute] | |||
reveal_type(a_and_b.x) # revealed: P & Q | |||
|
|||
# error: [possibly-unbound-attribute] |
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.
"possibly unbound" is a weird phrasing for an attribute we are assigning to, but we can revisit this in future.
} | ||
|
||
Type::Intersection(intersection) => { | ||
// TODO: Handle negative intersection elements |
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 is off the top of my head, but I think we can treat all negative intersection elements as object
for this purpose (I don't think a negative type ever gives us information about attributes.) And because any positive element must inherit object
, I think we actually can totally ignore negative elements except in the case where there are zero positive elements, in which case we treat the entire intersection as implicitly object
.
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 is off the top of my head, but I think we can treat all negative intersection elements as
object
for this purpose (I don't think a negative type ever gives us information about attributes.)
Let's consider two extreme cases. ~object = object & ~object = Never
and ~Never = object & ~Never = object
. In the former case, every attribute would exist (with type Never
). So every attribute assignment to ~object
should succeed. In the latter case, only the attributes on object
exist, so almost every attribute assignment to ~Never
should fail.
So in this sense, negative intersection elements do seem to provide information about attributes? I think we have special handling for these cases and we would not end up in this branch, but I struggled to justify why we can completely neglect negative elements here.
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.
Yes, I was implicitly excluding from consideration Not[object]
, since that will simplify to Never
, and Not[Never]
since that will simplify to object
(if alone) or disappear from the intersection.
In the realm of non-total negative elements that can actually exist in our intersection representation, I don't think that negative elements can contribute information about attributes, because the type system is not closed; there are always infinite possible objects with infinite possible attributes inhabiting a negation type.
* main: [red-knot] Use `try_call_dunder` for augmented assignment (#16717) [red-knot] Document current state of attribute assignment diagnostics (#16746) [red-knot] Case sensitive module resolver (#16521) [red-knot] Very minor simplification of the render tests (#16759) [syntax-errors] Unparenthesized assignment expressions in sets and indexes (#16404) ruff_db: add a new diagnostic renderer ruff_db: add `context` configuration red_knot: plumb through `DiagnosticFormat` to the CLI ruff_db: add concise diagnostic mode [syntax-errors] Star annotations before Python 3.11 (#16545) [syntax-errors] Star expression in index before Python 3.11 (#16544) Ruff 0.11.0 (#16723) [red-knot] Preliminary tests for typing.Final (#15917) [red-knot] fix: improve type inference for binary ops on tuples (#16725) [red-knot] Assignments to attributes (#16705) [`pygrep-hooks`]: Detect file-level suppressions comments without rul… (#16720) Fallback to requires-python in certain cases when target-version is not found (#16721)
Summary
This changeset adds proper support for assignments to attributes:
In particular, the following new features are now available:
attr
. This is now fixed.type(obj).attr
is a data descriptor, we now call its__set__
method instead of trying to assign to the load-context type ofobj.attr
, which can be different for data descriptors.Follow ups
The following things are planned as follow-ups:
__setattr__
methods (see new false positive in the ecosystem results)Ecosystem changes
Some changes are related to assignments on attributes with a custom
__setattr__
method (see above). Since we didn't notice missing attributes at all in store context previously, these are new.The other changes are related to properties. We previously used their read-context type to test the assignment. That results in weird error messages, as we often see assignments to
self.property
and then we think that those are instance attributes and descriptors, leading to union types. Now we properly look them up on the meta type, see the decorated function, and try to overwrite it with the new value (as we don't understand decorators yet). Long story short: the errors are still weird, we need to understand decorators to make them go away.Test Plan
New Markdown tests