use self annotation in synthesized __init__ callable

This commit is contained in:
Douglas Creager 2025-12-03 11:56:46 -05:00
parent 77ce24a5bf
commit 85e6143e07
2 changed files with 39 additions and 30 deletions

View File

@ -658,11 +658,14 @@ class ClassWithOverloadedInit[T]:
def __init__(self: "ClassWithOverloadedInit[str]", x: str) -> None: ...
def __init__(self, x: int | str) -> None: ...
# TODO: These unions are because we don't handle the ParamSpec in accepts_callable, so when
# inferring a specialization through the Callable we lose the information about how the parameter
# types distinguish the two overloads.
# TODO: revealed: ClassWithOverloadedInit[int]
# revealed: Top[ClassWithOverloadedInit[Unknown]]
# revealed: ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]
reveal_type(accepts_callable(ClassWithOverloadedInit)(0))
# TODO: revealed: ClassWithOverloadedInit[str]
# revealed: Top[ClassWithOverloadedInit[Unknown]]
# revealed: ClassWithOverloadedInit[int] | ClassWithOverloadedInit[str]
reveal_type(accepts_callable(ClassWithOverloadedInit)(""))
class GenericClass[T]:

View File

@ -1199,39 +1199,45 @@ impl<'db> ClassType<'db> {
// If the class defines an `__init__` method, then we synthesize a callable type with the
// 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::Defined(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_callable = if let Place::Defined(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,
};
if let Some(signature) = signature {
let synthesized_signature = |signature: &Signature<'db>| {
let self_annotation = signature
.parameters()
.get_positional(0)
.and_then(Parameter::annotated_type);
let return_type = self_annotation.unwrap_or(correct_return_type);
let instance_ty = self_annotation.unwrap_or_else(|| Type::instance(db, self));
Signature::new(signature.parameters().clone(), Some(return_type))
.with_definition(signature.definition())
.bind_self(db, Some(instance_ty))
};
if let Some(signature) = signature {
let synthesized_signature = |signature: &Signature<'db>| {
let instance_ty = Type::instance(db, self);
Signature::new(signature.parameters().clone(), Some(correct_return_type))
.with_definition(signature.definition())
.bind_self(db, Some(instance_ty))
};
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
signature.overloads.iter().map(synthesized_signature),
);
let synthesized_dunder_init_signature = CallableSignature::from_overloads(
signature.overloads.iter().map(synthesized_signature),
);
Some(CallableType::new(
db,
synthesized_dunder_init_signature,
true,
))
} else {
None
}
Some(CallableType::new(
db,
synthesized_dunder_init_signature,
true,
))
} else {
None
};
}
} else {
None
};
match (dunder_new_function, synthesized_dunder_init_callable) {
(Some(dunder_new_function), Some(synthesized_dunder_init_callable)) => {