mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-09 18:02:19 +00:00

## Summary This PR adds diagnostic for invalid binary operators in type expressions. It should close https://github.com/astral-sh/ty/issues/706 if merged. Please feel free to suggest better wordings for the diagnostic message. ## Test Plan I modified `mdtest/annotations/invalid.md` and added a test for each binary operator, and fixed tests that was broken by the new diagnostic.
9.3 KiB
9.3 KiB
Tests for invalid types in type expressions
Invalid types are rejected
Many types are illegal in the context of a type expression:
import typing
from ty_extensions import AlwaysTruthy, AlwaysFalsy
from typing_extensions import Literal, Never
class A: ...
def _(
a: type[int],
b: AlwaysTruthy,
c: AlwaysFalsy,
d: Literal[True],
e: Literal["bar"],
f: Literal[b"foo"],
g: tuple[int, str],
h: Never,
i: int,
j: A,
):
def foo(): ...
def invalid(
a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression"
b_: b, # error: [invalid-type-form]
c_: c, # error: [invalid-type-form]
d_: d, # error: [invalid-type-form]
e_: e, # error: [invalid-type-form]
f_: f, # error: [invalid-type-form]
g_: g, # error: [invalid-type-form]
h_: h, # error: [invalid-type-form]
i_: typing, # error: [invalid-type-form]
j_: foo, # error: [invalid-type-form]
k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a type expression"
l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a type expression"
):
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
Invalid AST nodes
def bar() -> None:
return None
async def outer(): # avoid unrelated syntax errors on yield, yield from, 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: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
):
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: Unknown
reveal_type(e) # revealed: int | 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(p) # revealed: Unknown
reveal_type(q) # revealed: int | Unknown
reveal_type(r) # revealed: @Todo(unknown type subscript)
class Mat:
def __init__(self, value: int):
self.value = value
def __matmul__(self, other) -> int:
return 42
def invalid_binary_operators(
a: "1" + "2", # error: [invalid-type-form] "Invalid binary operator `+` in type annotation"
b: 3 - 5.0, # error: [invalid-type-form] "Invalid binary operator `-` in type annotation"
c: 4 * -2, # error: [invalid-type-form] "Invalid binary operator `*` in type annotation"
d: Mat(4) @ Mat(2), # error: [invalid-type-form] "Invalid binary operator `@` in type annotation"
e: 10 / 2, # error: [invalid-type-form] "Invalid binary operator `/` in type annotation"
f: 10 % 3, # error: [invalid-type-form] "Invalid binary operator `%` in type annotation"
g: 2**-0.5, # error: [invalid-type-form] "Invalid binary operator `**` in type annotation"
h: 10 // 3, # error: [invalid-type-form] "Invalid binary operator `//` in type annotation"
i: 1 << 2, # error: [invalid-type-form] "Invalid binary operator `<<` in type annotation"
j: 4 >> 42, # error: [invalid-type-form] "Invalid binary operator `>>` in type annotation"
k: 5 ^ 3, # error: [invalid-type-form] "Invalid binary operator `^` in type annotation"
l: 5 & 3, # error: [invalid-type-form] "Invalid binary operator `&` in type annotation"
):
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
Invalid Collection based AST nodes
[environment]
python-version = "3.12"
def _(
a: {1: 2}, # error: [invalid-type-form] "Dict literals are not allowed in type expressions"
b: {1, 2}, # error: [invalid-type-form] "Set literals are not allowed in type expressions"
c: {k: v for k, v in [(1, 2)]}, # error: [invalid-type-form] "Dict comprehensions are not allowed in type expressions"
d: [k for k in [1, 2]], # error: [invalid-type-form] "List comprehensions are not allowed in type expressions"
e: {k for k in [1, 2]}, # error: [invalid-type-form] "Set comprehensions are not allowed in type expressions"
f: (k for k in [1, 2]), # error: [invalid-type-form] "Generator expressions are not allowed in type expressions"
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?"
g: [int, str],
# error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?"
h: (int, str),
i: (), # error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?"
):
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
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `list[int]`?"
class name_0[name_2: [int]]:
pass
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
# error: [invalid-type-form] "Dict literals are not allowed in type expressions"
class name_4[name_1: [{}]]:
pass
Diagnostics for common errors
Module-literal used when you meant to use a class from that module
It's pretty common in Python to accidentally use a module-literal type in a type expression when you meant to use a class by the same name that comes from that module. We emit a nice subdiagnostic for this case:
foo.py
:
import datetime
def f(x: datetime): ... # error: [invalid-type-form]
PIL/Image.py
:
class Image: ...
bar.py
:
from PIL import Image
def g(x: Image): ... # error: [invalid-type-form]
List-literal used when you meant to use a list or tuple
def _(
x: [int], # error: [invalid-type-form]
) -> [int]: # error: [invalid-type-form]
return x
def _(
x: [int, str], # error: [invalid-type-form]
) -> [int, str]: # error: [invalid-type-form]
return x
Tuple-literal used when you meant to use a tuple
def _(
x: (), # error: [invalid-type-form]
) -> (): # error: [invalid-type-form]
return x
def _(
x: (int,), # error: [invalid-type-form]
) -> (int,): # error: [invalid-type-form]
return x
def _(
x: (int, str), # error: [invalid-type-form]
) -> (int, str): # error: [invalid-type-form]
return x