From e8ea40012afda872ef9bdf496a8b888ba80b533b Mon Sep 17 00:00:00 2001 From: Matthew Mckee Date: Tue, 3 Jun 2025 17:59:43 +0100 Subject: [PATCH] [ty] Add generic inference for dataclasses (#18443) ## Summary An issue seen here https://github.com/astral-sh/ty/issues/500 The `__init__` method of dataclasses had no inherited generic context, so we could not infer the type of an instance from a constructor call with generics ## Test Plan Add tests to classes.md` in generics folder --- .../resources/mdtest/generics/legacy/classes.md | 15 +++++++++++++++ .../resources/mdtest/generics/pep695/classes.md | 12 ++++++++++++ .../resources/mdtest/named_tuple.md | 4 +--- crates/ty_python_semantic/src/types/class.rs | 3 ++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index ad1fe65958..5e8858bf7b 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -379,6 +379,21 @@ C[None](b"bytes") # error: [no-matching-overload] C[None](12) ``` +### Synthesized methods with dataclasses + +```py +from dataclasses import dataclass +from typing import Generic, TypeVar + +T = TypeVar("T") + +@dataclass +class A(Generic[T]): + x: T + +reveal_type(A(x=1)) # revealed: A[int] +``` + ## Generic subclass When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 1d3fee229b..9726dbae54 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -354,6 +354,18 @@ C[None](b"bytes") # error: [no-matching-overload] C[None](12) ``` +### Synthesized methods with dataclasses + +```py +from dataclasses import dataclass + +@dataclass +class A[T]: + x: T + +reveal_type(A(x=1)) # revealed: A[int] +``` + ## Generic subclass When a generic subclass fills its superclass's type parameter with one of its own, the actual types diff --git a/crates/ty_python_semantic/resources/mdtest/named_tuple.md b/crates/ty_python_semantic/resources/mdtest/named_tuple.md index 8c5a79d62f..dfc81a73f0 100644 --- a/crates/ty_python_semantic/resources/mdtest/named_tuple.md +++ b/crates/ty_python_semantic/resources/mdtest/named_tuple.md @@ -134,9 +134,7 @@ class Property[T](NamedTuple): name: str value: T -# TODO: this should be supported (no error, revealed type of `Property[float]`) -# error: [invalid-argument-type] -reveal_type(Property("height", 3.4)) # revealed: Property[Unknown] +reveal_type(Property("height", 3.4)) # revealed: Property[float] ``` ## Attributes on `NamedTuple` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index bcf6c6e1b7..ab99ae27a6 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -1372,7 +1372,8 @@ impl<'db> ClassLiteral<'db> { parameters.push(parameter); } - let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db))); + let mut signature = Signature::new(Parameters::new(parameters), Some(Type::none(db))); + signature.inherited_generic_context = self.generic_context(db); Some(CallableType::function_like(db, signature)) };