[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:
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
Reference in New Issue
Block a user