Skip to content

Commit 2927d1f

Browse files
committed
[ty] Fix validation of writes to attributes not declared on the class body
1 parent 282b19e commit 2927d1f

2 files changed

Lines changed: 31 additions & 14 deletions

File tree

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,23 @@ class C:
143143
self.bound_in_body_declared_in_init: str | None
144144

145145
if flag:
146-
# error: [invalid-assignment] "Object of type `Literal["a"]` is not assignable to attribute `bound_in_body_and_init` of type `int`"
147146
self.bound_in_body_and_init = "a"
148147

149148
c_instance = C(True)
150149

151150
reveal_type(c_instance.only_declared_in_body) # revealed: str | None
152151
reveal_type(c_instance.only_declared_in_init) # revealed: str | None
153152
reveal_type(c_instance.declared_in_body_and_init) # revealed: str | None
154-
155153
reveal_type(c_instance.declared_in_body_defined_in_init) # revealed: str | None
156-
157154
reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: str | None
158-
159155
reveal_type(c_instance.bound_in_body_and_init) # revealed: int | str
156+
157+
c_instance.only_declared_in_body = b"invalid" # error: [invalid-assignment]
158+
c_instance.only_declared_in_init = b"invalid" # error: [invalid-assignment]
159+
c_instance.declared_in_body_and_init = b"invalid" # error: [invalid-assignment]
160+
c_instance.declared_in_body_defined_in_init = b"invalid" # error: [invalid-assignment]
161+
c_instance.bound_in_body_declared_in_init = b"invalid" # error: [invalid-assignment]
162+
c_instance.bound_in_body_and_init = b"invalid" # error: [invalid-assignment]
160163
```
161164

162165
#### Variable defined in non-`__init__` method
@@ -827,7 +830,7 @@ reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"]
827830
c_instance = C()
828831
reveal_type(c_instance.pure_class_variable) # revealed: str
829832

830-
# TODO: should raise an error.
833+
# error: [possibly-missing-attribute]
831834
c_instance.pure_class_variable = "value set on instance"
832835
```
833836

@@ -1355,6 +1358,7 @@ def _(flag1: bool, flag2: bool):
13551358
reveal_type(C().x) # revealed: int | str
13561359

13571360
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`"
1361+
# error: [possibly-missing-attribute]
13581362
C().x = 100
13591363
```
13601364

@@ -1390,7 +1394,8 @@ def _(flag: bool, flag1: bool, flag2: bool):
13901394
reveal_type(C().x) # revealed: int | str | bytes
13911395

13921396
# error: [possibly-missing-attribute]
1393-
# error: [invalid-assignment]
1397+
# error: [possibly-missing-attribute]
1398+
# error: [possibly-missing-attribute]
13941399
C().x = 100
13951400
```
13961401

@@ -1439,6 +1444,7 @@ def _(flag: bool):
14391444
Bar.x = 3
14401445

14411446
reveal_type(Bar().x) # revealed: int
1447+
# error: [possibly-missing-attribute]
14421448
Bar().x = 3
14431449
```
14441450

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,7 +2348,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
23482348
PlaceAndQualifiers {
23492349
place:
23502350
Place::Defined(DefinedPlace {
2351-
ty: meta_attr_ty, ..
2351+
ty: meta_attr_ty,
2352+
origin,
2353+
..
23522354
}),
23532355
qualifiers,
23542356
} => {
@@ -2406,10 +2408,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
24062408
dunder_set_result.is_ok()
24072409
} else {
24082410
let write_ty = effective_write_type(meta_attr_ty);
2409-
let value_ty =
2410-
infer_value_ty.infer_silent(self, TypeContext::new(Some(write_ty)));
2411+
if origin.is_declared() {
2412+
let value_ty = infer_value_ty
2413+
.infer_silent(self, TypeContext::new(Some(write_ty)));
24112414

2412-
ensure_assignable_to(self, value_ty, write_ty)
2415+
ensure_assignable_to(self, value_ty, write_ty)
2416+
} else {
2417+
true
2418+
}
24132419
};
24142420

24152421
let assignable_to_instance_attribute =
@@ -2987,10 +2993,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
29872993
let meta_attr = object_ty.class_member(db, attribute.into());
29882994
let needs_fallback = matches!(
29892995
meta_attr.place,
2990-
Place::Defined(DefinedPlace {
2991-
definedness: Definedness::PossiblyUndefined,
2992-
..
2993-
}) | Place::Undefined
2996+
Place::Defined(
2997+
DefinedPlace {
2998+
definedness: Definedness::PossiblyUndefined,
2999+
..
3000+
} | DefinedPlace {
3001+
origin: TypeOrigin::Inferred,
3002+
..
3003+
}
3004+
) | Place::Undefined
29943005
);
29953006
let fallback_attr = if needs_fallback {
29963007
Some(match object_ty {

0 commit comments

Comments
 (0)