Repository: astral-sh/ruff
Pull request #17748: [semantic-syntax-tests] `IrrefutableCasePattern`, `SingleStarredAssignment`, `WriteToDebug`, `InvalidExpression`
Author: login: maxmynter
Labels: name: testing
description: Related to testing Ruff itself
Created: 2025-04-30T21:04:54Z | Merged: 2025-05-09T18:54:05Z

Re: #17526 

## Summary

Add integration test for semantic syntax for `IrrefutableCasePattern`, `SingleStarredAssignment`, `WriteToDebug`, and `InvalidExpression`.

## Notes
- Following @ntBre's suggestion, I will keep the test coming in batches like this over the next few days in separate PRs to keep the review load per PR manageable while also not spamming too many.

- I did not add a test for `del __debug__` which is one of the examples in `crates/ruff_python_parser/src/semantic_errors.rs:1051`. 
For python version `<= 3.8` there is no error and for `>=3.9` the error is not `WriteToDebug` but `SyntaxError: cannot delete __debug__ on Python 3.9 (syntax was removed in 3.9)`.

- The `blacken-docs` bypass is necessary because otherwise the test does not pass pre-commit checks; but we want to check for this faulty syntax.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
This is a test.


Diff:
diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs
index dc8fccdf9b2f9..7427b75d9c5cb 100644
--- a/crates/ruff_linter/src/linter.rs
+++ b/crates/ruff_linter/src/linter.rs
@@ -1116,6 +1116,95 @@ mod tests {
         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,
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_walrus_in_return_annotation_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_walrus_in_return_annotation_3.12.snap
new file mode 100644
index 0000000000000..a09dda153fa57
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_walrus_in_return_annotation_3.12.snap
@@ -0,0 +1,8 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:22: SyntaxError: named expression cannot be used within a generic definition
+  |
+2 | def f[T](x: int) -> (y := 3): return x
+  |                      ^^^^^^
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.12.snap
new file mode 100644
index 0000000000000..6f71b926a1c69
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.12.snap
@@ -0,0 +1,9 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:13: SyntaxError: yield expression cannot be used within a generic definition
+  |
+2 | class C[T]((yield from [object])):
+  |             ^^^^^^^^^^^^^^^^^^^
+3 |     pass
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_alias_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_alias_3.12.snap
new file mode 100644
index 0000000000000..39c220e6a1534
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_alias_3.12.snap
@@ -0,0 +1,8 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:11: SyntaxError: yield expression cannot be used within a type alias
+  |
+2 | type Y = (yield 1)
+  |           ^^^^^^^
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_param_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_param_3.12.snap
new file mode 100644
index 0000000000000..9f0c12aaacc42
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_in_type_param_3.12.snap
@@ -0,0 +1,8 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:12: SyntaxError: yield expression cannot be used within a TypeVar bound
+  |
+2 | type X[T: (yield 1)] = int
+  |            ^^^^^^^
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_capture_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_capture_3.10.snap
new file mode 100644
index 0000000000000..502f151558ad4
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_capture_3.10.snap
@@ -0,0 +1,11 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:3:10: SyntaxError: name capture `irrefutable` makes remaining patterns unreachable
+  |
+2 | match value:
+3 |     case irrefutable:
+  |          ^^^^^^^^^^^
+4 |         pass
+5 |     case 1:
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_wildcard_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_wildcard_3.10.snap
new file mode 100644
index 0000000000000..cc54b4776b6bb
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_IrrefutableCasePattern_irrefutable_case_pattern_wildcard_3.10.snap
@@ -0,0 +1,11 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:3:10: SyntaxError: wildcard makes remaining patterns unreachable
+  |
+2 | match value:
+3 |     case _:
+  |          ^
+4 |         pass
+5 |     case 1:
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_SingleStarredAssignment_single_starred_assignment_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_SingleStarredAssignment_single_starred_assignment_3.10.snap
new file mode 100644
index 0000000000000..ecc8f955d8fff
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_SingleStarredAssignment_single_starred_assignment_3.10.snap
@@ -0,0 +1,8 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:1:1: SyntaxError: starred assignment target must be in a list or tuple
+  |
+1 | *a = [1, 2, 3, 4]
+  | ^^
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_3.10.snap
new file mode 100644
index 0000000000000..baebbdf41f123
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_3.10.snap
@@ -0,0 +1,8 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:1: SyntaxError: cannot assign to `__debug__`
+  |
+2 | __debug__ = False
+  | ^^^^^^^^^
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_class_type_param_3.12.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_class_type_param_3.12.snap
new file mode 100644
index 0000000000000..44139dc6cf0cb
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_class_type_param_3.12.snap
@@ -0,0 +1,9 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:15: SyntaxError: cannot assign to `__debug__`
+  |
+2 | class Generic[__debug__]:
+  |               ^^^^^^^^^
+3 |     pass
+  |
diff --git a/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_in_function_param_3.10.snap b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_in_function_param_3.10.snap
new file mode 100644
index 0000000000000..935ad560543ce
--- /dev/null
+++ b/crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_WriteToDebug_write_to_debug_in_function_param_3.10.snap
@@ -0,0 +1,9 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:13: SyntaxError: cannot assign to `__debug__`
+  |
+2 | def process(__debug__):
+  |             ^^^^^^^^^
+3 |     pass
+  |
diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
index 7063c58dda1cd..840023f38dcfe 100644
--- a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
+++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
@@ -228,11 +228,102 @@ for x in *range(10):
     pass
 ```
 
+## Irrefutable case pattern
+
+Irrefutable patterns, i.e. wildcard or capture patterns, must be the last case in a match statement.
+Following case statements are unreachable.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```py
+value = 5
+
+match value:
+    # error: [invalid-syntax] "wildcard makes remaining patterns unreachable"
+    case _:  # Irrefutable wildcard pattern
+        pass
+    case 5:
+        pass
+
+match value:
+    # error: [invalid-syntax] "name capture `variable` makes remaining patterns unreachable"
+    case variable:  # Irrefutable capture pattern
+        pass
+    case 10:
+        pass
+```
+
+## Single starred assignment
+
+Starred assignment targets cannot appear by themselves. They must be in the context of a list or
+tuple.
+
+```py
+# error: [invalid-syntax] "starred assignment target must be in a list or tuple"
+*a = [1, 2, 3, 4]
+```
+
+## Write to debug
+
+The special Python builtin `__debug__` should not be modified.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```py
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+__debug__ = False
+
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+def process(__debug__):
+    pass
+
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+class Generic[__debug__]:
+    pass
+```
+
+## Invalid expression
+
+Certain expressions like `yield` or inlined walrus assignments are not valid in specific contexts.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```py
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
+# error: [invalid-syntax] "`yield` statement outside of a function"
+type X[T: (yield 1)] = int
+
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a type alias"
+# error: [invalid-syntax] "`yield` statement outside of a function"
+type Y = (yield 1)
+
+# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
+# error: [invalid-syntax] "named expression cannot be used within a generic definition"
+def f[T](x: int) -> (y := 3):
+    return x
+
+# error: [invalid-syntax] "`yield from` statement outside of a function"
+# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
+class C[T]((yield from [object])):
+    pass
+```
+
 ## `await` outside async function
 
 This error includes `await`, `async for`, `async with`, and `async` comprehensions.
 
-```python
+```py
 async def elements(n):
     yield n
 
@@ -245,7 +336,6 @@ def _():
     # error: [invalid-syntax] "`async with` outside of an asynchronous function"
     async with elements(1) as x:
         ...
-    # error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)"
     # error: [invalid-syntax] "asynchronous comprehension outside of an asynchronous function"
     [x async for x in elements(1)]
 ```


Reviews:
user: login: carljm
body: red-knot tests look good
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T21:19:00Z
user: login: ntBre
body: Thanks! I had one suggestion about the `yield from` cases and a nit you can ignore if you prefer it this way.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T15:47:34Z
user: login: maxmynter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T19:08:33Z
user: login: ntBre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T15:00:48Z
user: login: ntBre
body: Thanks! I was just going to push the small tweak to the parens in the ruff case, but then I noticed the mdtest code block issue. Could you update those? Then this should be ready to go.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T18:52:20Z
user: login: maxmynter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:41:20Z
user: login: maxmynter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:45:57Z
user: login: maxmynter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T22:54:24Z
user: login: ntBre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:05:23Z
user: login: ntBre
body: Thanks again! The new errors prompted one question, mostly for Alex or someone else on the ty team. I also had one suggestion about the "outside of a function" errors, but this is just about ready to go.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:14:54Z
user: login: maxmynter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T16:40:52Z
user: login: ntBre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T17:22:22Z
user: login: ntBre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T17:45:14Z
user: login: ntBre
body: Thanks! And thanks for all of your help on #17526, I'll close that now.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T17:48:53Z
user: login: ntBre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T18:00:56Z

Review comments:
diff_hunk: @@ -186,6 +186,97 @@ def f[X, Y, X]():
     pass
 ```
 
+## Irrefutable case pattern
+
+Irrefutable patterns, i.e. wildcard or capture patterns, must be the last case in a match statement.
+Following case statements are unreachable.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```py
+value = 5
+
+match value:
+    # error: [invalid-syntax] "wildcard makes remaining patterns unreachable"
+    case _:  # Irrefutable wildcard pattern
+        pass
+    case 5:
+        pass
+
+match value:
+    # error: [invalid-syntax] "name capture `variable` makes remaining patterns unreachable"
+    case variable:  # Irrefutable capture pattern
+        pass
+    case 10:
+        pass
+```
+
+## Single starred assignment
+
+Starred assignment targets cannot appear by themselves. They must be in the context of a list or
+tuple.
+
+```py
+# error: [invalid-syntax] "starred assignment target must be in a list or tuple"
+*a = [1, 2, 3, 4]
+```
+
+## Write to debug
+
+The special Python builtin `__debug__` should not be modified.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```py
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+__debug__ = False
+
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+def process(__debug__):
+    pass
+
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+class Generic[__debug__]:
+    pass
+```
+
+## Invalid expression
+
+Certain expressions like `yield` or inlined walrus assignments are not valid in specific contexts.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```python
+# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+type X[T: (yield 1)] = int
+
+# error: [invalid-syntax] "yield expression not allowed in type alias"
+type Y = (yield 1)
+
+# error: [invalid-syntax] "named expression not allowed in return annotation"
+def f[T](x: int) -> (y := 3):
+    return x
+```
+
+<!-- blacken-docs:off -->
+
+```python
+# error: [invalid-syntax] "yield from expression not allowed in base class list"
+class C(yield from [object]):
path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: I think if you parenthesize the `yield from` you can get by without the `blacken-docs:off`:

```suggestion
class C((yield from [object])):
```

Without the parens, it's actually a parse error in CPython, rather than a semantic/compiler error. I'm surprised we don't emit an error from the parser too.

```pycon
>>> import ast
>>> ast.parse("class C(yield from [object]): ...")
Traceback (most recent call last):
  File "<python-input-3>", line 1, in <module>
    ast.parse("class C(yield from [object]): ...")
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/ast.py", line 54, in parse
    return compile(source, filename, mode, flags,
                   _feature_version=feature_version, optimize=optimize)
  File "<unknown>", line 1
    class C(yield from [object]): ...
            ^^^^^
SyntaxError: invalid syntax
>>> ast.parse("class C((yield from [object])): ...")
<ast.Module object at 0x70a940be1c90>
```
created_at: 2025-05-01T15:43:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1064,6 +1064,97 @@ mod tests {
         PythonVersion::PY310,
         "MultipleCaseAssignment"
     )]
