Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions crates/ty_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,23 @@ class C:
self.bound_in_body_declared_in_init: str | None

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

c_instance = C(True)

reveal_type(c_instance.only_declared_in_body) # revealed: str | None
reveal_type(c_instance.only_declared_in_init) # revealed: str | None
reveal_type(c_instance.declared_in_body_and_init) # revealed: str | None

reveal_type(c_instance.declared_in_body_defined_in_init) # revealed: str | None

reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: str | None

reveal_type(c_instance.bound_in_body_and_init) # revealed: int | str

c_instance.only_declared_in_body = b"invalid" # error: [invalid-assignment]
c_instance.only_declared_in_init = b"invalid" # error: [invalid-assignment]
c_instance.declared_in_body_and_init = b"invalid" # error: [invalid-assignment]
c_instance.declared_in_body_defined_in_init = b"invalid" # error: [invalid-assignment]
c_instance.bound_in_body_declared_in_init = b"invalid" # error: [invalid-assignment]
c_instance.bound_in_body_and_init = b"invalid" # error: [invalid-assignment]
```

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

# TODO: should raise an error.
# error: [possibly-missing-attribute]
c_instance.pure_class_variable = "value set on instance"
```

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

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

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

# error: [possibly-missing-attribute]
# error: [invalid-assignment]
# error: [possibly-missing-attribute]
# error: [possibly-missing-attribute]
C().x = 100
```

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

reveal_type(Bar().x) # revealed: int
# error: [possibly-missing-attribute]
Bar().x = 3
```

Expand Down
27 changes: 19 additions & 8 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2348,7 +2348,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
PlaceAndQualifiers {
place:
Place::Defined(DefinedPlace {
ty: meta_attr_ty, ..
ty: meta_attr_ty,
origin,
..
}),
qualifiers,
} => {
Expand Down Expand Up @@ -2406,10 +2408,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
dunder_set_result.is_ok()
} else {
let write_ty = effective_write_type(meta_attr_ty);
let value_ty =
infer_value_ty.infer_silent(self, TypeContext::new(Some(write_ty)));
if origin.is_declared() {
let value_ty = infer_value_ty
.infer_silent(self, TypeContext::new(Some(write_ty)));

ensure_assignable_to(self, value_ty, write_ty)
ensure_assignable_to(self, value_ty, write_ty)
} else {
true
}
};

let assignable_to_instance_attribute =
Expand Down Expand Up @@ -2987,10 +2993,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let meta_attr = object_ty.class_member(db, attribute.into());
let needs_fallback = matches!(
meta_attr.place,
Place::Defined(DefinedPlace {
definedness: Definedness::PossiblyUndefined,
..
}) | Place::Undefined
Place::Defined(
DefinedPlace {
definedness: Definedness::PossiblyUndefined,
..
} | DefinedPlace {
origin: TypeOrigin::Inferred,
..
}
) | Place::Undefined
);
let fallback_attr = if needs_fallback {
Some(match object_ty {
Expand Down
Loading