@@ -818,40 +818,74 @@ def _(flag: bool):
818818 if flag:
819819 class C1 :
820820 x = 1
821+ y: int = 1
821822
822823 else :
823824 class C1 :
824825 x = 2
826+ y: int | str = " b"
825827
826828 reveal_type(C1.x) # revealed: Unknown | Literal[1, 2]
829+ reveal_type(C1.y) # revealed: int | str
830+
831+ C1.y = 100
832+ # error: [invalid-assignment] "Object of type `Literal["problematic"]` is not assignable to attribute `y` on type `Literal[C1, C1]`"
833+ C1.y = " problematic"
827834
828835 class C2 :
829836 if flag:
830837 x = 3
838+ y: int = 3
831839 else :
832840 x = 4
841+ y: int | str = " d"
833842
834843 reveal_type(C2.x) # revealed: Unknown | Literal[3, 4]
844+ reveal_type(C2.y) # revealed: int | str
845+
846+ C2.y = 100
847+ # error: [invalid-assignment] "Object of type `None` is not assignable to attribute `y` of type `int | str`"
848+ C2.y = None
849+ # TODO : should be an error, needs more sophisticated union handling in `validate_attribute_assignment`
850+ C2.y = " problematic"
835851
836852 if flag:
837853 class Meta3 (type ):
838854 x = 5
855+ y: int = 5
839856
840857 else :
841858 class Meta3 (type ):
842859 x = 6
860+ y: int | str = " f"
843861
844862 class C3 (metaclass = Meta3 ): ...
845863 reveal_type(C3.x) # revealed: Unknown | Literal[5, 6]
864+ reveal_type(C3.y) # revealed: int | str
865+
866+ C3.y = 100
867+ # error: [invalid-assignment] "Object of type `None` is not assignable to attribute `y` of type `int | str`"
868+ C3.y = None
869+ # TODO : should be an error, needs more sophisticated union handling in `validate_attribute_assignment`
870+ C3.y = " problematic"
846871
847872 class Meta4 (type ):
848873 if flag:
849874 x = 7
875+ y: int = 7
850876 else :
851877 x = 8
878+ y: int | str = " h"
852879
853880 class C4 (metaclass = Meta4 ): ...
854881 reveal_type(C4.x) # revealed: Unknown | Literal[7, 8]
882+ reveal_type(C4.y) # revealed: int | str
883+
884+ C4.y = 100
885+ # error: [invalid-assignment] "Object of type `None` is not assignable to attribute `y` of type `int | str`"
886+ C4.y = None
887+ # TODO : should be an error, needs more sophisticated union handling in `validate_attribute_assignment`
888+ C4.y = " problematic"
855889```
856890
857891## Unions with possibly unbound paths
@@ -875,8 +909,14 @@ def _(flag1: bool, flag2: bool):
875909 # error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
876910 reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
877911
912+ # error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `Literal[C1, C2, C3]`"
913+ C.x = 100
914+
878915 # error: [possibly-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound"
879916 reveal_type(C().x) # revealed: Unknown | Literal[1, 3]
917+
918+ # error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`"
919+ C().x = 100
880920```
881921
882922### Possibly-unbound within a class
@@ -901,10 +941,16 @@ def _(flag: bool, flag1: bool, flag2: bool):
901941 # error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
902942 reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
903943
944+ # error: [possibly-unbound-attribute]
945+ C.x = 100
946+
904947 # Note: we might want to consider ignoring possibly-unbound diagnostics for instance attributes eventually,
905948 # see the "Possibly unbound/undeclared instance attribute" section below.
906949 # error: [possibly-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound"
907950 reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3]
951+
952+ # error: [possibly-unbound-attribute]
953+ C().x = 100
908954```
909955
910956### Possibly-unbound within gradual types
@@ -922,6 +968,9 @@ def _(flag: bool):
922968 x: int
923969
924970 reveal_type(Derived().x) # revealed: int | Any
971+
972+ Derived().x = 1
973+ Derived().x = " a"
925974```
926975
927976### Attribute possibly unbound on a subclass but not on a superclass
@@ -936,8 +985,10 @@ def _(flag: bool):
936985 x = 2
937986
938987 reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1]
988+ Bar.x = 3
939989
940990 reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1]
991+ Bar().x = 3
941992```
942993
943994### Attribute possibly unbound on a subclass and on a superclass
@@ -955,8 +1006,14 @@ def _(flag: bool):
9551006 # error: [possibly-unbound-attribute]
9561007 reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1]
9571008
1009+ # error: [possibly-unbound-attribute]
1010+ Bar.x = 3
1011+
9581012 # error: [possibly-unbound-attribute]
9591013 reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1]
1014+
1015+ # error: [possibly-unbound-attribute]
1016+ Bar().x = 3
9601017```
9611018
9621019### Possibly unbound/undeclared instance attribute
@@ -975,6 +1032,9 @@ def _(flag: bool):
9751032
9761033 # error: [possibly-unbound-attribute]
9771034 reveal_type(Foo().x) # revealed: int | Unknown
1035+
1036+ # error: [possibly-unbound-attribute]
1037+ Foo().x = 1
9781038```
9791039
9801040#### Possibly unbound
@@ -989,6 +1049,9 @@ def _(flag: bool):
9891049 # Emitting a diagnostic in a case like this is not something we support, and it's unclear
9901050 # if we ever will (or want to)
9911051 reveal_type(Foo().x) # revealed: Unknown | Literal[1]
1052+
1053+ # Same here
1054+ Foo().x = 2
9921055```
9931056
9941057### Unions with all paths unbound
@@ -1003,6 +1066,11 @@ def _(flag: bool):
10031066
10041067 # error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
10051068 reveal_type(C.x) # revealed: Unknown
1069+
1070+ # TODO : This should ideally be a `unresolved-attribute` error. We need better union
1071+ # handling in `validate_attribute_assignment` for this.
1072+ # error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to attribute `x` on type `Literal[C1, C2]`"
1073+ C.x = 1
10061074```
10071075
10081076## Inherited class attributes
@@ -1017,6 +1085,8 @@ class B(A): ...
10171085class C (B ): ...
10181086
10191087reveal_type(C.X) # revealed: Unknown | Literal["foo"]
1088+
1089+ C.X = " bar"
10201090```
10211091
10221092### Multiple inheritance
@@ -1040,6 +1110,8 @@ reveal_type(A.__mro__)
10401110
10411111# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
10421112reveal_type(A.X) # revealed: Unknown | Literal[42]
1113+
1114+ A.X = 100
10431115```
10441116
10451117## Intersections of attributes
@@ -1057,9 +1129,13 @@ class B: ...
10571129def _ (a_and_b : Intersection[A, B]):
10581130 reveal_type(a_and_b.x) # revealed: int
10591131
1132+ a_and_b.x = 2
1133+
10601134# Same for class objects
10611135def _ (a_and_b : Intersection[type[A], type[B]]):
10621136 reveal_type(a_and_b.x) # revealed: int
1137+
1138+ a_and_b.x = 2
10631139```
10641140
10651141### Attribute available on both elements
@@ -1069,6 +1145,7 @@ from knot_extensions import Intersection
10691145
10701146class P : ...
10711147class Q : ...
1148+ class R (P , Q ): ...
10721149
10731150class A :
10741151 x: P = P()
@@ -1078,10 +1155,12 @@ class B:
10781155
10791156def _ (a_and_b : Intersection[A, B]):
10801157 reveal_type(a_and_b.x) # revealed: P & Q
1158+ a_and_b.x = R()
10811159
10821160# Same for class objects
10831161def _ (a_and_b : Intersection[type[A], type[B]]):
10841162 reveal_type(a_and_b.x) # revealed: P & Q
1163+ a_and_b.x = R()
10851164```
10861165
10871166### Possible unboundness
@@ -1091,6 +1170,7 @@ from knot_extensions import Intersection
10911170
10921171class P : ...
10931172class Q : ...
1173+ class R (P , Q ): ...
10941174
10951175def _ (flag : bool ):
10961176 class A1 :
@@ -1102,11 +1182,17 @@ def _(flag: bool):
11021182 def inner1 (a_and_b : Intersection[A1, B1]):
11031183 # error: [possibly-unbound-attribute]
11041184 reveal_type(a_and_b.x) # revealed: P
1185+
1186+ # error: [possibly-unbound-attribute]
1187+ a_and_b.x = R()
11051188 # Same for class objects
11061189 def inner1_class (a_and_b : Intersection[type[A1], type[B1]]):
11071190 # error: [possibly-unbound-attribute]
11081191 reveal_type(a_and_b.x) # revealed: P
11091192
1193+ # error: [possibly-unbound-attribute]
1194+ a_and_b.x = R()
1195+
11101196 class A2 :
11111197 if flag:
11121198 x: P = P()
@@ -1116,6 +1202,11 @@ def _(flag: bool):
11161202
11171203 def inner2 (a_and_b : Intersection[A2, B1]):
11181204 reveal_type(a_and_b.x) # revealed: P & Q
1205+
1206+ # TODO : this should not be an error, we need better intersection
1207+ # handling in `validate_attribute_assignment` for this
1208+ # error: [possibly-unbound-attribute]
1209+ a_and_b.x = R()
11191210 # Same for class objects
11201211 def inner2_class (a_and_b : Intersection[type[A2], type[B1]]):
11211212 reveal_type(a_and_b.x) # revealed: P & Q
@@ -1131,21 +1222,33 @@ def _(flag: bool):
11311222 def inner3 (a_and_b : Intersection[A3, B3]):
11321223 # error: [possibly-unbound-attribute]
11331224 reveal_type(a_and_b.x) # revealed: P & Q
1225+
1226+ # error: [possibly-unbound-attribute]
1227+ a_and_b.x = R()
11341228 # Same for class objects
11351229 def inner3_class (a_and_b : Intersection[type[A3], type[B3]]):
11361230 # error: [possibly-unbound-attribute]
11371231 reveal_type(a_and_b.x) # revealed: P & Q
11381232
1233+ # error: [possibly-unbound-attribute]
1234+ a_and_b.x = R()
1235+
11391236 class A4 : ...
11401237 class B4 : ...
11411238
11421239 def inner4 (a_and_b : Intersection[A4, B4]):
11431240 # error: [unresolved-attribute]
11441241 reveal_type(a_and_b.x) # revealed: Unknown
1242+
1243+ # error: [invalid-assignment]
1244+ a_and_b.x = R()
11451245 # Same for class objects
11461246 def inner4_class (a_and_b : Intersection[type[A4], type[B4]]):
11471247 # error: [unresolved-attribute]
11481248 reveal_type(a_and_b.x) # revealed: Unknown
1249+
1250+ # error: [invalid-assignment]
1251+ a_and_b.x = R()
11491252```
11501253
11511254### Intersection of implicit instance attributes
0 commit comments