+    #[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]
+        ",
path: crates/ruff_linter/src/linter.rs
user: login: ntBre
body: nit: I'd probably put these on one line if they fit, but it's not a big deal either way.

```suggestion
        "*a = [1, 2, 3, 4]",
```
created_at: 2025-05-01T15:44:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:9: SyntaxError: Yield expression cannot be used here
+  |
+2 | class C(yield from [object]):
+  |         ^
+3 |     pass
path: crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.10.snap
user: login: ntBre
body: Ah okay, we do emit a parse error for this. This one needs parens too to get the semantic error instead.
created_at: 2025-05-01T15:46:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:9: SyntaxError: Yield expression cannot be used here
+  |
+2 | class C(yield from [object]):
+  |         ^
+3 |     pass
path: crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.10.snap
user: login: maxmynter
body: with 

```py
class C((yield from [object])):
    pass
```
results in an empty snapshot for the associated test. 

Am I doing something wrong, is there an issue with the implementation, or is this desired? 
created_at: 2025-05-02T19:08:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+---
+source: crates/ruff_linter/src/linter.rs
+---
+<filename>:2:9: SyntaxError: Yield expression cannot be used here
+  |
+2 | class C(yield from [object]):
+  |         ^
+3 |     pass
path: crates/ruff_linter/src/snapshots/ruff_linter__linter__tests__semantic_syntax_error_InvalidExpression_invalid_expression_yield_from_in_base_class_3.10.snap
user: login: ntBre
body: Oh, this is actually allowed in CPython with the parens (at least if you enclose it in a function to avoid _that_ error):

