diff --git a/crates/ruff_benchmark/benches/ty.rs b/crates/ruff_benchmark/benches/ty.rs index e932aabd15..9ae6e9c40b 100644 --- a/crates/ruff_benchmark/benches/ty.rs +++ b/crates/ruff_benchmark/benches/ty.rs @@ -667,7 +667,7 @@ fn attrs(criterion: &mut Criterion) { max_dep_date: "2025-06-17", python_version: PythonVersion::PY313, }, - 110, + 120, ); bench_project(&benchmark, criterion); diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md index 5202b0921d..de5f16dcbb 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md @@ -98,7 +98,9 @@ async def outer_async(): # avoid unrelated syntax errors on `yield` and `await` n: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions" o: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions" p: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions" - q: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions" + # error: [invalid-type-form] "Slices are not allowed in type expressions" + # error: [invalid-type-form] "Invalid subscript" + q: [1, 2, 3][1:2], ): reveal_type(a) # revealed: Unknown reveal_type(b) # revealed: Unknown @@ -116,7 +118,7 @@ async def outer_async(): # avoid unrelated syntax errors on `yield` and `await` reveal_type(n) # revealed: Unknown reveal_type(o) # revealed: Unknown reveal_type(p) # revealed: int | Unknown - reveal_type(q) # revealed: @Todo(unknown type subscript) + reveal_type(q) # revealed: Unknown class Mat: def __init__(self, value: int): diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/literal.md b/crates/ty_python_semantic/resources/mdtest/annotations/literal.md index 0c6a443afa..8544ddd782 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/literal.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/literal.md @@ -330,10 +330,11 @@ from other import Literal # ? # # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" +# error: [invalid-type-form] "Invalid subscript of object of type `_SpecialForm` in type expression" a1: Literal[26] def f(): - reveal_type(a1) # revealed: @Todo(unknown type subscript) + reveal_type(a1) # revealed: Unknown ``` ## Detecting typing_extensions.Literal diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index 438594650a..12cde54595 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -33,9 +33,11 @@ g(None) We also support unions in type aliases: ```py -from typing_extensions import Any, Never, Literal, LiteralString, Tuple, Annotated, Optional, Union, Callable +from typing_extensions import Any, Never, Literal, LiteralString, Tuple, Annotated, Optional, Union, Callable, TypeVar from ty_extensions import Unknown +T = TypeVar("T") + IntOrStr = int | str IntOrStrOrBytes1 = int | str | bytes IntOrStrOrBytes2 = (int | str) | bytes @@ -70,6 +72,10 @@ IntOrTypeOfStr = int | type[str] TypeOfStrOrInt = type[str] | int IntOrCallable = int | Callable[[str], bytes] CallableOrInt = Callable[[str], bytes] | int +TypeVarOrInt = T | int +IntOrTypeVar = int | T +TypeVarOrNone = T | None +NoneOrTypeVar = None | T reveal_type(IntOrStr) # revealed: types.UnionType reveal_type(IntOrStrOrBytes1) # revealed: types.UnionType @@ -105,6 +111,10 @@ reveal_type(IntOrTypeOfStr) # revealed: types.UnionType reveal_type(TypeOfStrOrInt) # revealed: types.UnionType reveal_type(IntOrCallable) # revealed: types.UnionType reveal_type(CallableOrInt) # revealed: types.UnionType +reveal_type(TypeVarOrInt) # revealed: types.UnionType +reveal_type(IntOrTypeVar) # revealed: types.UnionType +reveal_type(TypeVarOrNone) # revealed: types.UnionType +reveal_type(NoneOrTypeVar) # revealed: types.UnionType def _( int_or_str: IntOrStr, @@ -141,6 +151,10 @@ def _( type_of_str_or_int: TypeOfStrOrInt, int_or_callable: IntOrCallable, callable_or_int: CallableOrInt, + type_var_or_int: TypeVarOrInt, + int_or_type_var: IntOrTypeVar, + type_var_or_none: TypeVarOrNone, + none_or_type_var: NoneOrTypeVar, ): reveal_type(int_or_str) # revealed: int | str reveal_type(int_or_str_or_bytes1) # revealed: int | str | bytes @@ -176,6 +190,14 @@ def _( reveal_type(type_of_str_or_int) # revealed: type[str] | int reveal_type(int_or_callable) # revealed: int | ((str, /) -> bytes) reveal_type(callable_or_int) # revealed: ((str, /) -> bytes) | int + # TODO should be Unknown | int + reveal_type(type_var_or_int) # revealed: T@_ | int + # TODO should be int | Unknown + reveal_type(int_or_type_var) # revealed: int | T@_ + # TODO should be Unknown | None + reveal_type(type_var_or_none) # revealed: T@_ | None + # TODO should be None | Unknown + reveal_type(none_or_type_var) # revealed: None | T@_ ``` If a type is unioned with itself in a value expression, the result is just that type. No @@ -357,7 +379,7 @@ MyList = list[T] def _(my_list: MyList[int]): # TODO: This should be `list[int]` - reveal_type(my_list) # revealed: @Todo(unknown type subscript) + reveal_type(my_list) # revealed: @Todo(specialized generic alias in type expression) ListOrTuple = list[T] | tuple[T, ...] diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index bf1b275165..85c645d37a 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -9507,7 +9507,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | KnownInstanceType::Literal(_) | KnownInstanceType::Annotated(_) | KnownInstanceType::TypeGenericAlias(_) - | KnownInstanceType::Callable(_), + | KnownInstanceType::Callable(_) + | KnownInstanceType::TypeVar(_), ), Type::ClassLiteral(..) | Type::SubclassOf(..) @@ -9518,7 +9519,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | KnownInstanceType::Literal(_) | KnownInstanceType::Annotated(_) | KnownInstanceType::TypeGenericAlias(_) - | KnownInstanceType::Callable(_), + | KnownInstanceType::Callable(_) + | KnownInstanceType::TypeVar(_), ), ast::Operator::BitOr, ) if pep_604_unions_allowed() => { @@ -10926,6 +10928,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .map(Type::from) .unwrap_or_else(Type::unknown); } + Type::KnownInstance(KnownInstanceType::UnionType(_)) => { + return todo_type!("Specialization of union type alias"); + } _ => {} } diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index 56706c90c2..6ea57af90f 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -858,7 +858,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Type::unknown() } }, - Type::Dynamic(DynamicType::Todo(_)) => { + Type::Dynamic(_) => { self.infer_type_expression(slice); value_ty } @@ -887,11 +887,27 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } } } - _ => { - // TODO: Emit a diagnostic once we've implemented all valid subscript type - // expressions. + Type::GenericAlias(_) => { self.infer_type_expression(slice); - todo_type!("unknown type subscript") + // If the generic alias is already fully specialized, this is an error. But it + // could have been specialized with another typevar (e.g. a type alias like `MyList + // = list[T]`), in which case it's later valid to do `MyList[int]`. + todo_type!("specialized generic alias in type expression") + } + Type::StringLiteral(_) => { + self.infer_type_expression(slice); + // For stringified TypeAlias; remove once properly supported + todo_type!("string literal subscripted in type expression") + } + _ => { + self.infer_type_expression(slice); + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { + builder.into_diagnostic(format_args!( + "Invalid subscript of object of type `{}` in type expression", + value_ty.display(self.db()) + )); + } + Type::unknown() } } }