Skip to content

Commit f32f7a3

Browse files
[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 astral-sh/ty#760 ## Test Plan Add subtype test --------- Co-authored-by: David Peter <[email protected]>
1 parent 68106dd commit f32f7a3

File tree

2 files changed

+44
-19
lines changed

2 files changed

+44
-19
lines changed

crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,25 @@ static_assert(is_subtype_of(type[B], Callable[[str], B]))
17741774
static_assert(not is_subtype_of(type[B], Callable[[int], B]))
17751775
```
17761776

1777+
### Dataclasses
1778+
1779+
Dataclasses synthesize a `__init__` method.
1780+
1781+
```py
1782+
from typing import Callable
1783+
from ty_extensions import TypeOf, static_assert, is_subtype_of
1784+
from dataclasses import dataclass
1785+
1786+
@dataclass
1787+
class A:
1788+
x: "A" | None
1789+
1790+
static_assert(is_subtype_of(type[A], Callable[[A], A]))
1791+
static_assert(is_subtype_of(type[A], Callable[[None], A]))
1792+
static_assert(is_subtype_of(type[A], Callable[[A | None], A]))
1793+
static_assert(not is_subtype_of(type[A], Callable[[int], A]))
1794+
```
1795+
17771796
### Bound methods
17781797

17791798
```py

crates/ty_python_semantic/src/types/class.rs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -661,27 +661,33 @@ impl<'db> ClassType<'db> {
661661
// same parameters as the `__init__` method after it is bound, and with the return type of
662662
// the concrete type of `Self`.
663663
let synthesized_dunder_init_callable =
664-
if let Place::Type(Type::FunctionLiteral(dunder_init_function), _) =
665-
dunder_init_function_symbol
666-
{
667-
let synthesized_signature = |signature: Signature<'db>| {
668-
Signature::new(signature.parameters().clone(), Some(correct_return_type))
669-
.bind_self()
664+
if let Place::Type(ty, _) = dunder_init_function_symbol {
665+
let signature = match ty {
666+
Type::FunctionLiteral(dunder_init_function) => {
667+
Some(dunder_init_function.signature(db))
668+
}
669+
Type::Callable(callable) => Some(callable.signatures(db)),
670+
_ => None,
670671
};
671672

672-
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
673-
dunder_init_function
674-
.signature(db)
675-
.overloads
676-
.iter()
677-
.cloned()
678-
.map(synthesized_signature),
679-
);
680-
Some(Type::Callable(CallableType::new(
681-
db,
682-
synthesized_dunder_init_signature,
683-
true,
684-
)))
673+
if let Some(signature) = signature {
674+
let synthesized_signature = |signature: &Signature<'db>| {
675+
Signature::new(signature.parameters().clone(), Some(correct_return_type))
676+
.bind_self()
677+
};
678+
679+
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
680+
signature.overloads.iter().map(synthesized_signature),
681+
);
682+
683+
Some(Type::Callable(CallableType::new(
684+
db,
685+
synthesized_dunder_init_signature,
686+
true,
687+
)))
688+
} else {
689+
None
690+
}
685691
} else {
686692
None
687693
};

0 commit comments

Comments
 (0)