```pycon
>>> class C((yield from [object])):
...     pass
...
  File "<python-input-2>", line 1
    class C((yield from [object])):
             ^^^^^^^^^^^^^^^^^^^
SyntaxError: 'yield from' outside function
>>> class C((yield from [object])):
...     pass
KeyboardInterrupt
>>> def f():
...     class C((yield from [object])): ...
...
>>>
```

So I guess I would say just remove this test case, or make it generic, which _should_ cause an error:

```pycon
>>> def f():
...     class C[T]((yield from [object])): ...
...
  File "<python-input-4>", line 2
    class C[T]((yield from [object])): ...
                ^^^^^^^^^^^^^^^^^^^
SyntaxError: yield expression cannot be used within the definition of a generic
```
created_at: 2025-05-05T15:00:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1116,6 +1116,95 @@ mod tests {
         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]):
path: crates/ruff_linter/src/linter.rs
user: login: ntBre
body: ```suggestion
        class C[T]((yield from [object])):
```
created_at: 2025-05-07T18:48:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -228,6 +228,93 @@ for x in *range(10):
     pass
 ```
 
+## Irrefutable case pattern
+
+Irrefutable patterns, i.e. wildcard or capture patterns, must be the last case in a match statement.
+Following case statements are unreachable.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```py
+value = 5
+
+match value:
+    # error: [invalid-syntax] "wildcard makes remaining patterns unreachable"
+    case _:  # Irrefutable wildcard pattern
+        pass
+    case 5:
+        pass
+
+match value:
+    # error: [invalid-syntax] "name capture `variable` makes remaining patterns unreachable"
+    case variable:  # Irrefutable capture pattern
+        pass
+    case 10:
+        pass
+```
+
+## Single starred assignment
+
+Starred assignment targets cannot appear by themselves. They must be in the context of a list or
+tuple.
+
+```py
+# error: [invalid-syntax] "starred assignment target must be in a list or tuple"
+*a = [1, 2, 3, 4]
+```
+
+## Write to debug
+
+The special Python builtin `__debug__` should not be modified.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```py
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+__debug__ = False
+
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+def process(__debug__):
+    pass
+
+# error: [invalid-syntax] "cannot assign to `__debug__`"
+class Generic[__debug__]:
+    pass
+```
+
+## Invalid expression
+
+Certain expressions like `yield` or inlined walrus assignments are not valid in specific contexts.
+
+```toml
+[environment]
+python-version = "3.12"
+```
+
+```python
+# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+type X[T: (yield 1)] = int
+
+# error: [invalid-syntax] "yield expression not allowed in type alias"
+type Y = (yield 1)
+
+# error: [invalid-syntax] "named expression not allowed in return annotation"
+def f[T](x: int) -> (y := 3):
+    return x
+```
+
+```python
+# error: [invalid-syntax] "yield from expression not allowed in base class list"
+class C[T]((yield from [object])):
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: Sorry for not noticing this before, but these need to be `py` not `python` blocks or the mdtests don't actually run. The same is true for the `await` outside async function block below. I think those are the only 3 places, though. I was alerted to this because the error message should be `yield expression cannot be used within a generic definition`. I didn't explicitly handle `yield from` for the message, although I probably should have!
created_at: 2025-05-07T18:51:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1116,6 +1116,95 @@ mod tests {
         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]):
