[ty] Implicit type aliases: Add support for Callable (#21496)

## Summary

Add support for `Callable` special forms in implicit type aliases.

## Typing conformance

Four new tests are passing

## Ecosystem impact

* All of the `invalid-type-form` errors are from libraries that use
`mypy_extensions` and do something like `Callable[[NamedArg("x", str)],
int]`.
* A handful of new false positives because we do not support generic
specializations of implicit type aliases, yet. But other
* Everything else looks like true positives or known limitations

## Test Plan

New Markdown tests.
This commit is contained in:
David Peter
2025-11-18 09:06:05 +01:00
committed by GitHub
parent b1e354bd99
commit d5a95ec824
5 changed files with 153 additions and 57 deletions

View File

@@ -6747,6 +6747,7 @@ impl<'db> Type<'db> {
Ok(ty.inner(db).to_meta_type(db))
}
KnownInstanceType::Callable(callable) => Ok(Type::Callable(*callable)),
},
Type::SpecialForm(special_form) => match special_form {
@@ -7990,6 +7991,9 @@ pub enum KnownInstanceType<'db> {
/// An instance of `typing.GenericAlias` representing a `type[...]` expression.
TypeGenericAlias(InternedType<'db>),
/// An instance of `typing.GenericAlias` representing a `Callable[...]` expression.
Callable(CallableType<'db>),
/// An identity callable created with `typing.NewType(name, base)`, which behaves like a
/// subtype of `base` in type expressions. See the `struct NewType` payload for an example.
NewType(NewType<'db>),
@@ -8029,6 +8033,9 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
| KnownInstanceType::TypeGenericAlias(ty) => {
visitor.visit_type(db, ty.inner(db));
}
KnownInstanceType::Callable(callable) => {
visitor.visit_callable_type(db, callable);
}
KnownInstanceType::NewType(newtype) => {
if let ClassType::Generic(generic_alias) = newtype.base_class_type(db) {
visitor.visit_generic_alias_type(db, generic_alias);
@@ -8074,6 +8081,7 @@ impl<'db> KnownInstanceType<'db> {
Self::Literal(ty) => Self::Literal(ty.normalized_impl(db, visitor)),
Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)),
Self::TypeGenericAlias(ty) => Self::TypeGenericAlias(ty.normalized_impl(db, visitor)),
Self::Callable(callable) => Self::Callable(callable.normalized_impl(db, visitor)),
Self::NewType(newtype) => Self::NewType(
newtype
.map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)),
@@ -8096,9 +8104,10 @@ impl<'db> KnownInstanceType<'db> {
Self::Field(_) => KnownClass::Field,
Self::ConstraintSet(_) => KnownClass::ConstraintSet,
Self::UnionType(_) => KnownClass::UnionType,
Self::Literal(_) | Self::Annotated(_) | Self::TypeGenericAlias(_) => {
KnownClass::GenericAlias
}
Self::Literal(_)
| Self::Annotated(_)
| Self::TypeGenericAlias(_)
| Self::Callable(_) => KnownClass::GenericAlias,
Self::NewType(_) => KnownClass::NewType,
}
}
@@ -8184,7 +8193,9 @@ impl<'db> KnownInstanceType<'db> {
KnownInstanceType::Annotated(_) => {
f.write_str("<typing.Annotated special form>")
}
KnownInstanceType::TypeGenericAlias(_) => f.write_str("GenericAlias"),
KnownInstanceType::TypeGenericAlias(_) | KnownInstanceType::Callable(_) => {
f.write_str("GenericAlias")
}
KnownInstanceType::NewType(declaration) => {
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
}

View File

@@ -174,6 +174,7 @@ impl<'db> ClassBase<'db> {
| KnownInstanceType::Deprecated(_)
| KnownInstanceType::Field(_)
| KnownInstanceType::ConstraintSet(_)
| KnownInstanceType::Callable(_)
| KnownInstanceType::UnionType(_)
| KnownInstanceType::Literal(_)
// A class inheriting from a newtype would make intuitive sense, but newtype

View File

@@ -9506,7 +9506,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
KnownInstanceType::UnionType(_)
| KnownInstanceType::Literal(_)
| KnownInstanceType::Annotated(_)
| KnownInstanceType::TypeGenericAlias(_),
| KnownInstanceType::TypeGenericAlias(_)
| KnownInstanceType::Callable(_),
),
Type::ClassLiteral(..)
| Type::SubclassOf(..)
@@ -9516,7 +9517,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
KnownInstanceType::UnionType(_)
| KnownInstanceType::Literal(_)
| KnownInstanceType::Annotated(_)
| KnownInstanceType::TypeGenericAlias(_),
| KnownInstanceType::TypeGenericAlias(_)
| KnownInstanceType::Callable(_),
),
ast::Operator::BitOr,
) if pep_604_unions_allowed() => {
@@ -10827,6 +10829,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
InternedType::new(self.db(), argument_ty),
));
}
Type::SpecialForm(SpecialFormType::Callable) => {
let callable = self
.infer_callable_type(subscript)
.as_callable()
.expect("always returns Type::Callable");
return Type::KnownInstance(KnownInstanceType::Callable(callable));
}
// `typing` special forms with a single generic argument
Type::SpecialForm(
special_form @ (SpecialFormType::List

View File

@@ -839,6 +839,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
Type::unknown()
}
KnownInstanceType::Callable(_) => {
self.infer_type_expression(slice);
todo_type!("Generic specialization of typing.Callable")
}
KnownInstanceType::Annotated(_) => {
self.infer_type_expression(slice);
todo_type!("Generic specialization of typing.Annotated")
@@ -929,6 +933,58 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
ty
}
/// Infer the type of a `Callable[...]` type expression.
pub(crate) fn infer_callable_type(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
let db = self.db();
let arguments_slice = &*subscript.slice;
let mut arguments = match arguments_slice {
ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()),
_ => {
self.infer_callable_parameter_types(arguments_slice);
Either::Right(std::iter::empty::<&ast::Expr>())
}
};
let first_argument = arguments.next();
let parameters = first_argument.and_then(|arg| self.infer_callable_parameter_types(arg));
let return_type = arguments.next().map(|arg| self.infer_type_expression(arg));
let correct_argument_number = if let Some(third_argument) = arguments.next() {
self.infer_type_expression(third_argument);
for argument in arguments {
self.infer_type_expression(argument);
}
false
} else {
return_type.is_some()
};
if !correct_argument_number {
report_invalid_arguments_to_callable(&self.context, subscript);
}
let callable_type = if let (Some(parameters), Some(return_type), true) =
(parameters, return_type, correct_argument_number)
{
CallableType::single(db, Signature::new(parameters, Some(return_type)))
} else {
CallableType::unknown(db)
};
// `Signature` / `Parameters` are not a `Type` variant, so we're storing
// the outer callable type on these expressions instead.
self.store_expression_type(arguments_slice, callable_type);
if let Some(first_argument) = first_argument {
self.store_expression_type(first_argument, callable_type);
}
callable_type
}
pub(crate) fn infer_parameterized_special_form_type_expression(
&mut self,
subscript: &ast::ExprSubscript,
@@ -979,53 +1035,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
_ => self.infer_type_expression(arguments_slice),
},
SpecialFormType::Callable => {
let mut arguments = match arguments_slice {
ast::Expr::Tuple(tuple) => Either::Left(tuple.iter()),
_ => {
self.infer_callable_parameter_types(arguments_slice);
Either::Right(std::iter::empty::<&ast::Expr>())
}
};
let first_argument = arguments.next();
let parameters =
first_argument.and_then(|arg| self.infer_callable_parameter_types(arg));
let return_type = arguments.next().map(|arg| self.infer_type_expression(arg));
let correct_argument_number = if let Some(third_argument) = arguments.next() {
self.infer_type_expression(third_argument);
for argument in arguments {
self.infer_type_expression(argument);
}
false
} else {
return_type.is_some()
};
if !correct_argument_number {
report_invalid_arguments_to_callable(&self.context, subscript);
}
let callable_type = if let (Some(parameters), Some(return_type), true) =
(parameters, return_type, correct_argument_number)
{
CallableType::single(db, Signature::new(parameters, Some(return_type)))
} else {
CallableType::unknown(db)
};
// `Signature` / `Parameters` are not a `Type` variant, so we're storing
// the outer callable type on these expressions instead.
self.store_expression_type(arguments_slice, callable_type);
if let Some(first_argument) = first_argument {
self.store_expression_type(first_argument, callable_type);
}
callable_type
}
SpecialFormType::Callable => self.infer_callable_type(subscript),
// `ty_extensions` special forms
SpecialFormType::Not => {
@@ -1491,7 +1501,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
///
/// It returns `None` if the argument is invalid i.e., not a list of types, parameter
/// specification, `typing.Concatenate`, or `...`.
fn infer_callable_parameter_types(
pub(super) fn infer_callable_parameter_types(
&mut self,
parameters: &ast::Expr,
) -> Option<Parameters<'db>> {