[semantic error tests]: refactor semantic error tests to separate files (#20926)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This PR refactors semantic error tests in each seperate file


## Test Plan

<!-- How was it tested? -->

## CC
- @ntBre

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
Bhuminjay Soni 2025-10-28 02:48:11 +05:30 committed by GitHub
parent 96b60c11d9
commit 7fee62b2de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 378 additions and 466 deletions

View file

@ -0,0 +1,12 @@
async def f(): return [[x async for x in foo(n)] for n in range(3)]
async def test(): return [[x async for x in elements(n)] async for n in range(3)]
async def f(): [x for x in foo()] and [x async for x in foo()]
async def f():
def g(): ...
[x async for x in foo()]
[x async for x in y]

View file

@ -0,0 +1,3 @@
match x:
case Point(x=1, x=2):
pass

View file

@ -0,0 +1,3 @@
match x:
case {'key': 1, 'key': 2}:
pass

View file

@ -0,0 +1 @@
class C[T, T]: pass

View file

@ -0,0 +1,29 @@
def f(a):
global a
def g(a):
if True:
global a
def h(a):
def inner():
global a
def i(a):
try:
global a
except Exception:
pass
def f(a):
a = 1
global a
def f(a):
a = 1
a = 2
global a
def f(a):
class Inner:
global a # ok

View file

@ -0,0 +1,8 @@
type X[T: (yield 1)] = int
type Y = (yield 1)
def f[T](x: int) -> (y := 3): return x
class C[T]((yield from [object])):
pass

View file

@ -0,0 +1,8 @@
def func():
return *x
for *x in range(10):
pass
def func():
yield *x

View file

@ -0,0 +1,11 @@
match value:
case _:
pass
case 1:
pass
match value:
case irrefutable:
pass
case 1:
pass

View file

@ -0,0 +1,5 @@
match x:
case [a, a]:
pass
case _:
pass

View file

@ -0,0 +1 @@
[x:= 2 for x in range(2)]

View file

@ -0,0 +1 @@
*a = [1, 2, 3, 4]

View file

@ -0,0 +1,7 @@
__debug__ = False
def process(__debug__):
pass
class Generic[__debug__]:
pass

View file

@ -919,17 +919,6 @@ mod tests {
Ok(()) Ok(())
} }
/// Wrapper around `test_contents_syntax_errors` for testing a snippet of code instead of a
/// file.
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<Diagnostic> {
let contents = dedent(contents);
test_contents_syntax_errors(
&SourceKind::Python(contents.to_string()),
Path::new("<filename>"),
settings,
)
}
/// A custom test runner that prints syntax errors in addition to other diagnostics. Adapted /// A custom test runner that prints syntax errors in addition to other diagnostics. Adapted
/// from `flakes` in pyflakes/mod.rs. /// from `flakes` in pyflakes/mod.rs.
fn test_contents_syntax_errors( fn test_contents_syntax_errors(
@ -972,245 +961,38 @@ mod tests {
} }
#[test_case( #[test_case(
"async_in_sync_error_on_310", Path::new("async_comprehension_outside_async_function.py"),
"async def f(): return [[x async for x in foo(n)] for n in range(3)]", PythonVersion::PY311
PythonVersion::PY310,
"AsyncComprehensionOutsideAsyncFunction"
)] )]
#[test_case( #[test_case(
"async_in_sync_okay_on_311", Path::new("async_comprehension_outside_async_function.py"),
"async def f(): return [[x async for x in foo(n)] for n in range(3)]", PythonVersion::PY310
PythonVersion::PY311,
"AsyncComprehensionOutsideAsyncFunction"
)] )]
#[test_case( #[test_case(Path::new("rebound_comprehension.py"), PythonVersion::PY310)]
"async_in_sync_okay_on_310", #[test_case(Path::new("duplicate_type_parameter.py"), PythonVersion::PY312)]
"async def test(): return [[x async for x in elements(n)] async for n in range(3)]", #[test_case(Path::new("multiple_case_assignment.py"), PythonVersion::PY310)]
PythonVersion::PY310, #[test_case(Path::new("duplicate_match_key.py"), PythonVersion::PY310)]
"AsyncComprehensionOutsideAsyncFunction" #[test_case(Path::new("duplicate_match_class_attribute.py"), PythonVersion::PY310)]
)] #[test_case(Path::new("invalid_star_expression.py"), PythonVersion::PY310)]
#[test_case( #[test_case(Path::new("irrefutable_case_pattern.py"), PythonVersion::PY310)]
"deferred_function_body", #[test_case(Path::new("single_starred_assignment.py"), PythonVersion::PY310)]
" #[test_case(Path::new("write_to_debug.py"), PythonVersion::PY312)]
async def f(): [x for x in foo()] and [x async for x in foo()] #[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
async def f(): #[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
def g(): ... #[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
[x async for x in foo()] fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
", let snapshot = format!(
PythonVersion::PY310, "semantic_syntax_error_{}_{}",
"AsyncComprehensionOutsideAsyncFunction" path.to_string_lossy(),
)] python_version
#[test_case( );
"async_in_sync_false_positive", let path = Path::new("resources/test/fixtures/semantic_errors").join(path);
"[x async for x in y]", let contents = std::fs::read_to_string(&path)?;
PythonVersion::PY310, let source_kind = SourceKind::Python(contents);
"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"
)]
#[test_case(
"duplicate_match_key",
"
match x:
case {'key': 1, 'key': 2}:
pass
",
PythonVersion::PY310,
"DuplicateMatchKey"
)]
#[test_case(
"global_parameter",
"
def f(a):
global a
def g(a): let diagnostics = test_contents_syntax_errors(
if True: &source_kind,
global a &path,
def h(a):
def inner():
global a
def i(a):
try:
global a
except Exception:
pass
def f(a):
a = 1
global a
def f(a):
a = 1
a = 2
global a
def f(a):
class Inner:
global a # ok
",
PythonVersion::PY310,
"GlobalParameter"
)]
#[test_case(
"duplicate_match_class_attribute",
"
match x:
case Point(x=1, x=2):
pass
",
PythonVersion::PY310,
"DuplicateMatchClassAttribute"
)]
#[test_case(
"invalid_star_expression",
"
def func():
return *x
",
PythonVersion::PY310,
"InvalidStarExpression"
)]
#[test_case(
"invalid_star_expression_for",
"
for *x in range(10):
pass
",
PythonVersion::PY310,
"InvalidStarExpression"
)]
#[test_case(
"invalid_star_expression_yield",
"
def func():
yield *x
",
PythonVersion::PY310,
"InvalidStarExpression"
)]
#[test_case(
"irrefutable_case_pattern_wildcard",
"
match value:
case _:
pass
case 1:
pass
",
PythonVersion::PY310,
"IrrefutableCasePattern"
)]
#[test_case(
"irrefutable_case_pattern_capture",
"
match value:
case irrefutable:
pass
case 1:
pass
",
PythonVersion::PY310,
"IrrefutableCasePattern"
)]
#[test_case(
"single_starred_assignment",
"*a = [1, 2, 3, 4]",
PythonVersion::PY310,
"SingleStarredAssignment"
)]
#[test_case(
"write_to_debug",
"
__debug__ = False
",
PythonVersion::PY310,
"WriteToDebug"
)]
#[test_case(
"write_to_debug_in_function_param",
"
def process(__debug__):
pass
",
PythonVersion::PY310,
"WriteToDebug"
)]
#[test_case(
"write_to_debug_class_type_param",
"
class Generic[__debug__]:
pass
",
PythonVersion::PY312,
"WriteToDebug"
)]
#[test_case(
"invalid_expression_yield_in_type_param",
"
type X[T: (yield 1)] = int
",
PythonVersion::PY312,
"InvalidExpression"
)]
#[test_case(
"invalid_expression_yield_in_type_alias",
"
type Y = (yield 1)
",
PythonVersion::PY312,
"InvalidExpression"
)]
#[test_case(
"invalid_expression_walrus_in_return_annotation",
"
def f[T](x: int) -> (y := 3): return x
",
PythonVersion::PY312,
"InvalidExpression"
)]
#[test_case(
"invalid_expression_yield_from_in_base_class",
"
class C[T]((yield from [object])):
pass
",
PythonVersion::PY312,
"InvalidExpression"
)]
fn test_semantic_errors(
name: &str,
contents: &str,
python_version: PythonVersion,
error_type: &str,
) {
let snapshot = format!("semantic_syntax_error_{error_type}_{name}_{python_version}");
let diagnostics = test_snippet_syntax_errors(
contents,
&LinterSettings { &LinterSettings {
rules: settings::rule_table::RuleTable::empty(), rules: settings::rule_table::RuleTable::empty(),
unresolved_target_version: python_version.into(), unresolved_target_version: python_version.into(),
@ -1218,7 +1000,11 @@ mod tests {
..Default::default() ..Default::default()
}, },
); );
assert_diagnostics!(snapshot, diagnostics); insta::with_settings!({filters => vec![(r"\\", "/")]}, {
assert_diagnostics!(format!("{snapshot}"), diagnostics);
});
Ok(())
} }
#[test_case(PythonVersion::PY310)] #[test_case(PythonVersion::PY310)]

