[ty] Emit error for invalid binary operations in type expressions (#18991)

## 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.
This commit is contained in:
med1844 2025-06-30 01:06:01 -07:00 committed by GitHub
parent 9469a982cc
commit 0ec2ad2fa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 46 additions and 2 deletions

View file

@ -91,6 +91,40 @@ async def outer(): # avoid unrelated syntax errors on yield, yield from, and aw
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

View file

@ -154,6 +154,7 @@ shouldn't panic.
```py
a: "1 or 2"
b: "(x := 1)"
# error: [invalid-type-form]
c: "1 + 2"
d: "lambda x: x"
e: "x if True else y"

View file

@ -251,7 +251,7 @@ static_assert(is_disjoint_from(Intersection[int, Any], Not[int]))
static_assert(is_disjoint_from(Not[int], Intersection[int, Any]))
# TODO https://github.com/astral-sh/ty/issues/216
static_assert(is_disjoint_from(AlwaysFalsy, LiteralString & ~Literal[""])) # error: [static-assert-error]
static_assert(is_disjoint_from(AlwaysFalsy, Intersection[LiteralString, Not[Literal[""]]])) # error: [static-assert-error]
```
## Special types

View file

@ -8416,8 +8416,17 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
UnionType::from_elements(self.db(), [left_ty, right_ty])
}
// anything else is an invalid annotation:
_ => {
op => {
self.infer_binary_expression(binary);
if let Some(mut diag) = self.report_invalid_type_expression(
expression,
format_args!(
"Invalid binary operator `{}` in type annotation",
op.as_str()
),
) {
diag.info("Did you mean to use `|`?");
}
Type::unknown()
}
}