Skip to content

Commit 03af8b2

Browse files
authored
[ty] Reject ellipsis literals in odd places in type/annotation expressions (#23611)
1 parent 2104d0a commit 03af8b2

3 files changed

Lines changed: 112 additions & 48 deletions

File tree

crates/ty_python_semantic/resources/mdtest/annotations/callable.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ def _(c: Callable[[int, 42, str, False], None]):
5959
reveal_type(c)
6060
```
6161

62+
Or, when an ellipsis literal is used as a parameter type in the list (note that the valid gradual
63+
form uses `...` as the entire first argument, not inside a list):
64+
65+
```py
66+
# error: [invalid-type-form] "`[...]` is not a valid parameter list for `Callable`: Did you mean `Callable[..., int]`?"
67+
def _(c: Callable[[...], int]):
68+
reveal_type(c) # revealed: (...) -> int
69+
```
70+
71+
```py
72+
# error: [invalid-type-form] "`...` is not allowed in this context in a type expression"
73+
def _(c: Callable[[int, ...], int]):
74+
reveal_type(c) # revealed: (int, Unknown, /) -> int
75+
```
76+
6277
### Missing return type
6378

6479
Using a parameter list:

crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ At its simplest, to define a type alias using PEP 695 syntax, you add a list of
1111
`ParamSpec`s or `TypeVarTuple`s after the alias name.
1212

1313
```py
14+
from typing import Callable
1415
from ty_extensions import generic_context
1516

16-
type SingleTypevar[T] = ...
17-
type MultipleTypevars[T, S] = ...
18-
type SingleParamSpec[**P] = ...
19-
type TypeVarAndParamSpec[T, **P] = ...
20-
type SingleTypeVarTuple[*Ts] = ...
21-
type TypeVarAndTypeVarTuple[T, *Ts] = ...
17+
type SingleTypevar[T] = list[T]
18+
type MultipleTypevars[T, S] = tuple[T, S]
19+
type SingleParamSpec[**P] = Callable[P, int]
20+
type TypeVarAndParamSpec[T, **P] = Callable[P, T]
21+
type SingleTypeVarTuple[*Ts] = tuple[*Ts]
22+
type TypeVarAndTypeVarTuple[T, *Ts] = tuple[T, *Ts]
2223

2324
# revealed: ty_extensions.GenericContext[T@SingleTypevar]
2425
reveal_type(generic_context(SingleTypevar))
@@ -41,7 +42,7 @@ You cannot use the same typevar more than once.
4142

4243
```py
4344
# error: [invalid-syntax] "duplicate type parameter"
44-
type RepeatedTypevar[T, T] = ...
45+
type RepeatedTypevar[T, T] = tuple[T, T]
4546
```
4647

4748
## Specializing type aliases explicitly
@@ -70,7 +71,7 @@ And non-generic types cannot be specialized:
7071
```py
7172
from typing import TypeVar, Protocol, TypedDict
7273

73-
type B = ...
74+
type B = int
7475

7576
# error: [not-subscriptable] "Cannot subscript non-generic type alias `B`"
7677
reveal_type(B[int]) # revealed: Unknown
@@ -158,8 +159,8 @@ def _(x: Union[int]):
158159
If the type variable has an upper bound, the specialized type must satisfy that bound:
159160

160161
```py
161-
type Bounded[T: int] = ...
162-
type BoundedByUnion[T: int | str] = ...
162+
type Bounded[T: int] = list[T]
163+
type BoundedByUnion[T: int | str] = list[T]
163164

164165
class IntSubclass(int): ...
165166

@@ -190,7 +191,7 @@ def _(x: TupleOfIntAndStr[int, int]):
190191
If the type variable is constrained, the specialized type must satisfy those constraints:
191192

192193
```py
193-
type Constrained[T: (int, str)] = ...
194+
type Constrained[T: (int, str)] = list[T]
194195

195196
reveal_type(Constrained[int]) # revealed: <type alias 'Constrained[int]'>
196197

@@ -220,7 +221,7 @@ def _(x: TupleOfIntOrStr[int, object]):
220221
If the type variable has a default, it can be omitted:
221222

222223
```py
223-
type WithDefault[T, U = int] = ...
224+
type WithDefault[T, U = int] = dict[T, U]
224225

225226
reveal_type(WithDefault[str, str]) # revealed: <type alias 'WithDefault[str, str]'>
226227
reveal_type(WithDefault[str]) # revealed: <type alias 'WithDefault[str, int]'>

crates/ty_python_semantic/src/types/infer/builder/type_expression.rs

Lines changed: 84 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
6565
fn report_invalid_type_expression(
6666
&self,
6767
expression: &ast::Expr,
68-
message: std::fmt::Arguments,
68+
message: impl std::fmt::Display,
6969
) -> Option<LintDiagnosticGuard<'_, '_>> {
7070
self.context
7171
.report_lint(&INVALID_TYPE_FORM, expression)
@@ -514,7 +514,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
514514
ast::Expr::IpyEscapeCommand(_) => todo!("Implement Ipy escape command support"),
515515

516516
ast::Expr::EllipsisLiteral(_) => {
517-
todo_type!("ellipsis literal in type expression")
517+
self.report_invalid_type_expression(
518+
expression,
519+
"`...` is not allowed in this context in a type expression",
520+
);
521+
Type::unknown()
518522
}
519523

520524
ast::Expr::Starred(starred) => self.infer_starred_type_expression(starred),
@@ -639,15 +643,18 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
639643
let mut first_unpacked_variadic_tuple = None;
640644

641645
for element in elements {
642-
if element.is_ellipsis_literal_expr()
643-
&& let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple)
644-
{
645-
let mut diagnostic =
646-
builder.into_diagnostic("Invalid `tuple` specialization");
647-
diagnostic.set_primary_message(
648-
"`...` can only be used as the second element \
649-
in a two-element `tuple` specialization",
650-
);
646+
if element.is_ellipsis_literal_expr() {
647+
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple) {
648+
let mut diagnostic =
649+
builder.into_diagnostic("Invalid `tuple` specialization");
650+
diagnostic.set_primary_message(
651+
"`...` can only be used as the second element \
652+
in a two-element `tuple` specialization",
653+
);
654+
}
655+
self.store_expression_type(element, Type::unknown());
656+
element_types.push(Type::unknown());
657+
continue;
651658
}
652659
let element_ty = self.infer_type_expression(element);
653660
return_todo |=
@@ -720,14 +727,17 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
720727
ty
721728
}
722729
single_element => {
723-
if single_element.is_ellipsis_literal_expr()
724-
&& let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple)
725-
{
726-
let mut diagnostic = builder.into_diagnostic("Invalid `tuple` specialization");
727-
diagnostic.set_primary_message(
728-
"`...` can only be used as the second element \
729-
in a two-element `tuple` specialization",
730-
);
730+
if single_element.is_ellipsis_literal_expr() {
731+
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple) {
732+
let mut diagnostic =
733+
builder.into_diagnostic("Invalid `tuple` specialization");
734+
diagnostic.set_primary_message(
735+
"`...` can only be used as the second element \
736+
in a two-element `tuple` specialization",
737+
);
738+
}
739+
self.store_expression_type(single_element, Type::unknown());
740+
return TupleType::heterogeneous(self.db(), std::iter::once(Type::unknown()));
731741
}
732742
let single_element_ty = self.infer_type_expression(single_element);
733743
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty, self)
@@ -1282,26 +1292,53 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
12821292