View file

@ -1,11 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: attribute name `x` repeated in class pattern
--> <filename>:3:21
|
2 | match x:
3 | case Point(x=1, x=2):
| ^
4 | pass
|

View file

@ -1,56 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: name `a` is parameter and global
--> <filename>:3:12
|
2 | def f(a):
3 | global a
| ^
4 |
5 | def g(a):
|
invalid-syntax: name `a` is parameter and global
--> <filename>:7:16
|
5 | def g(a):
6 | if True:
7 | global a
| ^
8 |
9 | def h(a):
|
invalid-syntax: name `a` is parameter and global
--> <filename>:15:16
|
13 | def i(a):
14 | try:
15 | global a
| ^
16 | except Exception:
17 | pass
|
invalid-syntax: name `a` is parameter and global
--> <filename>:21:12
|
19 | def f(a):
20 | a = 1
21 | global a
| ^
22 |
23 | def f(a):
|
invalid-syntax: name `a` is parameter and global
--> <filename>:26:12
|
24 | a = 1
25 | a = 2
26 | global a
| ^
27 |
28 | def f(a):
|

View file

@ -1,9 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: named expression cannot be used within a generic definition
--> <filename>:2:22
|
2 | def f[T](x: int) -> (y := 3): return x
| ^^^^^^
|

