diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md index 608b4f348e..01a3baab2e 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md @@ -546,7 +546,29 @@ class A: ### `slots` -To do +If a dataclass is defined with `slots=True`, the `__slots__` attribute is generated as a tuple. It +is not present otherwise. + +```py +from dataclasses import dataclass +from typing import Tuple + +@dataclass +class A: + x: int + y: int + +# revealed: Unknown +# error: [unresolved-attribute] +reveal_type(A.__slots__) + +@dataclass(slots=True) +class B: + x: int + y: int + +reveal_type(B.__slots__) # revealed: tuple[Literal["x"], Literal["y"]] +``` ### `weakref_slot` diff --git a/crates/ty_python_semantic/resources/mdtest/instance_layout_conflict.md b/crates/ty_python_semantic/resources/mdtest/instance_layout_conflict.md index f64412f4f3..bb8e013083 100644 --- a/crates/ty_python_semantic/resources/mdtest/instance_layout_conflict.md +++ b/crates/ty_python_semantic/resources/mdtest/instance_layout_conflict.md @@ -64,6 +64,30 @@ class AB( # error: [instance-layout-conflict] ): ... ``` +## Synthesized `__slots__` from dataclasses + +```py +from dataclasses import dataclass + +@dataclass(slots=True) +class F: ... + +@dataclass(slots=True) +class G: ... + +class H(F, G): ... # fine because both classes have empty `__slots__` + +@dataclass(slots=True) +class I: + x: int + +@dataclass(slots=True) +class J: + y: int + +class K(I, J): ... # error: [instance-layout-conflict] +``` + ## Invalid `__slots__` definitions TODO: Emit diagnostics diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md index aa5c3eaee2..ca6727815c 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_disjoint_from.md @@ -165,6 +165,37 @@ static_assert(is_disjoint_from(D, B)) static_assert(not is_disjoint_from(D, A)) ``` +## Dataclasses + +```py +from dataclasses import dataclass +from ty_extensions import is_disjoint_from, static_assert + +@dataclass(slots=True) +class F: ... + +@dataclass(slots=True) +class G: ... + +@dataclass(slots=True) +class I: + x: int + +@dataclass(slots=True) +class J: + y: int + +# A dataclass with empty `__slots__` is not disjoint from another dataclass with `__slots__` +static_assert(not is_disjoint_from(F, G)) +static_assert(not is_disjoint_from(F, I)) +static_assert(not is_disjoint_from(G, I)) +static_assert(not is_disjoint_from(F, J)) +static_assert(not is_disjoint_from(G, J)) + +# But two dataclasses with non-empty `__slots__` are disjoint +static_assert(is_disjoint_from(I, J)) +``` + ## Tuple types ```py diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index ac8b245a63..7b8d1e2ff9 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2282,6 +2282,13 @@ impl<'db> ClassLiteral<'db> { } None } + (CodeGeneratorKind::DataclassLike, "__slots__") => { + has_dataclass_param(DataclassParams::SLOTS).then(|| { + let fields = self.fields(db, specialization, field_policy); + let slots = fields.keys().map(|name| Type::string_literal(db, name)); + Type::heterogeneous_tuple(db, slots) + }) + } (CodeGeneratorKind::TypedDict, "__setitem__") => { let fields = self.fields(db, specialization, field_policy);