Skip to content

Commit c910aa5

Browse files
committed
Don't emit diagnostic for assigning to self
1 parent 75d38d0 commit c910aa5

File tree

4 files changed

+54
-21
lines changed

4 files changed

+54
-21
lines changed

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,7 @@ class C:
695695
pure_class_variable2: ClassVar = 1
696696

697697
def method(self):
698-
# TODO: this should be an error
698+
# error: [invalid-attribute-access] "Cannot assign to ClassVar `pure_class_variable1` from an instance of type `Self`"
699699
self.pure_class_variable1 = "value set through instance"
700700

701701
reveal_type(C.pure_class_variable1) # revealed: str
@@ -1634,6 +1634,7 @@ def external_getattribute(name) -> int:
16341634

16351635
class ThisFails:
16361636
def __init__(self):
1637+
# error: [invalid-assignment] "Implicit shadowing of function `__getattribute__`"
16371638
self.__getattribute__ = external_getattribute
16381639

16391640
# error: [unresolved-attribute]

crates/ty_python_semantic/src/types.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5975,6 +5975,17 @@ impl<'db> TypeVarInstance<'db> {
59755975
self.kind(db),
59765976
)
59775977
}
5978+
5979+
pub(crate) fn is_self(self, db: &'db dyn Db) -> bool {
5980+
self.bound_or_constraints(db)
5981+
.is_some_and(|bound_or_constraints| {
5982+
matches!(
5983+
bound_or_constraints,
5984+
TypeVarBoundOrConstraints::UpperBound(Type::NominalInstance(_))
5985+
)
5986+
})
5987+
&& self.name(db) == "Self"
5988+
}
59785989
}
59795990

59805991
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update)]
@@ -8162,6 +8173,7 @@ impl<'db> BoundSuperType<'db> {
81628173
let Some(owner_class) = owner.into_class() else {
81638174
return Some(owner);
81648175
};
8176+
81658177
if owner_class.is_subclass_of(db, pivot_class) {
81668178
Some(owner)
81678179
} else {

crates/ty_python_semantic/src/types/function.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,11 @@ impl<'db> FunctionType<'db> {
602602
self.literal(db).has_known_decorator(db, decorator)
603603
}
604604

605+
/// Returns if this function is a class method.
606+
pub(crate) fn is_class_method(self, db: &dyn Db) -> bool {
607+
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD) || self.name(db) == "__new__"
608+
}
609+
605610
/// Returns the [`Definition`] of the implementation or first overload of this function.
606611
///
607612
/// ## Warning

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,6 +2308,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
23082308
let module = &parsed_module(self.db(), self.scope().file(self.db())).load(self.db());
23092309
let method = current_scope.node().as_function(module)?;
23102310

2311+
let parent_scope_id = current_scope.parent()?;
2312+
let parent_scope = self.index.scope(parent_scope_id);
2313+
parent_scope.node().as_class(module)?;
2314+
23112315
let definition = self.index.expect_single_definition(method);
23122316
let DefinitionKind::Function(func_def) = definition.kind(self.db()) else {
23132317
return None;
@@ -2326,18 +2330,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
23262330
return None;
23272331
}
23282332

2329-
if func_type.has_known_decorator(self.db(), FunctionDecorators::CLASSMETHOD) {
2330-
// TODO: cls
2333+
if func_type.is_class_method(self.db()) {
2334+
// TODO: set the type for `cls` argument
23312335
return None;
23322336
} else if func_type.has_known_decorator(self.db(), FunctionDecorators::STATICMETHOD) {
23332337
return None;
2334-
} else {
2335-
return Some(
2336-
Type::SpecialForm(SpecialFormType::TypingSelf)
2337-
.in_type_expression(self.db(), self.scope())
2338-
.unwrap(),
2339-
);
2340-
};
2338+
}
2339+
Some(
2340+
Type::SpecialForm(SpecialFormType::TypingSelf)
2341+
.in_type_expression(self.db(), self.scope())
2342+
.unwrap(),
2343+
)
23412344
}
23422345

23432346
/// Set initial declared/inferred types for a `*args` variadic positional parameter.
@@ -3246,6 +3249,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
32463249
dataclass_params.is_some_and(|params| params.contains(DataclassParams::FROZEN))
32473250
};
32483251

3252+
// TODO: A hacky way to allow assigning instance attributes to self.
3253+
// Without this flag we would emit diagnostics for `self.x = 1` if `x` is unbound.
3254+
// The correct solution is to review how we emit diagnostics in store context.
3255+
let allow_instance_attribute_assignments_to_self = || match object_ty {
3256+
Type::TypeVar(tv) => tv.is_self(db),
3257+
_ => false,
3258+
};
3259+
32493260
match object_ty.class_member(db, attribute.into()) {
32503261
meta_attr @ PlaceAndQualifiers { .. } if meta_attr.is_class_var() => {
32513262
if emit_diagnostics {
@@ -3351,12 +3362,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
33513362
object_ty.instance_member(db, attribute).place
33523363
{
33533364
if instance_attr_boundness == Boundness::PossiblyUnbound {
3354-
report_possibly_unbound_attribute(
3355-
&self.context,
3356-
target,
3357-
attribute,
3358-
object_ty,
3359-
);
3365+
if !allow_instance_attribute_assignments_to_self() {
3366+
report_possibly_unbound_attribute(
3367+
&self.context,
3368+
target,
3369+
attribute,
3370+
object_ty,
3371+
);
3372+
}
33603373
}
33613374

33623375
if is_read_only() {
@@ -3411,11 +3424,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
34113424
if let Some(builder) =
34123425
self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target)
34133426
{
3414-
builder.into_diagnostic(format_args!(
3415-
"Unresolved attribute `{}` on type `{}`.",
3416-
attribute,
3417-
object_ty.display(db)
3418-
));
3427+
if !allow_instance_attribute_assignments_to_self() {
3428+
builder.into_diagnostic(format_args!(
3429+
"Unresolved attribute `{}` on type `{}`.",
3430+
attribute,
3431+
object_ty.display(db)
3432+
));
3433+
}
34193434
}
34203435
}
34213436

0 commit comments

Comments
 (0)