12831293
let return_type = arguments.next().map(|arg| self.infer_type_expression(arg));
12841294

1285-
let correct_argument_number = if let Some(third_argument) = arguments.next() {
1286-
self.infer_type_expression(third_argument);
1287-
for argument in arguments {
1288-
self.infer_type_expression(argument);
1295+
let callable_type = if parameters.is_none()
1296+
&& let Some(first_argument) = first_argument
1297+
&& let ast::Expr::List(list) = first_argument
1298+
&& let [single_param] = &list.elts[..]
1299+
&& single_param.is_ellipsis_literal_expr()
1300+
{
1301+
self.store_expression_type(single_param, Type::unknown());
1302+
if let Some(mut diagnostic) = self.report_invalid_type_expression(
1303+
first_argument,
1304+
"`[...]` is not a valid parameter list for `Callable`",
1305+
) {
1306+
if let Some(returns) = return_type {
1307+
diagnostic.set_primary_message(format_args!(
1308+
"Did you mean `Callable[..., {}]`?",
1309+
returns.display(db)
1310+
));
1311+
}
12891312
}
1290-
false
1313+
Type::single_callable(
1314+
db,
1315+
Signature::new(
1316+
Parameters::unknown(),
1317+
return_type.unwrap_or_else(Type::unknown),
1318+
),
1319+
)
12911320
} else {
1292-
return_type.is_some()
1293-
};
1321+
let correct_argument_number = if let Some(third_argument) = arguments.next() {
1322+
self.infer_type_expression(third_argument);
1323+
for argument in arguments {
1324+
self.infer_type_expression(argument);
1325+
}
1326+
false
1327+
} else {
1328+
return_type.is_some()
1329+
};
12941330

1295-
if !correct_argument_number {
1296-
report_invalid_arguments_to_callable(&self.context, subscript);
1297-
}
1331+
if !correct_argument_number {
1332+
report_invalid_arguments_to_callable(&self.context, subscript);
1333+
}
12981334

1299-
let callable_type = if let (Some(parameters), Some(return_type), true) =
1300-
(parameters, return_type, correct_argument_number)
1301-
{
1302-
Type::single_callable(db, Signature::new(parameters, return_type))
1303-
} else {
1304-
Type::Callable(CallableType::unknown(db))
1335+
if correct_argument_number
1336+
&& let (Some(parameters), Some(return_type)) = (parameters, return_type)
1337+
{
1338+
Type::single_callable(db, Signature::new(parameters, return_type))
1339+
} else {
1340+
Type::Callable(CallableType::unknown(db))
1341+
}
13051342
};
13061343

13071344
// `Signature` / `Parameters` are not a `Type` variant, so we're storing
@@ -1610,7 +1647,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
16101647
std::slice::from_ref(arguments_slice)
16111648
};
16121649
for argument in arguments {
1613-
self.infer_type_expression(argument);
1650+
if argument.is_ellipsis_literal_expr() {
1651+
// The trailing `...` in `Concatenate[int, str, ...]` is valid;
1652+
// store without going through type-expression inference.
1653+
self.store_expression_type(argument, Type::unknown());
1654+
} else {
1655+
self.infer_type_expression(argument);
1656+
}
16141657
}
16151658
let num_arguments = arguments.len();
16161659
let inferred_type = if num_arguments < 2 {
@@ -1847,6 +1890,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
18471890
return Some(Parameters::gradual_form());
18481891
}
18491892
ast::Expr::List(ast::ExprList { elts: params, .. }) => {
1893+
if let [ast::Expr::EllipsisLiteral(_)] = &params[..] {
1894+
// Return `None` here so that we emit a specific diagnostic at the callsite.
1895+
return None;
1896+
}
1897+
18501898
let mut parameter_types = Vec::with_capacity(params.len());
18511899

18521900
// Whether to infer `Todo` for the parameters

0 commit comments

Comments
 (0)