[ty] Fix ClassLiteral.into_callable for dataclasses (#19192)

## Summary

Change `ClassLiteral.into_callable` to also look for `__init__` functions
of type `Type::Callable` (such as synthesized `__init__` functions of
dataclasses).

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

## Test Plan

Add subtype test

---------

Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
Matthew Mckee 2025-07-09 09:04:55 +01:00 committed by GitHub
parent 68106dd631
commit f32f7a3b48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 19 deletions

View file

@ -1774,6 +1774,25 @@ static_assert(is_subtype_of(type[B], Callable[[str], B]))
static_assert(not is_subtype_of(type[B], Callable[[int], B]))
```
### Dataclasses
Dataclasses synthesize a `__init__` method.
```py
from typing import Callable
from ty_extensions import TypeOf, static_assert, is_subtype_of
from dataclasses import dataclass
@dataclass
class A:
x: "A" | None
static_assert(is_subtype_of(type[A], Callable[[A], A]))
static_assert(is_subtype_of(type[A], Callable[[None], A]))
static_assert(is_subtype_of(type[A], Callable[[A | None], A]))
static_assert(not is_subtype_of(type[A], Callable[[int], A]))
```
### Bound methods
```py

View file

@ -661,27 +661,33 @@ impl<'db> ClassType<'db> {
// same parameters as the `__init__` method after it is bound, and with the return type of
// the concrete type of `Self`.
let synthesized_dunder_init_callable =
if let Place::Type(Type::FunctionLiteral(dunder_init_function), _) =
dunder_init_function_symbol
{
let synthesized_signature = |signature: Signature<'db>| {
Signature::new(signature.parameters().clone(), Some(correct_return_type))
.bind_self()
if let Place::Type(ty, _) = dunder_init_function_symbol {
let signature = match ty {
Type::FunctionLiteral(dunder_init_function) => {
Some(dunder_init_function.signature(db))
}
Type::Callable(callable) => Some(callable.signatures(db)),
_ => None,
};
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
dunder_init_function
.signature(db)
.overloads
.iter()
.cloned()
.map(synthesized_signature),
);
Some(Type::Callable(CallableType::new(
db,
synthesized_dunder_init_signature,
true,
)))
if let Some(signature) = signature {
let synthesized_signature = |signature: &Signature<'db>| {
Signature::new(signature.parameters().clone(), Some(correct_return_type))
.bind_self()
};
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
signature.overloads.iter().map(synthesized_signature),
);
Some(Type::Callable(CallableType::new(
db,
synthesized_dunder_init_signature,
true,
)))
} else {
None
}
} else {
None
};