[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.
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.
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.
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-Names. For Names, 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.
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.
It was changed to use Never in b1ce8a3, which was prompted by #13981 (comment)
There was a problem hiding this comment.
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.
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".
Neveris 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.
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.
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.
In a case like
obj.attr = value, I think it's fine if hover onobj.attrshows the type ofvalue. If it shows the type ofobj.attrprior 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.
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.
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
| 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.
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.
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 |
carljm
left a comment
There was a problem hiding this comment.
Very nice! A few thoughts, but nothing that I think requires immediate follow-up.
| 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.
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.
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.
But this change only applies to attribute expressions, so for now at least I think it's fine as-is.
| reveal_type(Foo.__class__) # revealed: Literal[type] | ||
| ``` | ||
|
|
||
| ## Module attributes |
There was a problem hiding this comment.
Should we also have a test for multi-level attribute assignment, either mod.Class.attr = ... or Class.NestedClass.attr = ...?
## 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