Skip to content

Commit 45e98ea

Browse files
committed
[ty] tighten up handling of subscripts in type expressions
1 parent b1e354b commit 45e98ea

File tree

5 files changed

+50
-9
lines changed

5 files changed

+50
-9
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
9898
n: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
9999
o: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
100100
p: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
101-
q: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
101+
# error: [invalid-type-form] "Slices are not allowed in type expressions"
102+
# error: [invalid-type-form] "Cannot subscript object of type"
103+
q: [1, 2, 3][1:2],
102104
):
103105
reveal_type(a) # revealed: Unknown
104106
reveal_type(b) # revealed: Unknown
@@ -116,7 +118,7 @@ async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
116118
reveal_type(n) # revealed: Unknown
117119
reveal_type(o) # revealed: Unknown
118120
reveal_type(p) # revealed: int | Unknown
119-
reveal_type(q) # revealed: @Todo(unknown type subscript)
121+
reveal_type(q) # revealed: Unknown
120122

121123
class Mat:
122124
def __init__(self, value: int):

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,11 @@ from other import Literal
330330
# ?
331331
#
332332
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
333+
# error: [invalid-type-form] "Cannot subscript object of type `_SpecialForm` in type expression"
333334
a1: Literal[26]
334335

335336
def f():
336-
reveal_type(a1) # revealed: @Todo(unknown type subscript)
337+
reveal_type(a1) # revealed: Unknown
337338
```
338339

339340
## Detecting typing_extensions.Literal

crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ g(None)
3333
We also support unions in type aliases:
3434

3535
```py
36-
from typing_extensions import Any, Never, Literal, LiteralString, Tuple, Annotated, Optional, Union
36+
from typing_extensions import Any, Never, Literal, LiteralString, Tuple, Annotated, Optional, Union, TypeVar
3737
from ty_extensions import Unknown
3838

39+
T = TypeVar("T")
40+
3941
IntOrStr = int | str
4042
IntOrStrOrBytes1 = int | str | bytes
4143
IntOrStrOrBytes2 = (int | str) | bytes
@@ -68,6 +70,10 @@ IntOrOptional = int | Optional[str]
6870
OptionalOrInt = Optional[str] | int
6971
IntOrTypeOfStr = int | type[str]
7072
TypeOfStrOrInt = type[str] | int
73+
TypeVarOrInt = T | int
74+
IntOrTypeVar = int | T
75+
TypeVarOrNone = T | None
76+
NoneOrTypeVar = None | T
7177

7278
reveal_type(IntOrStr) # revealed: types.UnionType
7379
reveal_type(IntOrStrOrBytes1) # revealed: types.UnionType
@@ -101,6 +107,10 @@ reveal_type(IntOrOptional) # revealed: types.UnionType
101107
reveal_type(OptionalOrInt) # revealed: types.UnionType
102108
reveal_type(IntOrTypeOfStr) # revealed: types.UnionType
103109
reveal_type(TypeOfStrOrInt) # revealed: types.UnionType
110+
reveal_type(TypeVarOrInt) # revealed: types.UnionType
111+
reveal_type(IntOrTypeVar) # revealed: types.UnionType
112+
reveal_type(TypeVarOrNone) # revealed: types.UnionType
113+
reveal_type(NoneOrTypeVar) # revealed: types.UnionType
104114

105115
def _(
106116
int_or_str: IntOrStr,
@@ -135,6 +145,10 @@ def _(
135145
optional_or_int: OptionalOrInt,
136146
int_or_type_of_str: IntOrTypeOfStr,
137147
type_of_str_or_int: TypeOfStrOrInt,
148+
type_var_or_int: TypeVarOrInt,
149+
int_or_type_var: IntOrTypeVar,
150+
type_var_or_none: TypeVarOrNone,
151+
none_or_type_var: NoneOrTypeVar,
138152
):
139153
reveal_type(int_or_str) # revealed: int | str
140154
reveal_type(int_or_str_or_bytes1) # revealed: int | str | bytes
@@ -168,6 +182,14 @@ def _(
168182
reveal_type(optional_or_int) # revealed: str | None | int
169183
reveal_type(int_or_type_of_str) # revealed: int | type[str]
170184
reveal_type(type_of_str_or_int) # revealed: type[str] | int
185+
# TODO should be Unknown | int
186+
reveal_type(type_var_or_int) # revealed: T@_ | int
187+
# TODO should be int | Unknown
188+
reveal_type(int_or_type_var) # revealed: int | T@_
189+
# TODO should be Unknown | None
190+
reveal_type(type_var_or_none) # revealed: T@_ | None
191+
# TODO should be None | Unknown
192+
reveal_type(none_or_type_var) # revealed: None | T@_
171193
```
172194

173195
If a type is unioned with itself in a value expression, the result is just that type. No
@@ -349,7 +371,7 @@ MyList = list[T]
349371

350372
def _(my_list: MyList[int]):
351373
# TODO: This should be `list[int]`
352-
reveal_type(my_list) # revealed: @Todo(unknown type subscript)
374+
reveal_type(my_list) # revealed: @Todo(specialized generic alias in type expression)
353375

354376
ListOrTuple = list[T] | tuple[T, ...]
355377

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9506,6 +9506,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
95069506
KnownInstanceType::UnionType(_)
95079507
| KnownInstanceType::Literal(_)
95089508
| KnownInstanceType::Annotated(_)
9509+
| KnownInstanceType::TypeVar(_)
95099510
| KnownInstanceType::TypeGenericAlias(_),
95109511
),
95119512
Type::ClassLiteral(..)
@@ -9516,6 +9517,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
95169517
KnownInstanceType::UnionType(_)
95179518
| KnownInstanceType::Literal(_)
95189519
| KnownInstanceType::Annotated(_)
9520+
| KnownInstanceType::TypeVar(_)
95199521
| KnownInstanceType::TypeGenericAlias(_),
95209522
),
95219523
ast::Operator::BitOr,
@@ -10916,6 +10918,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
1091610918
.map(Type::from)
1091710919
.unwrap_or_else(Type::unknown);
1091810920
}
10921+
Type::KnownInstance(KnownInstanceType::UnionType(_)) => {
10922+
return todo_type!("Specialization of union type alias");
10923+
}
1091910924
_ => {}
1092010925
}
1092110926

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
854854
Type::unknown()
855855
}
856856
},
857-
Type::Dynamic(DynamicType::Todo(_)) => {
857+
Type::Dynamic(_) => {
858858
self.infer_type_expression(slice);
859859
value_ty
860860
}
@@ -883,11 +883,22 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
883883
}
884884
}
885885
}
886+
Type::GenericAlias(_) => {
887+
self.infer_type_expression(slice);
888+
// If the generic alias is already fully specialized, this is an error. But it
889+
// could have been specialized with another typevar (e.g. a type alias like `MyList
890+
// = list[T]`), in which case it's later valid to do `MyList[int]`.
891+
todo_type!("specialized generic alias in type expression")
892+
}
886893
_ => {
887-
// TODO: Emit a diagnostic once we've implemented all valid subscript type
888-
// expressions.
889894
self.infer_type_expression(slice);
890-
todo_type!("unknown type subscript")
895+
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
896+
builder.into_diagnostic(format_args!(
897+
"Cannot subscript object of type `{}` in type expression",
898+
value_ty.display(self.db())
899+
));
900+
}
901+
Type::unknown()
891902
}
892903
}
893904
}

0 commit comments

Comments
 (0)