mirror of https://github.com/astral-sh/ruff
[ty] Handle various invalid explicit specializations for `ParamSpec` (#21821)
## Summary fixes: https://github.com/astral-sh/ty/issues/1788 ## Test Plan Add new mdtests. --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
857fd4f683
commit
ac882f7e63
|
|
@ -244,6 +244,7 @@ Explicit specialization of a generic class involving `ParamSpec` is done by prov
|
||||||
of types, `...`, or another in-scope `ParamSpec`.
|
of types, `...`, or another in-scope `ParamSpec`.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
reveal_type(OnlyParamSpec[[]]().attr) # revealed: () -> None
|
||||||
reveal_type(OnlyParamSpec[[int, str]]().attr) # revealed: (int, str, /) -> None
|
reveal_type(OnlyParamSpec[[int, str]]().attr) # revealed: (int, str, /) -> None
|
||||||
reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None
|
reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None
|
||||||
|
|
||||||
|
|
@ -252,8 +253,28 @@ def func(c: Callable[P2, None]):
|
||||||
|
|
||||||
# TODO: error: paramspec is unbound
|
# TODO: error: paramspec is unbound
|
||||||
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- blacken-docs:off -->
|
||||||
|
|
||||||
|
```py
|
||||||
|
# error: [invalid-syntax]
|
||||||
|
reveal_type(OnlyParamSpec[]().attr) # revealed: (...) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- blacken-docs:on -->
|
||||||
|
|
||||||
The square brackets can be omitted when `ParamSpec` is the only type variable
|
The square brackets can be omitted when `ParamSpec` is the only type variable
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
@ -269,6 +290,7 @@ reveal_type(OnlyParamSpec[int]().attr) # revealed: (int, /) -> None
|
||||||
But, they cannot be omitted when there are multiple type variables.
|
But, they cannot be omitted when there are multiple type variables.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
reveal_type(TypeVarAndParamSpec[int, []]().attr) # revealed: () -> int
|
||||||
reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str, /) -> 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, [str]]().attr) # revealed: (str, /) -> int
|
||||||
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> 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: We could still specialize for `T1` as the type is valid which would reveal `(...) -> int`
|
||||||
# TODO: error: paramspec is unbound
|
# TODO: error: paramspec is unbound
|
||||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
|
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
|
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.
|
Nor can they be omitted when there are more than one `ParamSpec`s.
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ Explicit specialization of a generic class involving `ParamSpec` is done by prov
|
||||||
of types, `...`, or another in-scope `ParamSpec`.
|
of types, `...`, or another in-scope `ParamSpec`.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
reveal_type(OnlyParamSpec[[]]().attr) # revealed: () -> None
|
||||||
reveal_type(OnlyParamSpec[[int, str]]().attr) # revealed: (int, str, /) -> None
|
reveal_type(OnlyParamSpec[[int, str]]().attr) # revealed: (int, str, /) -> None
|
||||||
reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None
|
reveal_type(OnlyParamSpec[...]().attr) # revealed: (...) -> None
|
||||||
|
|
||||||
|
|
@ -238,8 +239,28 @@ P2 = ParamSpec("P2")
|
||||||
|
|
||||||
# TODO: error: paramspec is unbound
|
# TODO: error: paramspec is unbound
|
||||||
reveal_type(OnlyParamSpec[P2]().attr) # revealed: (...) -> None
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- blacken-docs:off -->
|
||||||
|
|
||||||
|
```py
|
||||||
|
# error: [invalid-syntax]
|
||||||
|
reveal_type(OnlyParamSpec[]().attr) # revealed: (...) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- blacken-docs:on -->
|
||||||
|
|
||||||
The square brackets can be omitted when `ParamSpec` is the only type variable
|
The square brackets can be omitted when `ParamSpec` is the only type variable
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
@ -255,14 +276,19 @@ reveal_type(OnlyParamSpec[int]().attr) # revealed: (int, /) -> None
|
||||||
But, they cannot be omitted when there are multiple type variables.
|
But, they cannot be omitted when there are multiple type variables.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
reveal_type(TypeVarAndParamSpec[int, []]().attr) # revealed: () -> int
|
||||||
reveal_type(TypeVarAndParamSpec[int, [int, str]]().attr) # revealed: (int, str, /) -> 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, [str]]().attr) # revealed: (str, /) -> int
|
||||||
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
|
reveal_type(TypeVarAndParamSpec[int, ...]().attr) # revealed: (...) -> int
|
||||||
|
|
||||||
# TODO: error: paramspec is unbound
|
# TODO: error: paramspec is unbound
|
||||||
reveal_type(TypeVarAndParamSpec[int, P2]().attr) # revealed: (...) -> Unknown
|
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
|
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`.
|
Nor can they be omitted when there are more than one `ParamSpec`.
|
||||||
|
|
|
||||||
|
|
@ -3472,17 +3472,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
ast::Expr::Tuple(ast::ExprTuple { elts, .. })
|
ast::Expr::Tuple(_) if !exactly_one_paramspec => {
|
||||||
| ast::Expr::List(ast::ExprList { elts, .. }) => {
|
// Tuple expression is only allowed when the generic context contains only one
|
||||||
// This should be taken care of by the caller.
|
// `ParamSpec` type variable and no other type variables.
|
||||||
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"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ast::Expr::Tuple(ast::ExprTuple { elts, .. })
|
||||||
|
| ast::Expr::List(ast::ExprList { elts, .. }) => {
|
||||||
let mut parameter_types = Vec::with_capacity(elts.len());
|
let mut parameter_types = Vec::with_capacity(elts.len());
|
||||||
|
|
||||||
// Whether to infer `Todo` for the parameters
|
// 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()));
|
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);
|
let param_type = self.infer_type_expression(expr);
|
||||||
|
|
||||||
match param_type {
|
match param_type {
|
||||||
|
|
@ -11632,7 +11632,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let exactly_one_paramspec = generic_context.exactly_one_paramspec(db);
|
let exactly_one_paramspec = generic_context.exactly_one_paramspec(db);
|
||||||
let (type_arguments, store_inferred_type_arguments) = match slice_node {
|
let (type_arguments, store_inferred_type_arguments) = match slice_node {
|
||||||
ast::Expr::Tuple(tuple) => {
|
ast::Expr::Tuple(tuple) => {
|
||||||
if exactly_one_paramspec {
|
if exactly_one_paramspec && !tuple.elts.is_empty() {
|
||||||
(std::slice::from_ref(slice_node), false)
|
(std::slice::from_ref(slice_node), false)
|
||||||
} else {
|
} else {
|
||||||
(tuple.elts.as_slice(), true)
|
(tuple.elts.as_slice(), true)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue