diff --git a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md index e7171b6dd4..d8619851a2 100644 --- a/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md +++ b/crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.md @@ -838,6 +838,40 @@ class WrappedIntAndExtraData[T](Wrap[int]): reveal_type(WrappedIntAndExtraData[bytes].__init__) ``` +### Non-dataclass inheriting from generic dataclass + +This is a regression test for . + +When a non-dataclass inherits from a generic dataclass, the generic type parameters should still be +properly inferred when calling the inherited `__init__` method. + +```py +from dataclasses import dataclass + +@dataclass +class ParentDataclass[T]: + value: T + +# Non-dataclass inheriting from generic dataclass +class ChildOfParentDataclass[T](ParentDataclass[T]): ... + +def uses_dataclass[T](x: T) -> ChildOfParentDataclass[T]: + return ChildOfParentDataclass(x) + +# TODO: ParentDataclass.__init__ should show generic types, not Unknown +# revealed: (self: ParentDataclass[Unknown], value: Unknown) -> None +reveal_type(ParentDataclass.__init__) + +# revealed: (self: ParentDataclass[T@ChildOfParentDataclass], value: T@ChildOfParentDataclass) -> None +reveal_type(ChildOfParentDataclass.__init__) + +result_int = uses_dataclass(42) +reveal_type(result_int) # revealed: ChildOfParentDataclass[Literal[42]] + +result_str = uses_dataclass("hello") +reveal_type(result_str) # revealed: ChildOfParentDataclass[Literal["hello"]] +``` + ## Descriptor-typed fields ### Same type in `__get__` and `__set__` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 75190a3c3a..4f8ee4c1fc 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -2176,7 +2176,8 @@ impl<'db> ClassLiteral<'db> { }); if member.is_undefined() { - if let Some(synthesized_member) = self.own_synthesized_member(db, specialization, name) + if let Some(synthesized_member) = + self.own_synthesized_member(db, specialization, inherited_generic_context, name) { return Member::definitely_declared(synthesized_member); } @@ -2192,6 +2193,7 @@ impl<'db> ClassLiteral<'db> { self, db: &'db dyn Db, specialization: Option>, + inherited_generic_context: Option>, name: &str, ) -> Option> { let dataclass_params = self.dataclass_params(db); @@ -2320,7 +2322,7 @@ impl<'db> ClassLiteral<'db> { let signature = match name { "__new__" | "__init__" => Signature::new_generic( - self.inherited_generic_context(db), + inherited_generic_context.or_else(|| self.inherited_generic_context(db)), Parameters::new(parameters), return_ty, ), @@ -2702,7 +2704,7 @@ impl<'db> ClassLiteral<'db> { name: &str, policy: MemberLookupPolicy, ) -> PlaceAndQualifiers<'db> { - if let Some(member) = self.own_synthesized_member(db, specialization, name) { + if let Some(member) = self.own_synthesized_member(db, specialization, None, name) { Place::bound(member).into() } else { KnownClass::TypedDictFallback