diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index da7003cdc0..869c6e53b1 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -845,6 +845,7 @@ class Person(NamedTuple): reveal_type(Person._field_defaults) # revealed: dict[str, Any] reveal_type(Person._fields) # revealed: tuple[Literal["name"], Literal["age"]] +reveal_type(Person.__slots__) # revealed: tuple[()] reveal_type(Person._make) # revealed: bound method ._make(iterable: Iterable[Any]) -> Person reveal_type(Person._asdict) # revealed: def _asdict(self) -> dict[str, Any] reveal_type(Person._replace) # revealed: (self: Self, *, name: str = ..., age: int | None = ...) -> Self @@ -887,6 +888,8 @@ Person = namedtuple("Person", ["id", "name", "age"], defaults=[None]) alice = Person(1, "Alice", 42) bob = Person(2, "Bob") + +reveal_type(Person.__slots__) # revealed: tuple[()] ``` ## `collections.namedtuple` with tuple variable field names diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index c393534d9f..a2eb0fb560 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3244,7 +3244,10 @@ impl<'db> StaticClassLiteral<'db> { ) }) } - (CodeGeneratorKind::NamedTuple, "__new__" | "_replace" | "__replace__" | "_fields") => { + ( + CodeGeneratorKind::NamedTuple, + "__new__" | "_replace" | "__replace__" | "_fields" | "__slots__", + ) => { let fields = self.fields(db, specialization, field_policy); let fields_iter = fields.iter().map(|(name, field)| { let default_ty = match &field.kind { @@ -5212,6 +5215,10 @@ fn synthesize_namedtuple_class_member<'db>( fields.map(|(field_name, _, _)| Type::string_literal(db, &field_name)); Some(Type::heterogeneous_tuple(db, field_types)) } + "__slots__" => { + // __slots__: tuple[()] - always empty for namedtuples + Some(Type::empty_tuple(db)) + } "_replace" | "__replace__" => { if name == "__replace__" && Program::get(db).python_version(db) < PythonVersion::PY313 { return None; @@ -5536,7 +5543,10 @@ impl<'db> DynamicNamedTupleLiteral<'db> { // For fallback members from NamedTupleFallback, apply type mapping to handle // `Self` types. The explicitly synthesized members (__new__, _fields, _replace, // __replace__) don't need this mapping. - if matches!(name, "__new__" | "_fields" | "_replace" | "__replace__") { + if matches!( + name, + "__new__" | "_fields" | "_replace" | "__replace__" | "__slots__" + ) { result } else { result.map(|ty| {