diff --git a/crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md b/crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md index 4c755ac6cc..e380e10018 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md +++ b/crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md @@ -130,6 +130,62 @@ async def 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 This error includes `await`, `async for`, `async with`, and `async` comprehensions. diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 1fd72b16d8..2886ea528c 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -1006,19 +1006,22 @@ mod tests { } #[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)]", - PythonVersion::PY310 + PythonVersion::PY310, + "AsyncComprehensionOutsideAsyncFunction" )] #[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)]", - PythonVersion::PY311 + PythonVersion::PY311, + "AsyncComprehensionOutsideAsyncFunction" )] #[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)]", - PythonVersion::PY310 + PythonVersion::PY310, + "AsyncComprehensionOutsideAsyncFunction" )] #[test_case( "deferred_function_body", @@ -1028,15 +1031,46 @@ mod tests { def g(): ... [x async for x in foo()] ", - PythonVersion::PY310 + PythonVersion::PY310, + "AsyncComprehensionOutsideAsyncFunction" )] - #[test_case("false_positive", "[x async for x in y]", PythonVersion::PY310)] - fn test_async_comprehension_in_sync_comprehension( + #[test_case( + "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, contents: &str, 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( contents, &LinterSettings { diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_error_on_310_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_error_on_310_3.10.snap similarity index 100% rename from crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_error_on_310_3.10.snap rename to crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_error_on_310_3.10.snap diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_deferred_function_body_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_false_positive_3.10.snap similarity index 100% rename from crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_deferred_function_body_3.10.snap rename to crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_false_positive_3.10.snap diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_okay_on_310_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_okay_on_310_3.10.snap similarity index 100% rename from crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_okay_on_310_3.10.snap rename to crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_okay_on_310_3.10.snap diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_okay_on_311_3.11.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_okay_on_311_3.11.snap similarity index 100% rename from crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_okay_on_311_3.11.snap rename to crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_async_in_sync_okay_on_311_3.11.snap diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_false_positive_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_deferred_function_body_3.10.snap similarity index 98% rename from crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_false_positive_3.10.snap rename to crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_deferred_function_body_3.10.snap index 8d0a8faf7a..4ba33c756c 100644 --- a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__async_comprehension_in_sync_comprehension_false_positive_3.10.snap +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_AsyncComprehensionOutsideAsyncFunction_deferred_function_body_3.10.snap @@ -1,3 +1,4 @@ --- source: crates/ruff_linter/src/linter.rs --- + diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_DuplicateTypeParameter_duplicate_type_param_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_DuplicateTypeParameter_duplicate_type_param_3.12.snap new file mode 100644 index 0000000000..5ede497ec6 --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_DuplicateTypeParameter_duplicate_type_param_3.12.snap @@ -0,0 +1,8 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:1:12: SyntaxError: duplicate type parameter + | +1 | class C[T, T]: pass + | ^ + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_MultipleCaseAssignment_multiple_case_assignment_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_MultipleCaseAssignment_multiple_case_assignment_3.10.snap new file mode 100644 index 0000000000..6cee83489a --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_MultipleCaseAssignment_multiple_case_assignment_3.10.snap @@ -0,0 +1,11 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:3:14: SyntaxError: multiple assignments to name `a` in pattern + | +2 | match x: +3 | case [a, a]: + | ^ +4 | pass +5 | case _: + | diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_ReboundComprehensionVariable_rebound_comprehension_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_ReboundComprehensionVariable_rebound_comprehension_3.10.snap new file mode 100644 index 0000000000..80fb65620a --- /dev/null +++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_ReboundComprehensionVariable_rebound_comprehension_3.10.snap @@ -0,0 +1,8 @@ +--- +source: crates/ruff_linter/src/linter.rs +--- +:1:2: SyntaxError: assignment expression cannot rebind comprehension variable + | +1 | [x:= 2 for x in range(2)] + | ^ + |