diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md index c4764c3886..49399f9c5a 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/paramspec.md @@ -244,6 +244,7 @@ Explicit specialization of a generic class involving `ParamSpec` is done by prov of types, `...`, or another in-scope `ParamSpec`. ```py +reveal_type(OnlyParamSpec[[]]().attr) # revealed: () -> None reveal_type(OnlyParamSpec[[int, str]]().attr) # revealed: (int, str, /) -> None reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None @@ -252,8 +253,28 @@ def func(c: Callable[P2, None]): # TODO: error: paramspec is unbound reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None + +# error: [invalid-type-arguments] "No type argument provided for required type variable `P1` of class `OnlyParamSpec`" +reveal_type(OnlyParamSpec[()]().attr) # revealed: (...) -> None ``` +An explicit tuple expression (unlike an implicit one that omits the parentheses) is also accepted +when the `ParamSpec` is the only type variable. But, this isn't recommended is mainly a fallout of +it having the same AST as the one without the parentheses. Both mypy and Pyright also allow this. + +```py +reveal_type(OnlyParamSpec[(int, str)]().attr) # revealed: (int, str, /) -> None +``` + + + +```py +# error: [invalid-syntax] +reveal_type(OnlyParamSpec[]().attr) # revealed: (...) -> None +``` + + + The square brackets can be omitted when `ParamSpec` is the only type variable ```py @@ -269,6 +290,7 @@ reveal_type(OnlyParamSpec[int]().attr) # revealed: (int, /) -> None But, they cannot be omitted when there are multiple type variables. ```py +reveal_type(TypeVarAndParamSpec[int, []]().attr) # revealed: () -> int reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str, /) -> int reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int @@ -276,8 +298,12 @@ reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int # TODO: We could still specialize for `T1` as the type is valid which would reveal `(...) -> int` # TODO: error: paramspec is unbound reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown -# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be either a list of types, `ParamSpec`, `Concatenate`, or `...`" +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be" reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> Unknown +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be" +reveal_type(TypeVarAndParamSpec[int, ()]().attr) # revealed: (...) -> Unknown +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be" +reveal_type(TypeVarAndParamSpec[int, (int, str)]().attr) # revealed: (...) -> Unknown ``` Nor can they be omitted when there are more than one `ParamSpec`s. diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md index 6483428bb3..75c76d5d02 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md @@ -228,6 +228,7 @@ Explicit specialization of a generic class involving `ParamSpec` is done by prov of types, `...`, or another in-scope `ParamSpec`. ```py +reveal_type(OnlyParamSpec[[]]().attr) # revealed: () -> None reveal_type(OnlyParamSpec[[int, str]]().attr) # revealed: (int, str, /) -> None reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None @@ -238,8 +239,28 @@ P2 = ParamSpec("P2") # TODO: error: paramspec is unbound reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None + +# error: [invalid-type-arguments] "No type argument provided for required type variable `P1` of class `OnlyParamSpec`" +reveal_type(OnlyParamSpec[()]().attr) # revealed: (...) -> None ``` +An explicit tuple expression (unlike an implicit one that omits the parentheses) is also accepted +when the `ParamSpec` is the only type variable. But, this isn't recommended is mainly a fallout of +it having the same AST as the one without the parentheses. Both mypy and Pyright also allow this. + +```py +reveal_type(OnlyParamSpec[(int, str)]().attr) # revealed: (int, str, /) -> None +``` + + + +```py +# error: [invalid-syntax] +reveal_type(OnlyParamSpec[]().attr) # revealed: (...) -> None +``` + + + The square brackets can be omitted when `ParamSpec` is the only type variable ```py @@ -255,14 +276,19 @@ reveal_type(OnlyParamSpec[int]().attr) # revealed: (int, /) -> None But, they cannot be omitted when there are multiple type variables. ```py +reveal_type(TypeVarAndParamSpec[int, []]().attr) # revealed: () -> int reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str, /) -> int reveal_type(TypeVarAndParamSpec[int, [str]]().attr) # revealed: (str, /) -> int reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int # TODO: error: paramspec is unbound reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown -# error: [invalid-type-arguments] +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be" reveal_type(TypeVarAndParamSpec[int, int]().attr) # revealed: (...) -> Unknown +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be" +reveal_type(TypeVarAndParamSpec[int, ()]().attr) # revealed: (...) -> Unknown +# error: [invalid-type-arguments] "Type argument for `ParamSpec` must be" +reveal_type(TypeVarAndParamSpec[int, (int, str)]().attr) # revealed: (...) -> Unknown ``` Nor can they be omitted when there are more than one `ParamSpec`. diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 6ddaea40a4..6014bb1f3f 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -3472,17 +3472,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { )); } + ast::Expr::Tuple(_) if !exactly_one_paramspec => { + // Tuple expression is only allowed when the generic context contains only one + // `ParamSpec` type variable and no other type variables. + } + ast::Expr::Tuple(ast::ExprTuple { elts, .. }) | ast::Expr::List(ast::ExprList { elts, .. }) => { - // This should be taken care of by the caller. - if expr.is_tuple_expr() { - assert!( - exactly_one_paramspec, - "Inferring ParamSpec value during explicit specialization for a \ - tuple expression should only happen when it contains exactly one ParamSpec" - ); - } - let mut parameter_types = Vec::with_capacity(elts.len()); // Whether to infer `Todo` for the parameters @@ -3519,7 +3515,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return Ok(Type::paramspec_value_callable(db, Parameters::todo())); } - ast::Expr::Name(_) => { + ast::Expr::Name(name) => { + if name.is_invalid() { + return Err(()); + } + let param_type = self.infer_type_expression(expr); match param_type { @@ -11632,7 +11632,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let exactly_one_paramspec = generic_context.exactly_one_paramspec(db); let (type_arguments, store_inferred_type_arguments) = match slice_node { ast::Expr::Tuple(tuple) => { - if exactly_one_paramspec { + if exactly_one_paramspec && !tuple.elts.is_empty() { (std::slice::from_ref(slice_node), false) } else { (tuple.elts.as_slice(), true)