@@ -37,7 +37,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
37
37
# See https://github.com/astral-sh/ruff/issues/15960 for a related discussion.
38
38
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
39
39
40
- reveal_type(c_instance.declared_only) # revealed: bytes
40
+ # TODO : Should be `bytes` with no error, like mypy and pyright?
41
+ # error: [unresolved-attribute]
42
+ reveal_type(c_instance.declared_only) # revealed: Unknown
41
43
42
44
reveal_type(c_instance.declared_and_bound) # revealed: bool
43
45
@@ -64,12 +66,10 @@ C.inferred_from_value = "overwritten on class"
64
66
# This assignment is fine:
65
67
c_instance.declared_and_bound = False
66
68
67
- # TODO : After this assignment to the attribute within this scope, we may eventually want to narrow
68
- # the `bool` type (see above) for this instance variable to `Literal[False]` here. This is unsound
69
- # in general (we don't know what else happened to `c_instance` between the assignment and the use
70
- # here), but mypy and pyright support this. In conclusion, this could be `bool` but should probably
71
- # be `Literal[False]`.
72
- reveal_type(c_instance.declared_and_bound) # revealed: bool
69
+ # Strictly speaking, inferring this as `Literal[False]` rather than `bool` is unsound in general
70
+ # (we don't know what else happened to `c_instance` between the assignment and the use here),
71
+ # but mypy and pyright support this.
72
+ reveal_type(c_instance.declared_and_bound) # revealed: Literal[False]
73
73
```
74
74
75
75
#### Variable declared in class body and possibly bound in ` __init__ `
@@ -149,14 +149,16 @@ class C:
149
149
c_instance = C(True )
150
150
151
151
reveal_type(c_instance.only_declared_in_body) # revealed: str | None
152
- reveal_type(c_instance.only_declared_in_init) # revealed: str | None
152
+ # TODO : should be `str | None` without error
153
+ # error: [unresolved-attribute]
154
+ reveal_type(c_instance.only_declared_in_init) # revealed: Unknown
153
155
reveal_type(c_instance.declared_in_body_and_init) # revealed: str | None
154
156
155
157
reveal_type(c_instance.declared_in_body_defined_in_init) # revealed: str | None
156
158
157
159
# TODO : This should be `str | None`. Fixing this requires an overhaul of the `Symbol` API,
158
160
# which is planned in https://github.com/astral-sh/ruff/issues/14297
159
- reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | str | None
161
+ reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | Literal["a"]
160
162
161
163
reveal_type(c_instance.bound_in_body_and_init) # revealed: Unknown | None | Literal["a"]
162
164
```
@@ -187,7 +189,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
187
189
188
190
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
189
191
190
- reveal_type(c_instance.declared_only) # revealed: bytes
192
+ # TODO : should be `bytes` with no error, like mypy and pyright?
193
+ # error: [unresolved-attribute]
194
+ reveal_type(c_instance.declared_only) # revealed: Unknown
191
195
192
196
reveal_type(c_instance.declared_and_bound) # revealed: bool
193
197
@@ -260,8 +264,8 @@ class C:
260
264
self .w += None
261
265
262
266
# TODO : Mypy and pyright do not support this, but it would be great if we could
263
- # infer `Unknown | str` or at least `Unknown | Weird | str` here .
264
- reveal_type(C().w) # revealed: Unknown | Weird
267
+ # infer `Unknown | str` here (`Weird` is not a possible type for the `w` attribute) .
268
+ reveal_type(C().w) # revealed: Unknown
265
269
```
266
270
267
271
#### Attributes defined in tuple unpackings
@@ -410,14 +414,41 @@ class C:
410
414
[... for self .a in IntIterable()]
411
415
[... for (self .b, self .c) in TupleIterable()]
412
416
[... for self .d in IntIterable() for self .e in IntIterable()]
417
+ [[... for self .f in IntIterable()] for _ in IntIterable()]
418
+ [[... for self .g in IntIterable()] for self in [D()]]
419
+
420
+ class D :
421
+ g: int
413
422
414
423
c_instance = C()
415
424
416
- reveal_type(c_instance.a) # revealed: Unknown | int
417
- reveal_type(c_instance.b) # revealed: Unknown | int
418
- reveal_type(c_instance.c) # revealed: Unknown | str
419
- reveal_type(c_instance.d) # revealed: Unknown | int
420
- reveal_type(c_instance.e) # revealed: Unknown | int
425
+ # TODO : no error, reveal Unknown | int
426
+ # error: [unresolved-attribute]
427
+ reveal_type(c_instance.a) # revealed: Unknown
428
+
429
+ # TODO : no error, reveal Unknown | int
430
+ # error: [unresolved-attribute]
431
+ reveal_type(c_instance.b) # revealed: Unknown
432
+
433
+ # TODO : no error, reveal Unknown | str
434
+ # error: [unresolved-attribute]
435
+ reveal_type(c_instance.c) # revealed: Unknown
436
+
437
+ # TODO : no error, reveal Unknown | int
438
+ # error: [unresolved-attribute]
439
+ reveal_type(c_instance.d) # revealed: Unknown
440
+
441
+ # TODO : no error, reveal Unknown | int
442
+ # error: [unresolved-attribute]
443
+ reveal_type(c_instance.e) # revealed: Unknown
444
+
445
+ # TODO : no error, reveal Unknown | int
446
+ # error: [unresolved-attribute]
447
+ reveal_type(c_instance.f) # revealed: Unknown
448
+
449
+ # This one is correctly not resolved as an attribute:
450
+ # error: [unresolved-attribute]
451
+ reveal_type(c_instance.g) # revealed: Unknown
421
452
```
422
453
423
454
#### Conditionally declared / bound attributes
@@ -721,10 +752,7 @@ reveal_type(C.pure_class_variable) # revealed: Unknown
721
752
# error: [invalid-attribute-access] "Cannot assign to instance attribute `pure_class_variable` from the class object `<class 'C'>`"
722
753
C.pure_class_variable = " overwritten on class"
723
754
724
- # TODO : should be `Unknown | Literal["value set in class method"]` or
725
- # Literal["overwritten on class"]`, once/if we support local narrowing.
726
- # error: [unresolved-attribute]
727
- reveal_type(C.pure_class_variable) # revealed: Unknown
755
+ reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"]
728
756
729
757
c_instance = C()
730
758
reveal_type(c_instance.pure_class_variable) # revealed: Unknown | Literal["value set in class method"]
@@ -762,19 +790,12 @@ reveal_type(c_instance.variable_with_class_default2) # revealed: Unknown | Lite
762
790
c_instance.variable_with_class_default1 = " value set on instance"
763
791
764
792
reveal_type(C.variable_with_class_default1) # revealed: str
765
-
766
- # TODO : Could be Literal["value set on instance"], or still `str` if we choose not to
767
- # narrow the type.
768
- reveal_type(c_instance.variable_with_class_default1) # revealed: str
793
+ reveal_type(c_instance.variable_with_class_default1) # revealed: Literal["value set on instance"]
769
794
770
795
C.variable_with_class_default1 = " overwritten on class"
771
796
772
- # TODO : Could be `Literal["overwritten on class"]`, or still `str` if we choose not to
773
- # narrow the type.
774
- reveal_type(C.variable_with_class_default1) # revealed: str
775
-
776
- # TODO : should still be `Literal["value set on instance"]`, or `str`.
777
- reveal_type(c_instance.variable_with_class_default1) # revealed: str
797
+ reveal_type(C.variable_with_class_default1) # revealed: Literal["overwritten on class"]
798
+ reveal_type(c_instance.variable_with_class_default1) # revealed: Literal["value set on instance"]
778
799
```
779
800
780
801
#### Descriptor attributes as class variables
0 commit comments