Compare commits

...

1 Commits

Author SHA1 Message Date
David Peter
49c077d5d4 [ty] Emit unresolved-attribute diagnostics in annotated assignments 2025-05-26 19:41:33 +02:00
2 changed files with 52 additions and 2 deletions

View File

@@ -2109,6 +2109,52 @@ reveal_type(Foo.BAR.value) # revealed: @Todo(Attribute access on enum classes)
reveal_type(Foo.__members__) # revealed: @Todo(Attribute access on enum classes)
```
## Errors for unresolved and invalid attribute assignments
```py
class C:
class_attr: int = 1
def __init__(self) -> None:
self.instance_attr: int = 1
c = C()
C.class_attr = 2
c.class_attr = 2
c.instance_attr = 2
C.class_attr = "invalid" # error: [invalid-assignment]
c.class_attr = "invalid" # error: [invalid-assignment]
c.instance_attr = "invalid" # error: [invalid-assignment]
# TODO: The following attribute assignments are flagged by mypy/pyright (Type can
# not be declared in assignment to non-self attribute). We currently do not emit
# any errors and also don't validate the type of the annotation in any form. This
# should probably be changed.
C.class_attr: None = 2
c.class_attr: None = 2
c.instance_attr: None = 2
# TODO: Similar here. We do report `invalid-assignment` errors, but only because
# the value type does not match.
C.class_attr: str = "invalid" # error: [invalid-assignment]
c.class_attr: str = "invalid" # error: [invalid-assignment]
c.instance_attr: str = "invalid" # error: [invalid-assignment]
# For non-existent attributes, we emit `unresolved-attribute` errors:
C.non_existent = 2 # error: [unresolved-attribute]
c.non_existent = 2 # error: [unresolved-attribute]
# TODO: Similar to above, these should either be forbidden or validated.
#
# Make sure that we emit `unresolved-attribute` errors, at least:
C.non_existent: int = 2 # error: [unresolved-attribute]
c.non_existent: int = 2 # error: [unresolved-attribute]
C.non_existent: None = 2 # error: [unresolved-attribute]
c.non_existent: None = 2 # error: [unresolved-attribute]
```
## References
Some of the tests in the *Class and instance variables* section draw inspiration from

View File

@@ -3549,11 +3549,15 @@ impl<'db> TypeInferenceBuilder<'db> {
} = assignment;
let annotated =
self.infer_annotation_expression(annotation, DeferredExpressionState::None);
self.infer_optional_expression(value.as_deref());
// If we have an annotated assignment like `self.attr: int = 1`, we still need to
// do type inference on the `self.attr` target to get types for all sub-expressions.
self.infer_expression(target);
if let Some(value) = value {
let value_ty = self.infer_expression(value);
self.infer_target(target, value, |_builder, _value_expr| value_ty);
} else {
self.infer_expression(target);
}
// But here we explicitly overwrite the type for the overall `self.attr` node with
// the annotated type. We do no use `store_expression_type` here, because it checks