View file

@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: yield expression cannot be used within a generic definition
--> <filename>:2:13
|
2 | class C[T]((yield from [object])):
| ^^^^^^^^^^^^^^^^^^^
3 | pass
|

View file

@ -1,9 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: yield expression cannot be used within a type alias
--> <filename>:2:11
|
2 | type Y = (yield 1)
| ^^^^^^^
|

View file

@ -1,9 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: yield expression cannot be used within a TypeVar bound
--> <filename>:2:12
|
2 | type X[T: (yield 1)] = int
| ^^^^^^^
|

View file

@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: Starred expression cannot be used here
--> <filename>:3:12
|
2 | def func():
3 | return *x
| ^^
|

View file

@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: Starred expression cannot be used here
--> <filename>:2:5
|
2 | for *x in range(10):
| ^^
3 | pass
|

View file

@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: Starred expression cannot be used here
--> <filename>:3:11
|
2 | def func():
3 | yield *x
| ^^
|

View file

@ -1,12 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: name capture `irrefutable` makes remaining patterns unreachable
--> <filename>:3:10
|
2 | match value:
3 | case irrefutable:
| ^^^^^^^^^^^
4 | pass
5 | case 1:
|

View file

@ -1,12 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: wildcard makes remaining patterns unreachable
--> <filename>:3:10
|
2 | match value:
3 | case _:
| ^
4 | pass
5 | case 1:
|

View file

@ -1,12 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: multiple assignments to name `a` in pattern
--> <filename>:3:14
|
2 | match x:
3 | case [a, a]:
| ^
4 | pass
5 | case _:
|

View file

@ -1,9 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: cannot assign to `__debug__`
--> <filename>:2:1
|
2 | __debug__ = False
| ^^^^^^^^^
|

View file

@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: cannot assign to `__debug__`
--> <filename>:2:15
|
2 | class Generic[__debug__]:
| ^^^^^^^^^
3 | pass
|

View file

@ -1,10 +0,0 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: cannot assign to `__debug__`
--> <filename>:2:13
|
2 | def process(__debug__):
| ^^^^^^^^^
3 | pass
|

View file

