diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md index ba5151fa61ff28..fa6f76de753fc1 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md @@ -1462,3 +1462,57 @@ def test_c(): c = C(1) c.__lt__ = Mock() ``` + +## Imperatively calling `dataclasses.dataclass` + +While we do not currently recognize the special behaviour of `dataclasses.dataclass` if it is called +imperatively, we recognize that it can be called imperatively and do not emit any false-positive +diagnostics on such calls: + +```py +from dataclasses import dataclass +from typing_extensions import TypeVar, dataclass_transform + +U = TypeVar("U") + +@dataclass_transform(kw_only_default=True) +def sequence(cls: type[U]) -> type[U]: + d = dataclass( + repr=False, + eq=False, + match_args=False, + kw_only=True, + )(cls) + reveal_type(d) # revealed: type[U@sequence] & Any + return d + +@dataclass_transform(kw_only_default=True) +def sequence2(cls: type) -> type: + d = dataclass( + repr=False, + eq=False, + match_args=False, + kw_only=True, + )(cls) + reveal_type(d) # revealed: type & Any + return d + +@dataclass_transform(kw_only_default=True) +def sequence3(cls: type[U]) -> type[U]: + # TODO: should reveal `type[U@sequence3]` + return reveal_type(dataclass(cls)) # revealed: Unknown + +@dataclass_transform(kw_only_default=True) +def sequence4(cls: type) -> type: + # TODO: should reveal `type` + return reveal_type(dataclass(cls)) # revealed: Unknown + +class Foo: ... + +ordered_foo = dataclass(order=True)(Foo) +reveal_type(ordered_foo) # revealed: type[Foo] & Any +# TODO: should be `Foo & Any` +reveal_type(ordered_foo()) # revealed: @Todo(Type::Intersection.call) +# TODO: should be `Any` +reveal_type(ordered_foo() < ordered_foo()) # revealed: @Todo(Type::Intersection.call) +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 92aa8ae4e87153..427fd7d113ee37 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6195,12 +6195,26 @@ impl<'db> Type<'db> { ), Type::Intersection(_) => { - Binding::single(self, Signature::todo("Type::Intersection.call()")).into() + Binding::single(self, Signature::todo("Type::Intersection.call")).into() + } + + Type::DataclassDecorator(_) => { + let typevar = BoundTypeVarInstance::synthetic(db, "T", TypeVarVariance::Invariant); + let typevar_meta = SubclassOfType::from(db, typevar); + let context = GenericContext::from_typevar_instances(db, [typevar]); + let parameters = [Parameter::positional_only(Some(Name::new_static("cls"))) + .with_annotated_type(typevar_meta)]; + // Intersect with `Any` for the return type to reflect the fact that the `dataclass()` + // decorator adds methods to the class + let returns = IntersectionType::from_elements(db, [typevar_meta, Type::any()]); + let signature = Signature::new_generic( + Some(context), + Parameters::new(db, parameters), + Some(returns), + ); + Binding::single(self, signature).into() } - // TODO: this is actually callable - Type::DataclassDecorator(_) => CallableBinding::not_callable(self).into(), - // TODO: some `SpecialForm`s are callable (e.g. TypedDicts) Type::SpecialForm(_) => CallableBinding::not_callable(self).into(),