[ty] Emit invalid type form for stringified annotations (#22752)

## Summary

Closes https://github.com/astral-sh/ty/issues/2553.
This commit is contained in:
Charlie Marsh
2026-01-20 03:37:55 -05:00
committed by GitHub
parent af886b06c9
commit 0a1dddbf73
3 changed files with 122 additions and 23 deletions

View File

@@ -155,6 +155,52 @@ def invalid_binary_operators(
reveal_type(l) # revealed: Unknown
```
## Invalid AST nodes in string annotations
Invalid AST nodes should also be rejected when they appear in string annotations:
```py
def bar() -> None:
return None
async def baz(): ...
async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
def _(
a: "1", # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
b: "2.3", # error: [invalid-type-form] "Float literals are not allowed in type expressions"
c: "4j", # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
d: "True", # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
e: "1 and 2", # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
f: "1 or 2", # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
g: "(foo := 1)", # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
h: "not 1", # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
i: "lambda: 1", # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
j: "1 if True else 2", # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
k: "await baz()", # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
l: "(yield 1)", # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
m: "1 < 2", # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
n: "bar()", # error: [invalid-type-form] "Function calls are not allowed in type expressions"
# error: [invalid-type-form] "Slices are not allowed in type expressions"
# error: [invalid-type-form] "Invalid subscript"
o: "[1, 2, 3][1:2]",
):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: Unknown
reveal_type(e) # revealed: Unknown
reveal_type(f) # revealed: Unknown
reveal_type(g) # revealed: Unknown
reveal_type(h) # revealed: Unknown
reveal_type(i) # revealed: Unknown
reveal_type(j) # revealed: Unknown
reveal_type(k) # revealed: Unknown
reveal_type(l) # revealed: Unknown
reveal_type(m) # revealed: Unknown
reveal_type(n) # revealed: Unknown
reveal_type(o) # revealed: Unknown
```
## Invalid Collection based AST nodes
```toml

View File

@@ -166,29 +166,48 @@ stringified_fstring_with_dict_comprehension: "f'{ {i: i for i in range(5)} }'"
# error: [fstring-type-annotation]
stringified_fstring_with_set_comprehension: "f'{ {i for i in range(5)} }'"
# error: [invalid-type-form]
a: "1 or 2"
# error: [invalid-type-form]
b: "(x := 1)"
# error: [invalid-type-form]
c: "1 + 2"
# Regression test for https://github.com/astral-sh/ty/issues/1847
# error: [invalid-type-form]
c2: "a*(i for i in [])"
# error: [invalid-type-form]
d: "lambda x: x"
# error: [invalid-type-form]
e: "x if True else y"
# error: [invalid-type-form]
f: "{'a': 1, 'b': 2}"
# error: [invalid-type-form]
g: "{1, 2}"
# error: [invalid-type-form]
h: "[i for i in range(5)]"
# error: [invalid-type-form]
i: "{i for i in range(5)}"
# error: [invalid-type-form]
j: "{i: i for i in range(5)}"
# error: [invalid-type-form]
k: "(i for i in range(5))"
# error: [invalid-type-form]
l: "await 1"
# error: [invalid-syntax-in-forward-annotation]
m: "yield 1"
# error: [invalid-syntax-in-forward-annotation]
n: "yield from 1"
# error: [invalid-type-form]
o: "1 < 2"
# error: [invalid-type-form]
p: "call()"
# error: [invalid-type-form] "List literals are not allowed"
# error: [invalid-type-form] "Int literals are not allowed"
# error: [invalid-type-form] "Int literals are not allowed"
r: "[1, 2]"
# error: [invalid-type-form] "Tuple literals are not allowed"
# error: [invalid-type-form] "Int literals are not allowed"
# error: [invalid-type-form] "Int literals are not allowed"
s: "(1, 2)"
```

View File

@@ -173,10 +173,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
}
// Avoid inferring the types of invalid type expressions that have been parsed from a
// string annotation, as they are not present in the semantic index.
_ if self.deferred_state.in_string_annotation() => Type::unknown(),
// =====================================================================================
// Forms which are invalid in the context of annotation expressions: we infer their
// nested expressions as normal expressions, but the type of the top-level expression is
@@ -308,7 +304,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::BoolOp(bool_op) => {
self.infer_boolean_expression(bool_op);
if !self.deferred_state.in_string_annotation() {
self.infer_boolean_expression(bool_op);
}
self.report_invalid_type_expression(
expression,
format_args!("Boolean operations are not allowed in type expressions"),
@@ -317,7 +315,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Named(named) => {
self.infer_named_expression(named);
if !self.deferred_state.in_string_annotation() {
self.infer_named_expression(named);
}
self.report_invalid_type_expression(
expression,
format_args!("Named expressions are not allowed in type expressions"),
@@ -326,7 +326,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::UnaryOp(unary) => {
self.infer_unary_expression(unary);
if !self.deferred_state.in_string_annotation() {
self.infer_unary_expression(unary);
}
self.report_invalid_type_expression(
expression,
format_args!("Unary operations are not allowed in type expressions"),
@@ -335,7 +337,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Lambda(lambda_expression) => {
self.infer_lambda_expression(lambda_expression);
if !self.deferred_state.in_string_annotation() {
self.infer_lambda_expression(lambda_expression);
}
self.report_invalid_type_expression(
expression,
format_args!("`lambda` expressions are not allowed in type expressions"),
@@ -344,7 +348,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::If(if_expression) => {
self.infer_if_expression(if_expression, TypeContext::default());
if !self.deferred_state.in_string_annotation() {
self.infer_if_expression(if_expression, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!("`if` expressions are not allowed in type expressions"),
@@ -353,7 +359,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Dict(dict) => {
self.infer_dict_expression(dict, TypeContext::default());
if !self.deferred_state.in_string_annotation() {
self.infer_dict_expression(dict, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!("Dict literals are not allowed in type expressions"),
@@ -362,7 +370,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Set(set) => {
self.infer_set_expression(set, TypeContext::default());
if !self.deferred_state.in_string_annotation() {
self.infer_set_expression(set, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!("Set literals are not allowed in type expressions"),
@@ -371,7 +381,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::DictComp(dictcomp) => {
self.infer_dict_comprehension_expression(dictcomp, TypeContext::default());
if !self.deferred_state.in_string_annotation() {
self.infer_dict_comprehension_expression(dictcomp, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!("Dict comprehensions are not allowed in type expressions"),
@@ -380,7 +392,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::ListComp(listcomp) => {
self.infer_list_comprehension_expression(listcomp, TypeContext::default());
if !self.deferred_state.in_string_annotation() {
self.infer_list_comprehension_expression(listcomp, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!("List comprehensions are not allowed in type expressions"),
@@ -389,7 +403,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::SetComp(setcomp) => {
self.infer_set_comprehension_expression(setcomp, TypeContext::default());
if !self.deferred_state.in_string_annotation() {
self.infer_set_comprehension_expression(setcomp, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!("Set comprehensions are not allowed in type expressions"),
@@ -398,7 +414,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Generator(generator) => {
self.infer_generator_expression(generator);
if !self.deferred_state.in_string_annotation() {
self.infer_generator_expression(generator);
}
self.report_invalid_type_expression(
expression,
format_args!("Generator expressions are not allowed in type expressions"),
@@ -407,7 +425,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Await(await_expression) => {
self.infer_await_expression(await_expression);
if !self.deferred_state.in_string_annotation() {
self.infer_await_expression(await_expression);
}
self.report_invalid_type_expression(
expression,
format_args!("`await` expressions are not allowed in type expressions"),
@@ -416,7 +436,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Yield(yield_expression) => {
self.infer_yield_expression(yield_expression);
if !self.deferred_state.in_string_annotation() {
self.infer_yield_expression(yield_expression);
}
self.report_invalid_type_expression(
expression,
format_args!("`yield` expressions are not allowed in type expressions"),
@@ -425,7 +447,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::YieldFrom(yield_from) => {
self.infer_yield_from_expression(yield_from);
if !self.deferred_state.in_string_annotation() {
self.infer_yield_from_expression(yield_from);
}
self.report_invalid_type_expression(
expression,
format_args!("`yield from` expressions are not allowed in type expressions"),
@@ -434,7 +458,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Compare(compare) => {
self.infer_compare_expression(compare);
if !self.deferred_state.in_string_annotation() {
self.infer_compare_expression(compare);
}
self.report_invalid_type_expression(
expression,
format_args!("Comparison expressions are not allowed in type expressions"),
@@ -443,7 +469,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Call(call_expr) => {
self.infer_call_expression(call_expr, TypeContext::default());
if !self.deferred_state.in_string_annotation() {
self.infer_call_expression(call_expr, TypeContext::default());
}
self.report_invalid_type_expression(
expression,
format_args!("Function calls are not allowed in type expressions"),
@@ -452,7 +480,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::FString(fstring) => {
self.infer_fstring_expression(fstring);
if !self.deferred_state.in_string_annotation() {
self.infer_fstring_expression(fstring);
}
self.report_invalid_type_expression(
expression,
format_args!("F-strings are not allowed in type expressions"),
@@ -461,7 +491,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::TString(tstring) => {
self.infer_tstring_expression(tstring);
if !self.deferred_state.in_string_annotation() {
self.infer_tstring_expression(tstring);
}
self.report_invalid_type_expression(
expression,
format_args!("T-strings are not allowed in type expressions"),
@@ -470,7 +502,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
ast::Expr::Slice(slice) => {
self.infer_slice_expression(slice);
if !self.deferred_state.in_string_annotation() {
self.infer_slice_expression(slice);
}
self.report_invalid_type_expression(
expression,
format_args!("Slices are not allowed in type expressions"),