@ -2,8 +2,10 @@
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
invalid-syntax: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11) invalid-syntax: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
--> <filename>:1:27 --> resources/test/fixtures/semantic_errors/async_comprehension_outside_async_function.py:1:27
| |
1 | async def f(): return [[x async for x in foo(n)] for n in range(3)] 1 | async def f(): return [[x async for x in foo(n)] for n in range(3)]
| ^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^
2 |
3 | async def test(): return [[x async for x in elements(n)] async for n in range(3)]
| |

View file

@ -0,0 +1,11 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: attribute name `x` repeated in class pattern
--> resources/test/fixtures/semantic_errors/duplicate_match_class_attribute.py:2:21
|
1 | match x:
2 | case Point(x=1, x=2):
| ^
3 | pass
|

View file

@ -2,10 +2,10 @@
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
invalid-syntax: mapping pattern checks duplicate key `'key'` invalid-syntax: mapping pattern checks duplicate key `'key'`
--> <filename>:3:21 --> resources/test/fixtures/semantic_errors/duplicate_match_key.py:2:21
| |
2 | match x: 1 | match x:
3 | case {'key': 1, 'key': 2}: 2 | case {'key': 1, 'key': 2}:
| ^^^^^ | ^^^^^
4 | pass 3 | pass
| |

View file

@ -2,7 +2,7 @@
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
invalid-syntax: duplicate type parameter invalid-syntax: duplicate type parameter
--> <filename>:1:12 --> resources/test/fixtures/semantic_errors/duplicate_type_parameter.py:1:12
| |
1 | class C[T, T]: pass 1 | class C[T, T]: pass
| ^ | ^

View file

@ -0,0 +1,56 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: name `a` is parameter and global
--> resources/test/fixtures/semantic_errors/global_parameter.py:2:12
|
1 | def f(a):
2 | global a
| ^
3 |
4 | def g(a):
|
invalid-syntax: name `a` is parameter and global
--> resources/test/fixtures/semantic_errors/global_parameter.py:6:16
|
4 | def g(a):
5 | if True:
6 | global a
| ^
7 |
8 | def h(a):
|
invalid-syntax: name `a` is parameter and global
--> resources/test/fixtures/semantic_errors/global_parameter.py:14:16
|
12 | def i(a):
13 | try:
14 | global a
| ^
15 | except Exception:
16 | pass
|
invalid-syntax: name `a` is parameter and global
--> resources/test/fixtures/semantic_errors/global_parameter.py:20:12
|
18 | def f(a):
19 | a = 1
20 | global a
| ^
21 |
22 | def f(a):
|
invalid-syntax: name `a` is parameter and global
--> resources/test/fixtures/semantic_errors/global_parameter.py:25:12
|
23 | a = 1
24 | a = 2
25 | global a
| ^
26 |
27 | def f(a):
|

View file

@ -0,0 +1,43 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: yield expression cannot be used within a TypeVar bound
--> resources/test/fixtures/semantic_errors/invalid_expression.py:1:12
|
1 | type X[T: (yield 1)] = int
| ^^^^^^^
2 |
3 | type Y = (yield 1)
|
invalid-syntax: yield expression cannot be used within a type alias
--> resources/test/fixtures/semantic_errors/invalid_expression.py:3:11
|
1 | type X[T: (yield 1)] = int
2 |
3 | type Y = (yield 1)
| ^^^^^^^
4 |
5 | def f[T](x: int) -> (y := 3): return x
|
invalid-syntax: named expression cannot be used within a generic definition
--> resources/test/fixtures/semantic_errors/invalid_expression.py:5:22
|
3 | type Y = (yield 1)
4 |
5 | def f[T](x: int) -> (y := 3): return x
| ^^^^^^
6 |
7 | class C[T]((yield from [object])):
|
invalid-syntax: yield expression cannot be used within a generic definition
--> resources/test/fixtures/semantic_errors/invalid_expression.py:7:13
|
5 | def f[T](x: int) -> (y := 3): return x
6 |
7 | class C[T]((yield from [object])):
| ^^^^^^^^^^^^^^^^^^^
8 | pass
|

View file

