Skip to content

Commit a45d7f0

Browse files
grievejiameta-codesync[bot]
authored andcommitted
Fix metaclass lookup for class object __class__
Summary: Pyrefly could infer `C.__class__` as `type[type[C]]` when `C` was represented as a `ClassDef`, but attribute-base conversion had no case for `type[ClassDef]`. That caused ordinary metaclass method lookups such as `C.__class__.__setattr__` to report an internal `attribute base undefined` error. Treat `type[ClassDef(C)]` as `C`'s metaclass viewed as a class object, preserving custom metaclass method signatures while leaving the broader `type[ClassType]` / generic alias behavior as the existing known bug. Fixes #4093 Reviewed By: kinto0 Differential Revision: D111341385 fbshipit-source-id: b610104a8e39887b7cda66f3c608832d00b2cd72
1 parent 654b1f8 commit a45d7f0

2 files changed

Lines changed: 35 additions & 3 deletions

File tree

pyrefly/lib/alt/attr.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,17 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
23562356
acc.push(class_base)
23572357
}
23582358
}
2359+
Type::Type(f) if matches!(&*f, Type::ClassDef(_)) => {
2360+
let Type::ClassDef(class) = *f else {
2361+
unreachable!("guarded by matches! above")
2362+
};
2363+
// type[ClassDef(C)] is C.__class__: C's metaclass, viewed as a class object.
2364+
let metaclass = self
2365+
.get_metadata_for_class(&class)
2366+
.metaclass(self.stdlib)
2367+
.clone();
2368+
acc.push(AttributeBase1::ClassObject(ClassBase::ClassType(metaclass)))
2369+
}
23592370
Type::QuantifiedValue(q) => acc.push(AttributeBase1::QuantifiedValue(*q)),
23602371
Type::Type(f) if matches!(&*f, Type::Quantified(_)) => {
23612372
let Type::Quantified(quantified) = *f else {

pyrefly/lib/test/attributes.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1730,7 +1730,7 @@ def test_union(x: A | B):
17301730
);
17311731

17321732
testcase!(
1733-
bug = "type[ClassDef(..)] and type[ClassType(..)] should be type (or the direct metaclass?)",
1733+
bug = "type[ClassType(..)] should be type (or the direct metaclass?)",
17341734
test_attribute_access_on_type_class,
17351735
r#"
17361736
# handy hack to get a type[X] for any X
@@ -1744,11 +1744,32 @@ class D[T]:
17441744
@classmethod
17451745
def m(cls, x: T): ...
17461746
1747-
ty(C).m(0) # E: Expr::attr_infer_for_type attribute base undefined
1747+
ty(C).m(0) # E: Class `type` has no class attribute `m`
17481748
ty(D[int]).m(0) # E: Expr::attr_infer_for_type attribute base undefined
17491749
"#,
17501750
);
17511751

1752+
testcase!(
1753+
test_attribute_access_on_classdef_metaclass,
1754+
r#"
1755+
class Meta(type):
1756+
def __setattr__(cls, name: str, value: int) -> None: ...
1757+
def __getattribute__(cls, name: str) -> object: ...
1758+
1759+
class C(metaclass=Meta):
1760+
pass
1761+
1762+
C.__class__.__setattr__(C, "x", 0)
1763+
C.__class__.__setattr__(C, "x", "bad") # E: Argument `Literal['bad']` is not assignable to parameter `value` with type `int`
1764+
C.__class__.__getattribute__(C, "x")
1765+
1766+
class DefaultMeta:
1767+
pass
1768+
1769+
DefaultMeta.__class__.mro(DefaultMeta)
1770+
"#,
1771+
);
1772+
17521773
testcase!(
17531774
bug = "type[TypedDict()] should be type",
17541775
test_attribute_access_on_type_typeddict,
@@ -1762,7 +1783,7 @@ class TD(TypedDict):
17621783
x: int
17631784
17641785
ty(TD(x = 0))(x = 0)
1765-
ty(TD).mro() # E: Expr::attr_infer_for_type attribute base undefined
1786+
ty(TD).mro() # E: Missing argument `self` in function `type.mro`
17661787
"#,
17671788
);
17681789

0 commit comments

Comments
 (0)