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
2 changes: 1 addition & 1 deletion crates/ruff_benchmark/benches/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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@_
Comment on lines +193 to +200
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on it ... 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually going to ask if you wanted these tests here or in a separate "generics" section, since they are testing the combination of both generic aliases and unions :) Feel free to move them if you want in your PR.

```

If a type is unioned with itself in a value expression, the result is just that type. No
Expand Down Expand Up @@ -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, ...]

Expand Down
9 changes: 7 additions & 2 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(..)
Expand All @@ -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() => {
Expand Down Expand Up @@ -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");
}
_ => {}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
Type::unknown()
}
},
Type::Dynamic(DynamicType::Todo(_)) => {
Type::Dynamic(_) => {
self.infer_type_expression(slice);
value_ty
}
Expand Down Expand Up @@ -887,11 +887,27 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
}
}
Type::GenericAlias(_) => {
self.infer_type_expression(slice);
// 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")
}
_ => {
// TODO: Emit a diagnostic once we've implemented all valid subscript type
// expressions.
self.infer_type_expression(slice);
todo_type!("unknown type subscript")
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()
}
}
}
Expand Down