@@ -118,7 +118,9 @@ use crate::types::types::Union;
118118#[ derive( Debug , Clone ) ]
119119pub enum ClassAttribute {
120120 /// A read-write attribute with a closed form type for both get and set actions.
121- ReadWrite ( Type ) ,
121+ /// The flag is `true` when the attribute is a class-body-declared instance
122+ /// variable (annotated, not `ClassVar`), used to flag writes via the class.
123+ ReadWrite ( Type , bool ) ,
122124 /// A read-only attribute with a closed form type for get actions.
123125 ReadOnly ( Type , ReadOnlyReason ) ,
124126 /// A `NoAccess` attribute indicates that the attribute is well-defined, but does
@@ -143,7 +145,13 @@ pub enum ClassAttribute {
143145
144146impl ClassAttribute {
145147 pub fn read_write ( ty : Type ) -> Self {
146- Self :: ReadWrite ( ty)
148+ Self :: ReadWrite ( ty, false )
149+ }
150+
151+ /// Like [`read_write`](Self::read_write) but marks the attribute as a
152+ /// class-body-declared instance variable (annotated, not `ClassVar`).
153+ pub fn read_write_instance_var ( ty : Type ) -> Self {
154+ Self :: ReadWrite ( ty, true )
147155 }
148156
149157 pub fn read_only ( ty : Type , reason : ReadOnlyReason ) -> Self {
@@ -183,7 +191,7 @@ impl ClassAttribute {
183191
184192 pub fn read_only_equivalent ( self , reason : ReadOnlyReason ) -> Self {
185193 match self {
186- Self :: ReadWrite ( ty) => Self :: ReadOnly ( ty, reason) ,
194+ Self :: ReadWrite ( ty, _ ) => Self :: ReadOnly ( ty, reason) ,
187195 Self :: Property ( getter, _, cls) => Self :: Property ( getter, None , cls) ,
188196 Self :: Descriptor ( descriptor, base) => Self :: Descriptor (
189197 Descriptor {
@@ -215,7 +223,7 @@ impl ClassAttribute {
215223 match self {
216224 // TODO(stroxler): ReadWrite attributes are not actually methods but limiting access to
217225 // ReadOnly breaks unit tests; we should investigate callsites to understand this better.
218- ClassAttribute :: ReadWrite ( ty) | ClassAttribute :: ReadOnly ( ty, _) => Some ( ty) ,
226+ ClassAttribute :: ReadWrite ( ty, _ ) | ClassAttribute :: ReadOnly ( ty, _) => Some ( ty) ,
219227 ClassAttribute :: NoAccess ( ..)
220228 | ClassAttribute :: Property ( ..)
221229 | ClassAttribute :: Descriptor ( ..)
@@ -1289,10 +1297,13 @@ fn bind_class_attribute(
12891297 cls : & ClassBase ,
12901298 attr : Type ,
12911299 read_only_reason : Option < ReadOnlyReason > ,
1300+ is_instance_var : bool ,
12921301) -> ClassAttribute {
12931302 let ty = make_bound_classmethod ( heap, cls, attr) . into_inner ( ) ;
12941303 if let Some ( reason) = read_only_reason {
12951304 ClassAttribute :: read_only ( ty, reason)
1305+ } else if is_instance_var {
1306+ ClassAttribute :: read_write_instance_var ( ty)
12961307 } else {
12971308 ClassAttribute :: read_write ( ty)
12981309 }
@@ -3569,12 +3580,16 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
35693580 }
35703581 ClassFieldInner :: ClassAttribute {
35713582 mut ty,
3583+ annotation,
35723584 is_classvar,
35733585 read_only_reason,
35743586 descriptor_range,
35753587 ..
35763588 } => {
35773589 ty = self . normalize_attr_ty ( ty) ;
3590+ // A class-body-declared instance variable (annotated, not `ClassVar`);
3591+ // writing it via the class object is flagged at the set chokepoint.
3592+ let is_instance_var = annotation. is_some ( ) && !is_classvar;
35783593 let read_only_reason = if is_classvar {
35793594 Some ( ReadOnlyReason :: ClassVar )
35803595 } else {
@@ -3599,6 +3614,8 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
35993614 ( _, ty) => {
36003615 if let Some ( reason) = read_only_reason {
36013616 ClassAttribute :: read_only ( ty, reason)
3617+ } else if is_instance_var {
3618+ ClassAttribute :: read_write_instance_var ( ty)
36023619 } else {
36033620 ClassAttribute :: read_write ( ty)
36043621 }
@@ -3654,7 +3671,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
36543671 ClassFieldInner :: Property { mut ty, .. } => {
36553672 // When accessing a property on a class (not instance), you get the property object itself
36563673 ty = self . normalize_attr_ty ( ty) ;
3657- bind_class_attribute ( self . heap , cls, ty, None )
3674+ bind_class_attribute ( self . heap , cls, ty, None , false )
36583675 }
36593676 ClassFieldInner :: Descriptor { descriptor, .. } => {
36603677 ClassAttribute :: descriptor ( descriptor, DescriptorBase :: ClassDef ( cls. clone ( ) ) )
@@ -3676,7 +3693,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
36763693 if let Some ( quantified) = self_quantified {
36773694 ty = self . wrap_with_quantified ( ty, quantified) ;
36783695 }
3679- bind_class_attribute ( self . heap , cls, ty, None )
3696+ bind_class_attribute ( self . heap , cls, ty, None , false )
36803697 }
36813698 ClassFieldInner :: ProxyMethod { .. } => ClassAttribute :: no_access (
36823699 NoAccessReason :: ProxyMethodClassAccess ( cls. class_object ( ) . dupe ( ) ) ,
@@ -3689,6 +3706,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
36893706 cls,
36903707 ty,
36913708 Some ( ReadOnlyReason :: ClassObjectInitializedOnBody ) ,
3709+ false ,
36923710 )
36933711 }
36943712 ClassFieldInner :: ClassAttribute {
@@ -3699,11 +3717,16 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
36993717 ) ) ,
37003718 ClassFieldInner :: ClassAttribute {
37013719 mut ty,
3720+ annotation,
3721+ is_classvar,
37023722 read_only_reason,
37033723 descriptor_range,
37043724 ..
37053725 } => {
37063726 ty = self . normalize_attr_ty ( ty) ;
3727+ // A class-body-declared instance variable (annotated, not `ClassVar`);
3728+ // writing it via the class object is flagged at the set chokepoint.
3729+ let is_instance_var = annotation. is_some ( ) && !is_classvar;
37073730 if ambiguous && !matches ! ( cls, ClassBase :: SelfType ( _) ) {
37083731 ClassAttribute :: no_access ( NoAccessReason :: ClassAttributeIsGeneric (
37093732 cls. class_object ( ) . dupe ( ) ,
@@ -3718,7 +3741,13 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
37183741 base : DescriptorBase :: ClassDef ( cls. clone ( ) ) ,
37193742 read_only_reason,
37203743 } ,
3721- ( _, ty) => bind_class_attribute ( self . heap , cls, ty, read_only_reason) ,
3744+ ( _, ty) => bind_class_attribute (
3745+ self . heap ,
3746+ cls,
3747+ ty,
3748+ read_only_reason,
3749+ is_instance_var,
3750+ ) ,
37223751 }
37233752 }
37243753 }
@@ -4242,7 +4271,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
42424271 } ) ;
42434272 } ;
42444273 match & mut attr {
4245- ClassAttribute :: ReadOnly ( ty, _) | ClassAttribute :: ReadWrite ( ty)
4274+ ClassAttribute :: ReadOnly ( ty, _) | ClassAttribute :: ReadWrite ( ty, _ )
42464275 if ty. has_toplevel_func_metadata ( ) =>
42474276 {
42484277 relax_ty ( ty) ;
@@ -5338,7 +5367,26 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
53385367 * should_narrow = false ;
53395368 }
53405369 }
5341- ClassAttribute :: ReadWrite ( attr_ty) => {
5370+ ClassAttribute :: ReadWrite ( attr_ty, is_instance_var) => {
5371+ // Writing a class-body-declared instance variable via the class object
5372+ // misuses it (it should be assigned on instances). Emitted before the
5373+ // type-compat check below so this kind and bad-assignment fire on
5374+ // separate paths, and before the narrowing decision so the check is
5375+ // purely additive.
5376+ if is_instance_var && instance_class. is_none ( ) && class_base. is_some ( ) {
5377+ errors
5378+ . error_builder (
5379+ range,
5380+ ErrorKind :: InstanceVarAssign ,
5381+ format ! ( "Cannot set instance variable `{attr_name}` from the class" ) ,
5382+ )
5383+ . with_detail (
5384+ "instance variables declared in a class body (without ClassVar) should be assigned on instances"
5385+ . to_owned ( ) ,
5386+ )
5387+ . emit ( ) ;
5388+ * should_narrow = false ;
5389+ }
53425390 // If the attribute has a converter, then `want` should be the type expected by the converter.
53435391 let attr_ty = match instance_class {
53445392 Some ( cls) => match self . get_dataclass_member ( cls. class_object ( ) , attr_name) {
@@ -5553,7 +5601,9 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
55535601 }
55545602 } ;
55555603 match attr {
5556- ClassAttribute :: ReadWrite ( ty) => Some ( ClassAttribute :: ReadWrite ( filter_type ( ty) ?) ) ,
5604+ ClassAttribute :: ReadWrite ( ty, is_instance_var) => {
5605+ Some ( ClassAttribute :: ReadWrite ( filter_type ( ty) ?, is_instance_var) )
5606+ }
55575607 ClassAttribute :: ReadOnly ( ty, reason) => {
55585608 Some ( ClassAttribute :: ReadOnly ( filter_type ( ty) ?, reason) )
55595609 }
@@ -5607,13 +5657,13 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
56075657 ) => Err ( Box :: new ( AttrSubsetError :: Property ) ) ,
56085658 (
56095659 ClassAttribute :: ReadOnly ( ..) ,
5610- ClassAttribute :: Property ( _, Some ( _) , _) | ClassAttribute :: ReadWrite ( _ ) ,
5660+ ClassAttribute :: Property ( _, Some ( _) , _) | ClassAttribute :: ReadWrite ( .. ) ,
56115661 ) => Err ( Box :: new ( AttrSubsetError :: ReadOnly ) ) ,
56125662 (
56135663 // TODO(stroxler): Investigate this case more: methods should be ReadOnly, but
56145664 // in some cases for unknown reasons they wind up being ReadWrite.
5615- ClassAttribute :: ReadWrite ( got) ,
5616- ClassAttribute :: ReadWrite ( want) ,
5665+ ClassAttribute :: ReadWrite ( got, _ ) ,
5666+ ClassAttribute :: ReadWrite ( want, _ ) ,
56175667 ) if got. has_toplevel_func_metadata ( ) && want. has_toplevel_func_metadata ( ) => {
56185668 is_subset ( got, want) . map_err ( |subset_error| {
56195669 Box :: new ( AttrSubsetError :: Covariant {
@@ -5625,7 +5675,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
56255675 } )
56265676 } )
56275677 }
5628- ( ClassAttribute :: ReadWrite ( got) , ClassAttribute :: ReadWrite ( want) ) => {
5678+ ( ClassAttribute :: ReadWrite ( got, _ ) , ClassAttribute :: ReadWrite ( want, _ ) ) => {
56295679 let subset_error = is_subset ( got, want)
56305680 . map_or_else ( Some , |_| is_subset ( want, got) . map_or_else ( Some , |_| None ) ) ;
56315681 if let Some ( subset_error) = subset_error {
@@ -5639,7 +5689,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
56395689 }
56405690 }
56415691 (
5642- ClassAttribute :: ReadWrite ( got) | ClassAttribute :: ReadOnly ( got, ..) ,
5692+ ClassAttribute :: ReadWrite ( got, _ ) | ClassAttribute :: ReadOnly ( got, ..) ,
56435693 ClassAttribute :: ReadOnly ( want, _) ,
56445694 ) => is_subset ( got, want) . map_err ( |subset_error| {
56455695 Box :: new ( AttrSubsetError :: Covariant {
@@ -5666,7 +5716,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
56665716 } )
56675717 } )
56685718 }
5669- ( ClassAttribute :: ReadWrite ( got) , ClassAttribute :: Property ( want, want_setter, _) ) => {
5719+ ( ClassAttribute :: ReadWrite ( got, _ ) , ClassAttribute :: Property ( want, want_setter, _) ) => {
56705720 is_subset (
56715721 // Synthesize a getter method
56725722 & self . heap . mk_callable_ellipsis ( got. clone ( ) ) ,
@@ -5767,7 +5817,7 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
57675817 ) -> Result < Type , NoAccessReason > {
57685818 match class_attr {
57695819 ClassAttribute :: NoAccess ( reason) => Err ( reason) ,
5770- ClassAttribute :: ReadWrite ( ty) | ClassAttribute :: ReadOnly ( ty, _) => Ok ( ty) ,
5820+ ClassAttribute :: ReadWrite ( ty, _ ) | ClassAttribute :: ReadOnly ( ty, _) => Ok ( ty) ,
57715821 ClassAttribute :: Property ( getter, ..) => {
57725822 self . record_property_getter ( range, & getter) ;
57735823 Ok ( self . call_property_getter ( getter, range, errors, context) )
0 commit comments