-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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] Invalid assignments to attributes #15613
Conversation
This comment was marked as resolved.
This comment was marked as resolved.
51cde27
to
92b828f
Compare
47c5ba1
to
07c51c8
Compare
fn infer_target<F>(&mut self, target: &ast::Expr, value: &ast::Expr, to_assigned_ty: F) | ||
where | ||
F: Fn(&'db dyn Db, Type<'db>) -> Type<'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 went through three or four different iterations of this API here. I'm still not satisfied. If we want to pass in the assigned_ty
directly, we need to call infer_standalone_expression
at the call site. But we are only allowed to do that if the target
is not a ast::Expr::Name(_)
, so we need to basically copy the whole match
statement below to the call site. The duplication maybe not so bad, but I could some expression types are not yet handled here (see pre-existing TODO), and then we would need to remember to update it everywhere.
Maybe my struggle to incorporate this here is also a sign that I'm doing something wrong?
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 think it's fine. An altenrative, to avoid monomorphization is to have a Target
enum with a Name
, and another variant but naming kind of gets awkward.
What's the reason that we only infer the value's type for name? Is it because the inference for non-names happens elsewhere and it, therefore, avoids doubl-inference?
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.
What's the reason that we only infer the value's type for name? Is it because the inference for non-names happens elsewhere and it, therefore, avoids doubl-inference?
We only call infer_standalone_expression(value)
for non-Name
s. For Name
s, we call infer_definition
, which (somewhere down the line) calls infer_standalone_expression(value)
itself.
So yes, if we would call infer_standalone_expression(value)
for all targets, we would get double-inference (results e.g. in duplicated diagnostics).
|
||
Type::Never |
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.
Is it important that we return Never
here? I wasn't able to construct a Python example that would get access to this type...?
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.
It was changed to use Never
in b1ce8a3, which was prompted by #13981 (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.
You can't get access to this type in Python, the only reason we need a type for it is the "type pulling" test, which is a proxy for "what should we show if you hover over this expression in an IDE". Never
is my best answer for that (I think it's a better answer than None
), but I don't think it matters much.
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.
You can't get access to this type in Python, the only reason we need a type for it is the "type pulling" test, which is a proxy for "what should we show if you hover over this expression in an IDE".
Never
is my best answer for that (I think it's a better answer thanNone
), but I don't think it matters much.
Sorry, I should have phrased my question better: given that we "don't think it matters much", is it okay if we change the type of the obj.attr
expression in an assignment like obj.attr = value
from Never
to the type that we would get for obj.attr
in a Load
context? I'm inclined to say that this is much more helpful for a user in the IDE, which is why I thought there must have been a reason why Never
was chosen instead.
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.
To get this merged, I'm going to assume that it's okay to do this (let me know if not!).
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.
In a case like obj.attr = value
, I think it's fine if hover on obj.attr
shows the type of value
. If it shows the type of obj.attr
prior to the assignment, I think that's not good. (I haven't had a chance to fully review this PR yet so I don't know yet which it is.)
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.
In a case like
obj.attr = value
, I think it's fine if hover onobj.attr
shows the type ofvalue
. If it shows the type ofobj.attr
prior to the assignment, I think that's not good.
Okay. In that case, this will need another iteration (it's the latter).
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 just realized that until we add narrowing on assignments to attributes, there's no difference in the obj.attr = value
case. But in the obj = value
, or particularly the obj: annotation = value
case, it could be a totally different type, and I don't think it's sensible for hover on that assignment target to show its type prior to the assignment.
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.
But this change only applies to attribute expressions, so for now at least I think it's fine as-is.
Raise "invalid-assignment" diagnostics for incorrect assignments to attributes, for example: ```py class C: c: str = "a" C.c = 1 ```
3facc1d
to
818f4c1
Compare
@@ -102,7 +102,7 @@ reveal_type(C.pure_instance_variable) # revealed: str | |||
# and pyright allow this. | |||
C.pure_instance_variable = "overwritten on class" | |||
|
|||
# TODO: this should be an error (incompatible types in assignment) | |||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`" |
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 wonder if this might be a slightly friendlier error message for users?
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`" | |
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute with public type `str`" |
looks like a pre-existing issue, though, so feel free to ignore this for this PR
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'll open a follow up PR to discuss this.
# error: [invalid-assignment] "Object of type `int` is not assignable to `str`" | ||
for mod.global_symbol in IntIterable(): | ||
pass |
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.
very nice!
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.
Very nice! A few thoughts, but nothing that I think requires immediate follow-up.
@@ -3416,7 +3461,7 @@ impl<'db> TypeInferenceBuilder<'db> { | |||
ExprContext::Store => { | |||
let value_ty = self.infer_expression(value); | |||
|
|||
if let Type::Instance(instance) = value_ty { | |||
let symbol = if let Type::Instance(instance) = value_ty { |
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.
note for future: I suspect that we should allow member access to signal error conditions, rather than doing this special-casing based on the specific type out here in type inference
.. | ||
}, | ||
) => { | ||
let attribute_expr_ty = self.infer_attribute_expression(lhs_expr); |
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.
In future, if we add narrowing of attribute types, there could be a distinction between "infer the local type of this attribute expression" and "get the declared type of this attribute", and we would want the latter here, not the former. Which might also obsolete the change in this PR to the inferred type of Store
attribute expressions. But for now, there is no difference, so it's convenient to just use infer_attribute_expression
for both.
|
||
Type::Never |
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.
But this change only applies to attribute expressions, so for now at least I think it's fine as-is.
@@ -436,6 +436,40 @@ class Foo: ... | |||
reveal_type(Foo.__class__) # revealed: Literal[type] | |||
``` | |||
|
|||
## Module attributes |
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.
Should we also have a test for multi-level attribute assignment, either mod.Class.attr = ...
or Class.NestedClass.attr = ...
?
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.
## Summary Add a new test for attribute accesses in case of nested modules / classes. Resolves this comment: #15613 (comment) ## Test Plan New MD test.
Summary
Raise "invalid-assignment" diagnostics for incorrect assignments to attributes, for example:
closes #15456
Test Plan