Skip to content

Commit f6f3238

Browse files
asukaminato0721meta-codesync[bot]
authored andcommitted
fix Support __class_getitem__ (#1349)
Summary: fix #256 Pull Request resolved: #1349 Reviewed By: rchen152 Differential Revision: D85180587 Pulled By: stroxler fbshipit-source-id: ef412c8025a5b85eed0bec016d147b52780162cb
1 parent 5b07894 commit f6f3238

File tree

3 files changed

+52
-6
lines changed

3 files changed

+52
-6
lines changed

crates/pyrefly_python/src/dunder.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub const AEXIT: Name = Name::new_static("__aexit__");
1313
pub const ALL: Name = Name::new_static("__all__");
1414
pub const BOOL: Name = Name::new_static("__bool__");
1515
pub const CALL: Name = Name::new_static("__call__");
16+
pub const CLASS_GETITEM: Name = Name::new_static("__class_getitem__");
1617
pub const CONTAINS: Name = Name::new_static("__contains__");
1718
pub const DATACLASS_FIELDS: Name = Name::new_static("__dataclass_fields__");
1819
pub const DELATTR: Name = Name::new_static("__delattr__");

pyrefly/lib/alt/expr.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,12 +1855,44 @@ impl<'a, Ans: LookupAnswer> AnswersSolver<'a, Ans> {
18551855
)
18561856
}
18571857
}
1858-
Type::ClassDef(cls) => Type::type_form(self.specialize(
1859-
&cls,
1860-
xs.map(|x| self.expr_untype(x, TypeFormContext::TypeArgument, errors)),
1861-
range,
1862-
errors,
1863-
)),
1858+
Type::ClassDef(cls) => {
1859+
let metadata = self.get_metadata_for_class(&cls);
1860+
let class_getitem_result = if self.get_class_tparams(&cls).is_empty()
1861+
&& !metadata.has_base_any()
1862+
&& !metadata.is_new_type()
1863+
{
1864+
let class_ty = Type::ClassDef(cls.dupe());
1865+
// TODO(stroxler): Add a new API, similar to `type_of_attr_get` but returning a
1866+
// LookupResult or an Optional type, that we could use here to avoid the double lookup.
1867+
if self.has_attr(&class_ty, &dunder::CLASS_GETITEM) {
1868+
let cls_value = self.promote_silently(&cls);
1869+
let call_args = [CallArg::ty(&cls_value, range), CallArg::expr(slice)];
1870+
Some(self.call_method_or_error(
1871+
&class_ty,
1872+
&dunder::CLASS_GETITEM,
1873+
range,
1874+
&call_args,
1875+
&[],
1876+
errors,
1877+
Some(&|| ErrorContext::Index(self.for_display(class_ty.clone()))),
1878+
))
1879+
} else {
1880+
None
1881+
}
1882+
} else {
1883+
None
1884+
};
1885+
if let Some(result) = class_getitem_result {
1886+
result
1887+
} else {
1888+
Type::type_form(self.specialize(
1889+
&cls,
1890+
xs.map(|x| self.expr_untype(x, TypeFormContext::TypeArgument, errors)),
1891+
range,
1892+
errors,
1893+
))
1894+
}
1895+
}
18641896
Type::Type(box Type::SpecialForm(special)) => {
18651897
self.apply_special_form(special, slice, range, errors)
18661898
}

pyrefly/lib/test/simple.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,6 +1737,19 @@ def f(condition: bool):
17371737
"#,
17381738
);
17391739

1740+
testcase!(
1741+
test_class_getitem_magic_dunder,
1742+
r#"
1743+
from typing import assert_type
1744+
1745+
class Foo:
1746+
def __class_getitem__(cls, item: int) -> str:
1747+
return str(item)
1748+
1749+
assert_type(Foo[0], str)
1750+
"#,
1751+
);
1752+
17401753
testcase!(test_panic_docstring, "\"\"\" F\n\u{85}\"\"\"",);
17411754

17421755
testcase!(

0 commit comments

Comments
 (0)