mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 14:52:01 +00:00
Expand Semantic Syntax Coverage (#17725)
Re: #17526 ## Summary Adds tests to red knot and `linter.rs` for the semantic syntax. Specifically add tests for `ReboundComprehensionVariable`, `DuplicateTypeParameter`, and `MultipleCaseAssignment`. Refactor the `test_async_comprehension_in_sync_comprehension` → `test_semantic_error` to be more general for all semantic syntax test cases. ## Test Plan This is a test. ## Question I'm happy to contribute more tests the coming days. Should that happen here or should we merge this PR such that the refactor `test_async_comprehension_in_sync_comprehension` → `test_semantic_error` is available on main and others can chime in, too?
This commit is contained in:
parent
ad1a8da4d1
commit
f584b66824
10 changed files with 128 additions and 10 deletions
|
@ -130,6 +130,62 @@ async def g():
|
||||||
(x async for x in g())
|
(x async for x in g())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Rebound comprehension variable
|
||||||
|
|
||||||
|
Walrus operators cannot rebind variables already in use as iterators:
|
||||||
|
|
||||||
|
```py
|
||||||
|
# error: [invalid-syntax] "assignment expression cannot rebind comprehension variable"
|
||||||
|
[x := 2 for x in range(10)]
|
||||||
|
|
||||||
|
# error: [invalid-syntax] "assignment expression cannot rebind comprehension variable"
|
||||||
|
{y := 5 for y in range(10)}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple case assignments
|
||||||
|
|
||||||
|
Variable names in pattern matching must be unique within a single pattern:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.10"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
x = [1, 2]
|
||||||
|
match x:
|
||||||
|
# error: [invalid-syntax] "multiple assignments to name `a` in pattern"
|
||||||
|
case [a, a]:
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
|
||||||
|
d = {"key": "value"}
|
||||||
|
match d:
|
||||||
|
# error: [invalid-syntax] "multiple assignments to name `b` in pattern"
|
||||||
|
case {"key": b, "other": b}:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
## Duplicate type parameter
|
||||||
|
|
||||||
|
Type parameter names must be unique in a generic class or function definition:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
# error: [invalid-syntax] "duplicate type parameter"
|
||||||
|
class C[T, T]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# error: [invalid-syntax] "duplicate type parameter"
|
||||||
|
def f[X, Y, X]():
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
## `await` outside async function
|
## `await` outside async function
|
||||||
|
|
||||||
This error includes `await`, `async for`, `async with`, and `async` comprehensions.
|
This error includes `await`, `async for`, `async with`, and `async` comprehensions.
|
||||||
|
|
|
@ -1006,19 +1006,22 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(
|
#[test_case(
|
||||||
"error_on_310",
|
"async_in_sync_error_on_310",
|
||||||
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
|
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
|
||||||
PythonVersion::PY310
|
PythonVersion::PY310,
|
||||||
|
"AsyncComprehensionOutsideAsyncFunction"
|
||||||
)]
|
)]
|
||||||
#[test_case(
|
#[test_case(
|
||||||
"okay_on_311",
|
"async_in_sync_okay_on_311",
|
||||||
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
|
"async def f(): return [[x async for x in foo(n)] for n in range(3)]",
|
||||||
PythonVersion::PY311
|
PythonVersion::PY311,
|
||||||
|
"AsyncComprehensionOutsideAsyncFunction"
|
||||||
)]
|
)]
|
||||||
#[test_case(
|
#[test_case(
|
||||||
"okay_on_310",
|
"async_in_sync_okay_on_310",
|
||||||
"async def test(): return [[x async for x in elements(n)] async for n in range(3)]",
|
"async def test(): return [[x async for x in elements(n)] async for n in range(3)]",
|
||||||
PythonVersion::PY310
|
PythonVersion::PY310,
|
||||||
|
"AsyncComprehensionOutsideAsyncFunction"
|
||||||
)]
|
)]
|
||||||
#[test_case(
|
#[test_case(
|
||||||
"deferred_function_body",
|
"deferred_function_body",
|
||||||
|
@ -1028,15 +1031,46 @@ mod tests {
|
||||||
def g(): ...
|
def g(): ...
|
||||||
[x async for x in foo()]
|
[x async for x in foo()]
|
||||||
",
|
",
|
||||||
PythonVersion::PY310
|
PythonVersion::PY310,
|
||||||
|
"AsyncComprehensionOutsideAsyncFunction"
|
||||||
)]
|
)]
|
||||||
#[test_case("false_positive", "[x async for x in y]", PythonVersion::PY310)]
|
#[test_case(
|
||||||
fn test_async_comprehension_in_sync_comprehension(
|
"async_in_sync_false_positive",
|
||||||
|
"[x async for x in y]",
|
||||||
|
PythonVersion::PY310,
|
||||||
|
"AsyncComprehensionOutsideAsyncFunction"
|
||||||
|
)]
|
||||||
|
#[test_case(
|
||||||
|
"rebound_comprehension",
|
||||||
|
"[x:= 2 for x in range(2)]",
|
||||||
|
PythonVersion::PY310,
|
||||||
|
"ReboundComprehensionVariable"
|
||||||
|
)]
|
||||||
|
#[test_case(
|
||||||
|
"duplicate_type_param",
|
||||||
|
"class C[T, T]: pass",
|
||||||
|
PythonVersion::PY312,
|
||||||
|
"DuplicateTypeParameter"
|
||||||
|
)]
|
||||||
|
#[test_case(
|
||||||
|
"multiple_case_assignment",
|
||||||
|
"
|
||||||
|
match x:
|
||||||
|
case [a, a]:
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
",
|
||||||
|
PythonVersion::PY310,
|
||||||
|
"MultipleCaseAssignment"
|
||||||
|
)]
|
||||||
|
fn test_semantic_errors(
|
||||||
name: &str,
|
name: &str,
|
||||||
contents: &str,
|
contents: &str,
|
||||||
python_version: PythonVersion,
|
python_version: PythonVersion,
|
||||||
|
error_type: &str,
|
||||||
) {
|
) {
|
||||||
let snapshot = format!("async_comprehension_in_sync_comprehension_{name}_{python_version}");
|
let snapshot = format!("semantic_syntax_error_{error_type}_{name}_{python_version}");
|
||||||
let messages = test_snippet_syntax_errors(
|
let messages = test_snippet_syntax_errors(
|
||||||
contents,
|
contents,
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/linter.rs
|
source: crates/ruff_linter/src/linter.rs
|
||||||
---
|
---
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/linter.rs
|
||||||
|
---
|
||||||
|
<filename>:1:12: SyntaxError: duplicate type parameter
|
||||||
|
|
|
||||||
|
1 | class C[T, T]: pass
|
||||||
|
| ^
|
||||||
|
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/linter.rs
|
||||||
|
---
|
||||||
|
<filename>:3:14: SyntaxError: multiple assignments to name `a` in pattern
|
||||||
|
|
|
||||||
|
2 | match x:
|
||||||
|
3 | case [a, a]:
|
||||||
|
| ^
|
||||||
|
4 | pass
|
||||||
|
5 | case _:
|
||||||
|
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/linter.rs
|
||||||
|
---
|
||||||
|
<filename>:1:2: SyntaxError: assignment expression cannot rebind comprehension variable
|
||||||
|
|
|
||||||
|
1 | [x:= 2 for x in range(2)]
|
||||||
|
| ^
|
||||||
|
|
|
Loading…
Add table
Add a link
Reference in a new issue