@ -0,0 +1,30 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: Starred expression cannot be used here
--> resources/test/fixtures/semantic_errors/invalid_star_expression.py:2:12
|
1 | def func():
2 | return *x
| ^^
3 |
4 | for *x in range(10):
|
invalid-syntax: Starred expression cannot be used here
--> resources/test/fixtures/semantic_errors/invalid_star_expression.py:4:5
|
2 | return *x
3 |
4 | for *x in range(10):
| ^^
5 | pass
|
invalid-syntax: Starred expression cannot be used here
--> resources/test/fixtures/semantic_errors/invalid_star_expression.py:8:11
|
7 | def func():
8 | yield *x
| ^^
|

View file

@ -0,0 +1,22 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: wildcard makes remaining patterns unreachable
--> resources/test/fixtures/semantic_errors/irrefutable_case_pattern.py:2:10
|
1 | match value:
2 | case _:
| ^
3 | pass
4 | case 1:
|
invalid-syntax: name capture `irrefutable` makes remaining patterns unreachable
--> resources/test/fixtures/semantic_errors/irrefutable_case_pattern.py:8:10
|
7 | match value:
8 | case irrefutable:
| ^^^^^^^^^^^
9 | pass
10 | case 1:
|

View file

@ -0,0 +1,12 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: multiple assignments to name `a` in pattern
--> resources/test/fixtures/semantic_errors/multiple_case_assignment.py:2:14
|
1 | match x:
2 | case [a, a]:
| ^
3 | pass
4 | case _:
|

View file

@ -2,7 +2,7 @@
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
invalid-syntax: assignment expression cannot rebind comprehension variable invalid-syntax: assignment expression cannot rebind comprehension variable
--> <filename>:1:2 --> resources/test/fixtures/semantic_errors/rebound_comprehension.py:1:2
| |
1 | [x:= 2 for x in range(2)] 1 | [x:= 2 for x in range(2)]
| ^ | ^

View file

@ -2,7 +2,7 @@
source: crates/ruff_linter/src/linter.rs source: crates/ruff_linter/src/linter.rs
--- ---
invalid-syntax: starred assignment target must be in a list or tuple invalid-syntax: starred assignment target must be in a list or tuple
--> <filename>:1:1 --> resources/test/fixtures/semantic_errors/single_starred_assignment.py:1:1
| |
1 | *a = [1, 2, 3, 4] 1 | *a = [1, 2, 3, 4]
| ^^ | ^^

View file

@ -0,0 +1,41 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: cannot assign to `__debug__`
--> resources/test/fixtures/semantic_errors/write_to_debug.py:1:1
|
1 | __debug__ = False
| ^^^^^^^^^
2 |
3 | def process(__debug__):
|
invalid-syntax: cannot assign to `__debug__`
--> resources/test/fixtures/semantic_errors/write_to_debug.py:3:13
|
1 | __debug__ = False
2 |
3 | def process(__debug__):
| ^^^^^^^^^
4 | pass
|
invalid-syntax: Cannot use type parameter lists on Python 3.10 (syntax was added in Python 3.12)
--> resources/test/fixtures/semantic_errors/write_to_debug.py:6:14
|
4 | pass
5 |
6 | class Generic[__debug__]:
| ^^^^^^^^^^^
7 | pass
|
invalid-syntax: cannot assign to `__debug__`
--> resources/test/fixtures/semantic_errors/write_to_debug.py:6:15
|
4 | pass
5 |
6 | class Generic[__debug__]:
| ^^^^^^^^^
7 | pass
|

View file

@ -0,0 +1,31 @@
---
source: crates/ruff_linter/src/linter.rs
---
invalid-syntax: cannot assign to `__debug__`
--> resources/test/fixtures/semantic_errors/write_to_debug.py:1:1
|
1 | __debug__ = False
| ^^^^^^^^^
2 |
3 | def process(__debug__):
|
invalid-syntax: cannot assign to `__debug__`
--> resources/test/fixtures/semantic_errors/write_to_debug.py:3:13
|
1 | __debug__ = False
2 |
3 | def process(__debug__):
| ^^^^^^^^^
4 | pass
|
invalid-syntax: cannot assign to `__debug__`
--> resources/test/fixtures/semantic_errors/write_to_debug.py:6:15
|
4 | pass
5 |
6 | class Generic[__debug__]:
| ^^^^^^^^^
7 | pass
|