[ty] Fix generic inference for non-dataclass inheriting from generic dataclass (#21159)

## Summary

Fixes https://github.com/astral-sh/ty/issues/1427

This PR fixes a regression introduced in alpha.24 where non-dataclass
children of generic dataclasses lost generic type parameter information
during `__init__` synthesis.

The issue occurred because when looking up inherited members in the MRO,
the child class's `inherited_generic_context` was correctly passed down,
but `own_synthesized_member()` (which synthesizes dataclass `__init__`
methods) didn't accept this parameter. It only used
`self.inherited_generic_context(db)`, which returned the parent's
context instead of the child's.

The fix threads the child's generic context through to the synthesis
logic, allowing proper generic type inference for inherited dataclass
constructors.

## Test Plan

- Added regression test for non-dataclass inheriting from generic
dataclass
- Verified the exact repro case from the issue now works
- All 277 mdtest tests passing
- Clippy clean
- Manually verified with Python runtime, mypy, and pyright - all accept
this code pattern

## Verification

Tested against multiple type checkers:
-  Python runtime: Code works correctly
-  mypy: No issues found
-  pyright: 0 errors, 0 warnings
-  ty alpha.23: Worked (before regression)
-  ty alpha.24: Regression
-  ty with this fix: Works correctly

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
Mahmoud Saada
2025-10-31 08:55:17 -04:00
committed by GitHub
parent 3585c96ea5
commit 735ec0c1f9
2 changed files with 39 additions and 3 deletions

View File

@@ -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<Specialization<'db>>,
inherited_generic_context: Option<GenericContext<'db>>,
name: &str,
) -> Option<Type<'db>> {
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