path: crates/ruff_linter/src/linter.rs
user: login: maxmynter
body: Damn, I must have undone this somehow during rebasing; because I remember having added these. Re-added in [94d6aee](https://github.com/astral-sh/ruff/pull/17748/commits/94d6aee4ddd515320befb8e855b954ee1c4a17c9)
created_at: 2025-05-07T22:41:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -332,7 +343,6 @@ def _():
     # error: [invalid-syntax] "`async with` outside of an asynchronous function"
     async with elements(1) as x:
         ...
-    # error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: maxmynter
body: This one was not thrown after the `python` -> `py` change. Is that a reason for concern @ntBre? Line was added in [e7f38fe](https://github.com/astral-sh/ruff/commit/e7f38fe74ba642cbd4cf286d8524f4172cbe30e2)
created_at: 2025-05-07T22:45:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -332,7 +343,6 @@ def _():
     # error: [invalid-syntax] "`async with` outside of an asynchronous function"
     async with elements(1) as x:
         ...
-    # error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: maxmynter
body: A couple other errors were thrown due to this change. I adapted the test cases because they seem reasonanle. Let me know if you disagree.
created_at: 2025-05-07T22:54:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -332,7 +343,6 @@ def _():
     # error: [invalid-syntax] "`async with` outside of an asynchronous function"
     async with elements(1) as x:
         ...
-    # error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: Wow one of the `python` blocks was mine! Thanks for the fix.

And yes, that error should not be thrown. I fixed it in a different PR and mentioned it on the PR adding this case [here](https://github.com/astral-sh/ruff/pull/17463#discussion_r2050785521).
created_at: 2025-05-08T14:05:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -297,20 +297,31 @@ Certain expressions like `yield` or inlined walrus assignments are not valid in
 python-version = "3.12"
 ```
 
-```python
-# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+```py
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
+# error: [invalid-syntax] "`yield` statement outside of a function"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: nit: I'd probably just wrap this in an outer function to suppress these `outside of a function` errors since we only intend to test the other errors here. I think a lot of other mdtests use a function named `_` to indicate this:

```py
def _():
    type X[T: (yield 1)] = int
```

but it's not a huge deal.
created_at: 2025-05-08T14:09:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -297,20 +297,31 @@ Certain expressions like `yield` or inlined walrus assignments are not valid in
 python-version = "3.12"
 ```
 
-```python
-# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+```py
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
+# error: [invalid-syntax] "`yield` statement outside of a function"
 type X[T: (yield 1)] = int
 
-# error: [invalid-syntax] "yield expression not allowed in type alias"
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a type alias"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: This doesn't necessarily need to block this PR since you're just reporting the current state of things, but we might need to open a follow-up issue here to avoid duplicate diagnostics. @AlexWaygood do you know if the type error is separate from the syntax error here, or should we only pick one of these to emit?
created_at: 2025-05-08T14:10:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -297,20 +297,31 @@ Certain expressions like `yield` or inlined walrus assignments are not valid in
 python-version = "3.12"
 ```
 
-```python
-# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+```py
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
+# error: [invalid-syntax] "`yield` statement outside of a function"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: maxmynter
body: I think there is still something here. With 
```py
def _():
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
    type X[T: (yield 1)] = int

def _():
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
    type Y = (yield 1)
```

I still get the outside of a function errors

```bash
semantic_syntax_errors.md - Semantic syntax error diagnostics - Invalid expression

  crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md:304 unexpected error: 16 [invalid-syntax] "`yield` statement outside of a function"
  crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md:309 unexpected error: 15 [invalid-syntax] "`yield` statement outside of a function"
```
created_at: 2025-05-09T16:40:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -297,20 +297,31 @@ Certain expressions like `yield` or inlined walrus assignments are not valid in
 python-version = "3.12"
 ```
 
-```python
-# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+```py
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
+# error: [invalid-syntax] "`yield` statement outside of a function"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: Ah, that's a bug, thank you! We can merge this as is, and I'll tackle it in a follow-up.

If you're curious, ty has separate scope kinds for type aliases and annotations, so the check for function scope needs to skip those instead of just checking that the immediate scope is a function. At least that looks like the easy fix to me.
created_at: 2025-05-09T17:22:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -297,20 +297,31 @@ Certain expressions like `yield` or inlined walrus assignments are not valid in
 python-version = "3.12"
 ```
 
-```python
-# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+```py
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
+# error: [invalid-syntax] "`yield` statement outside of a function"
 type X[T: (yield 1)] = int
 
-# error: [invalid-syntax] "yield expression not allowed in type alias"
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a type alias"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: I talked to Alex, and this is a symptom of a more general need to suppress type-checking errors when there are syntax errors present. I'll spin off an issue for this too, so no worries here.
created_at: 2025-05-09T17:45:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -297,20 +297,31 @@ Certain expressions like `yield` or inlined walrus assignments are not valid in
 python-version = "3.12"
 ```
 
-```python
-# error: [invalid-syntax] "yield expression not allowed in type parameter bounds"
+```py
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
+# error: [invalid-syntax] "`yield` statement outside of a function"
 type X[T: (yield 1)] = int
 
-# error: [invalid-syntax] "yield expression not allowed in type alias"
+# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
+# error: [invalid-syntax] "yield expression cannot be used within a type alias"
path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
user: login: ntBre
body: The issue already exists in the ty repo! https://github.com/astral-sh/ty/issues/119
created_at: 2025-05-09T18:00:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-30T21:07:39Z
body: <!-- generated-comment mypy_primer -->
## `mypy_primer` results
No ecosystem changes detected ✅

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-30T21:28:23Z
body: <!-- generated-comment ecosystem -->
## `ruff-ecosystem` results
### Linter (stable)
✅ ecosystem check detected no linter changes.

### Linter (preview)
✅ ecosystem check detected no linter changes.



author_association: CONTRIBUTOR
user: login: maxmynter
created_at: 2025-05-06T22:01:12Z
body: I went with the generic type parameter and pinned the necessary Python version, `3.12`. Also rebased on top of main and resolved merge conflicts that came from the other PRs adding tests. 
author_association: CONTRIBUTOR
user: login: maxmynter
created_at: 2025-05-06T22:23:05Z
body: If this is good, it also closes #17526,  I guess. 
author_association: CONTRIBUTOR
user: login: maxmynter
created_at: 2025-05-09T16:41:43Z
body: One duplicate test case had slipped in, which i removed in [96aeedc](https://github.com/astral-sh/ruff/pull/17748/commits/96aeedc4b442acc632aa69c39f3403559d2516ed).
author_association: CONTRIBUTOR