diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 688dcb321ad151..91c561749f1dc3 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -42,6 +42,7 @@ Or, when it's a literal type: ```py # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" +# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" def _(c: Callable[42, str]): reveal_type(c) # revealed: (...) -> Unknown ``` @@ -58,8 +59,6 @@ def _(c: Callable[[int, 42, str, False], None]): ### Missing return type - - Using a parameter list: ```py @@ -84,12 +83,25 @@ Or something else that's invalid in a type expression generally: # fmt: off def _(c: Callable[ # error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)" + # error: [invalid-type-form] "Set literals are not allowed in type expressions" {1, 2} # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" ] ): reveal_type(c) # revealed: (...) -> Unknown ``` +```py +# fmt: off + +def _(c: Callable[ + # error: [invalid-type-form] "Set literals are not allowed in type expressions" + # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" + {1, 2}, 2 # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" + ] + ): + reveal_type(c) # revealed: (...) -> Unknown +``` + ### More than two arguments We can't reliably infer the callable type if there are more then 2 arguments because we don't know diff --git a/crates/ty_python_semantic/resources/mdtest/call/annotation.md b/crates/ty_python_semantic/resources/mdtest/call/annotation.md index 937d4226fc4a93..8deb6ae2dae688 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/annotation.md +++ b/crates/ty_python_semantic/resources/mdtest/call/annotation.md @@ -38,6 +38,7 @@ An invalid `Callable` form can accept any parameters and will return `Unknown`. ```py # error: [invalid-type-form] +# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" def _(c: Callable[42, str]): reveal_type(c()) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 9b6aa19becef47..029335b237f1eb 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -9601,18 +9601,37 @@ impl<'db> TypeInferenceBuilder<'db, '_> { 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>()) - } + _ => Either::Right(std::iter::once(arguments_slice)), }; let first_argument = arguments.next(); - let parameters = - first_argument.and_then(|arg| self.infer_callable_parameter_types(arg)); + // infer_callable_parameter_types(..) does not store the type, but in the case it is not a valid argument + // we infer and store it. `storing_parameters_type_needed` avoids storing the `parameters` type multiple times. + let parameters = first_argument.and_then(|arg| { + let ty = self.infer_callable_parameter_types(arg); + if ty.is_none() { + self.infer_type_expression(arg); + } + ty + }); + let storing_parameters_type_needed = parameters.is_some(); - let return_type = arguments.next().map(|arg| self.infer_type_expression(arg)); + let return_type = match arguments.next() { + Some(expr) => Some(self.infer_type_expression(expr)), + None => { + // In case we have a single argument, we return early since the type annotation is not valid. + // We also make sure to store the `CallableType` to the `parameters` if `parameters` is valid. + let ty = CallableType::unknown(db); + if let Some(first_argument) = first_argument { + if storing_parameters_type_needed { + self.store_expression_type(first_argument, ty); + } + } + report_invalid_arguments_to_callable(&self.context, subscript); + return ty; + } + }; let correct_argument_number = if let Some(third_argument) = arguments.next() { self.infer_type_expression(third_argument); @@ -9637,10 +9656,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }; // `Signature` / `Parameters` are not a `Type` variant, so we're storing - // the outer callable type on these expressions instead. + // the outer callable type on these expressions instead, unless `first_argument` was invalid + // for `Callable`, in which case a type is already stored. self.store_expression_type(arguments_slice, callable_type); if let Some(first_argument) = first_argument { - self.store_expression_type(first_argument, callable_type); + if storing_parameters_type_needed { + self.store_expression_type(first_argument, callable_type); + } } callable_type