Skip to content

Commit c34582f

Browse files
committed
rename implicit_instance_attribute & add lookup_dunder_new
1 parent 93f583f commit c34582f

File tree

4 files changed

+35
-40
lines changed

4 files changed

+35
-40
lines changed

crates/ty_python_semantic/resources/mdtest/attributes.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,8 +745,6 @@ C.class_method()
745745
reveal_type(C.pure_class_variable) # revealed: Unknown | Literal["value set in class method"]
746746

747747
C.pure_class_variable = "overwritten on class"
748-
# TODO: should be no error
749-
# error: [unresolved-attribute] "Attribute `pure_class_variable` can only be accessed on instances, not on the class object `<class 'C'>` itself."
750748
reveal_type(C.pure_class_variable) # revealed: Literal["overwritten on class"]
751749

752750
c_instance = C()

crates/ty_python_semantic/src/types.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,6 +2503,16 @@ impl<'db> Type<'db> {
25032503
}
25042504
}
25052505

2506+
#[salsa::tracked]
2507+
fn lookup_dunder_new(self, db: &'db dyn Db, _unit: ()) -> Option<PlaceAndQualifiers<'db>> {
2508+
self.find_name_in_mro_with_policy(
2509+
db,
2510+
"__new__".into(),
2511+
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK
2512+
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
2513+
)
2514+
}
2515+
25062516
/// Look up an attribute in the MRO of the meta-type of `self`. This returns class-level attributes
25072517
/// when called on an instance-like type, and metaclass attributes when called on a class-like type.
25082518
///
@@ -4662,12 +4672,7 @@ impl<'db> Type<'db> {
46624672
// An alternative might be to not skip `object.__new__` but instead mark it such that it's
46634673
// easy to check if that's the one we found?
46644674
// Note that `__new__` is a static method, so we must inject the `cls` argument.
4665-
let new_method = self_type.find_name_in_mro_with_policy(
4666-
db,
4667-
"__new__",
4668-
MemberLookupPolicy::MRO_NO_OBJECT_FALLBACK
4669-
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
4670-
);
4675+
let new_method = self_type.lookup_dunder_new(db, ());
46714676
let new_call_outcome = new_method.and_then(|new_method| {
46724677
match new_method.place.try_call_dunder_get(db, self_type) {
46734678
Place::Type(new_method, boundness) => {

crates/ty_python_semantic/src/types/class.rs

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -641,10 +641,9 @@ impl<'db> From<ClassType<'db>> for Type<'db> {
641641
}
642642
}
643643

644-
// Helper class for constraining the method we look up for attribute in `ClassLiteral::implicit_instance_attribute`
645-
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
644+
// Helper class for constraining the method we look up for attribute in `ClassLiteral::implicit_attribute`
645+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
646646
pub(super) enum MethodDecorator {
647-
#[default]
648647
None,
649648
ClassMethod,
650649
StaticMethod,
@@ -1309,16 +1308,14 @@ impl<'db> ClassLiteral<'db> {
13091308
{
13101309
return Place::bound(synthesized_member).into();
13111310
}
1312-
if !matches!(name, "__new__" | "__init__") {
1313-
// Symbol is not found in class scope, but still might be defined in class methods.
1314-
return Self::implicit_instance_attribute(
1315-
db,
1316-
body_scope,
1317-
name,
1318-
MethodDecorator::ClassMethod,
1319-
)
1320-
.into();
1321-
}
1311+
// Symbol is not found in class scope, but still might be defined in class methods.
1312+
return Self::implicit_attribute(
1313+
db,
1314+
body_scope,
1315+
name.into(),
1316+
MethodDecorator::ClassMethod,
1317+
)
1318+
.into();
13221319
}
13231320
symbol
13241321
}
@@ -1635,15 +1632,16 @@ impl<'db> ClassLiteral<'db> {
16351632
}
16361633
}
16371634

1638-
/// Tries to find declarations/bindings of an instance attribute named `name` that are only
1635+
/// Tries to find declarations/bindings of an attribute named `name` that are only
16391636
/// "implicitly" defined in a method of the class that corresponds to `class_body_scope`
16401637
/// and has the exact same method decorator.
1641-
fn implicit_instance_attribute(
1638+
fn implicit_attribute(
16421639
db: &'db dyn Db,
16431640
class_body_scope: ScopeId<'db>,
1644-
name: &str,
1641+
name: Name,
16451642
target_method_decorator: MethodDecorator,
16461643
) -> Place<'db> {
1644+
let name = name.as_str();
16471645
// If we do not see any declarations of an attribute, neither in the class body nor in
16481646
// any method, we build a union of `Unknown` with the inferred types of all bindings of
16491647
// that attribute. We include `Unknown` in that union to account for the fact that the
@@ -1947,11 +1945,11 @@ impl<'db> ClassLiteral<'db> {
19471945
if has_binding {
19481946
// The attribute is declared and bound in the class body.
19491947

1950-
if let Some(implicit_ty) = Self::implicit_instance_attribute(
1948+
if let Some(implicit_ty) = Self::implicit_attribute(
19511949
db,
19521950
body_scope,
1953-
name,
1954-
MethodDecorator::default(),
1951+
name.into(),
1952+
MethodDecorator::None,
19551953
)
19561954
.ignore_possibly_unbound()
19571955
{
@@ -1986,11 +1984,11 @@ impl<'db> ClassLiteral<'db> {
19861984
if declaredness == Boundness::Bound {
19871985
declared.with_qualifiers(qualifiers)
19881986
} else {
1989-
if let Some(implicit_ty) = Self::implicit_instance_attribute(
1987+
if let Some(implicit_ty) = Self::implicit_attribute(
19901988
db,
19911989
body_scope,
1992-
name,
1993-
MethodDecorator::default(),
1990+
name.into(),
1991+
MethodDecorator::None,
19941992
)
19951993
.ignore_possibly_unbound()
19961994
{
@@ -2013,13 +2011,8 @@ impl<'db> ClassLiteral<'db> {
20132011
// The attribute is not *declared* in the class body. It could still be declared/bound
20142012
// in a method.
20152013

2016-
Self::implicit_instance_attribute(
2017-
db,
2018-
body_scope,
2019-
name,
2020-
MethodDecorator::default(),
2021-
)
2022-
.into()
2014+
Self::implicit_attribute(db, body_scope, name.into(), MethodDecorator::None)
2015+
.into()
20232016
}
20242017
Err((declared, _conflicting_declarations)) => {
20252018
// There are conflicting declarations for this attribute in the class body.
@@ -2030,8 +2023,7 @@ impl<'db> ClassLiteral<'db> {
20302023
// This attribute is neither declared nor bound in the class body.
20312024
// It could still be implicitly defined in a method.
20322025

2033-
Self::implicit_instance_attribute(db, body_scope, name, MethodDecorator::default())
2034-
.into()
2026+
Self::implicit_attribute(db, body_scope, name.into(), MethodDecorator::None).into()
20352027
}
20362028
}
20372029

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10940,7 +10940,7 @@ mod tests {
1094010940
r#"
1094110941
from mod import C
1094210942
C.method()
10943-
x = C.class_attr
10943+
x = C().class_attr
1094410944
"#,
1094510945
)?;
1094610946

0 commit comments

Comments
 (0)