diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md index 4674f1495a00e..d8d77ca59ff62 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/attribute_assignment.md @@ -99,6 +99,17 @@ error[invalid-assignment]: Object of type `None` is not assignable to `str` | ``` +An annotated assignment to an existing attribute outside `self` should also be validated against the +attribute's declared type. + +```py +class C: + declared: int + +c = C() +c.declared: str = "foo" # error: [invalid-assignment] +``` + ## `ClassVar`s These can only be set on class objects: diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 947a19df69001..057bce23e50ed 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -4086,6 +4086,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let target_ty = if let Some(value_ty) = value_ty { let declared_ty = annotated.inner_type(); if value_ty.is_assignable_to(self.db(), declared_ty) { + if let ast::Expr::Attribute(attr_expr) = target.as_ref() { + if !annotated.qualifiers.contains(TypeQualifiers::FINAL) { + let object_ty = self.expression_type(&attr_expr.value); + let mut infer_assigned_ty = + |_: &mut Self, _: TypeContext<'db>| value_ty; + self.validate_attribute_assignment( + attr_expr, + object_ty, + attr_expr.attr.id(), + &mut infer_assigned_ty, + true, + ); + } + } value_ty } else { if let Some(builder) = self