Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ def _(c: Callable[[int, 42, str, False], None]):
reveal_type(c)
```

Or, when an ellipsis literal is used as a parameter type in the list (note that the valid gradual
form uses `...` as the entire first argument, not inside a list):

```py
# error: [invalid-type-form] "`[...]` is not a valid parameter list for `Callable`: Did you mean `Callable[..., int]`?"
def _(c: Callable[[...], int]):
reveal_type(c) # revealed: (...) -> int
```

```py
# error: [invalid-type-form] "`...` is not allowed in this context in a type expression"
def _(c: Callable[[int, ...], int]):
reveal_type(c) # revealed: (int, Unknown, /) -> int
```

### Missing return type

Using a parameter list:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ At its simplest, to define a type alias using PEP 695 syntax, you add a list of
`ParamSpec`s or `TypeVarTuple`s after the alias name.

```py
from typing import Callable
from ty_extensions import generic_context

type SingleTypevar[T] = ...
type MultipleTypevars[T, S] = ...
type SingleParamSpec[**P] = ...
type TypeVarAndParamSpec[T, **P] = ...
type SingleTypeVarTuple[*Ts] = ...
type TypeVarAndTypeVarTuple[T, *Ts] = ...
type SingleTypevar[T] = list[T]
type MultipleTypevars[T, S] = tuple[T, S]
type SingleParamSpec[**P] = Callable[P, int]
type TypeVarAndParamSpec[T, **P] = Callable[P, T]
type SingleTypeVarTuple[*Ts] = tuple[*Ts]
type TypeVarAndTypeVarTuple[T, *Ts] = tuple[T, *Ts]

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

```py
# error: [invalid-syntax] "duplicate type parameter"
type RepeatedTypevar[T, T] = ...
type RepeatedTypevar[T, T] = tuple[T, T]
```

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

type B = ...
type B = int

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

```py
type Bounded[T: int] = ...
type BoundedByUnion[T: int | str] = ...
type Bounded[T: int] = list[T]
type BoundedByUnion[T: int | str] = list[T]

class IntSubclass(int): ...

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

```py
type Constrained[T: (int, str)] = ...
type Constrained[T: (int, str)] = list[T]

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

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

```py
type WithDefault[T, U = int] = ...
type WithDefault[T, U = int] = dict[T, U]

reveal_type(WithDefault[str, str]) # revealed: <type alias 'WithDefault[str, str]'>
reveal_type(WithDefault[str]) # revealed: <type alias 'WithDefault[str, int]'>
Expand Down
120 changes: 84 additions & 36 deletions crates/ty_python_semantic/src/types/infer/builder/type_expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
fn report_invalid_type_expression(
&self,
expression: &ast::Expr,
message: std::fmt::Arguments,
message: impl std::fmt::Display,
) -> Option<LintDiagnosticGuard<'_, '_>> {
self.context
.report_lint(&INVALID_TYPE_FORM, expression)
Expand Down Expand Up @@ -514,7 +514,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
ast::Expr::IpyEscapeCommand(_) => todo!("Implement Ipy escape command support"),

ast::Expr::EllipsisLiteral(_) => {
todo_type!("ellipsis literal in type expression")
self.report_invalid_type_expression(
expression,
"`...` is not allowed in this context in a type expression",
);
Type::unknown()
}

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

for element in elements {
if element.is_ellipsis_literal_expr()
&& let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple)
{
let mut diagnostic =
builder.into_diagnostic("Invalid `tuple` specialization");
diagnostic.set_primary_message(
"`...` can only be used as the second element \
in a two-element `tuple` specialization",
);
if element.is_ellipsis_literal_expr() {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple) {
let mut diagnostic =
builder.into_diagnostic("Invalid `tuple` specialization");
diagnostic.set_primary_message(
"`...` can only be used as the second element \
in a two-element `tuple` specialization",
);
}
self.store_expression_type(element, Type::unknown());
element_types.push(Type::unknown());
continue;
}
let element_ty = self.infer_type_expression(element);
return_todo |=
Expand Down Expand Up @@ -720,14 +727,17 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
ty
}
single_element => {
if single_element.is_ellipsis_literal_expr()
&& let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple)
{
let mut diagnostic = builder.into_diagnostic("Invalid `tuple` specialization");
diagnostic.set_primary_message(
"`...` can only be used as the second element \
in a two-element `tuple` specialization",
);
if single_element.is_ellipsis_literal_expr() {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, tuple) {
let mut diagnostic =
builder.into_diagnostic("Invalid `tuple` specialization");
diagnostic.set_primary_message(
"`...` can only be used as the second element \
in a two-element `tuple` specialization",
);
}
self.store_expression_type(single_element, Type::unknown());
return TupleType::heterogeneous(self.db(), std::iter::once(Type::unknown()));
}
let single_element_ty = self.infer_type_expression(single_element);
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty, self)
Expand Down Expand Up @@ -1282,26 +1292,53 @@ impl<'db> TypeInferenceBuilder<'db, '_> {

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);
let callable_type = if parameters.is_none()
&& let Some(first_argument) = first_argument
&& let ast::Expr::List(list) = first_argument
&& let [single_param] = &list.elts[..]
&& single_param.is_ellipsis_literal_expr()
{
self.store_expression_type(single_param, Type::unknown());
if let Some(mut diagnostic) = self.report_invalid_type_expression(
first_argument,
"`[...]` is not a valid parameter list for `Callable`",
) {
if let Some(returns) = return_type {
diagnostic.set_primary_message(format_args!(
"Did you mean `Callable[..., {}]`?",
returns.display(db)
));
}
}
false
Type::single_callable(
db,
Signature::new(
Parameters::unknown(),
return_type.unwrap_or_else(Type::unknown),
),
)
} else {
return_type.is_some()
};
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);
}
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)
{
Type::single_callable(db, Signature::new(parameters, return_type))
} else {
Type::Callable(CallableType::unknown(db))
if correct_argument_number
&& let (Some(parameters), Some(return_type)) = (parameters, return_type)
{
Type::single_callable(db, Signature::new(parameters, return_type))
} else {
Type::Callable(CallableType::unknown(db))
}
};

// `Signature` / `Parameters` are not a `Type` variant, so we're storing
Expand Down Expand Up @@ -1610,7 +1647,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
std::slice::from_ref(arguments_slice)
};
for argument in arguments {
self.infer_type_expression(argument);
if argument.is_ellipsis_literal_expr() {
// The trailing `...` in `Concatenate[int, str, ...]` is valid;
// store without going through type-expression inference.
self.store_expression_type(argument, Type::unknown());
} else {
self.infer_type_expression(argument);
}
}
let num_arguments = arguments.len();
let inferred_type = if num_arguments < 2 {
Expand Down Expand Up @@ -1847,6 +1890,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
return Some(Parameters::gradual_form());
}
ast::Expr::List(ast::ExprList { elts: params, .. }) => {
if let [ast::Expr::EllipsisLiteral(_)] = &params[..] {
// Return `None` here so that we emit a specific diagnostic at the callsite.
return None;
}

let mut parameter_types = Vec::with_capacity(params.len());

// Whether to infer `Todo` for the parameters
Expand Down