Skip to content

Improve parser error recovery for invalid UnaryOp nodes inside unpacking assignments#24697

Draft
AlexWaygood wants to merge 1 commit intomainfrom
alex/invalid-unaryop-target
Draft

Improve parser error recovery for invalid UnaryOp nodes inside unpacking assignments#24697
AlexWaygood wants to merge 1 commit intomainfrom
alex/invalid-unaryop-target

Conversation

@AlexWaygood
Copy link
Copy Markdown
Member

Root-cause fix. helpers::set_expr_ctx was propagating Store (or Del)
through UnaryOp's operand when fixing up the ctx on a parsed assignment
target. But UnaryOp is never a valid assignment or deletion target in
Python, so propagating the target ctx into its operand told a lie: an
empty or otherwise-unrelated Name inside would then claim to be a
binding site.

That lie was the root of the LSP panic reported in astral-sh/ty#3283:
semantic indexing trusted the ctx and created a phantom binding; the
unpacker ignored UnaryOp in its _ => arm and never recorded a type;
binding_type on the phantom binding then hit expression_type() with
no entry and panicked.

Marking the operand Invalid aligns the two walkers: semantic indexing's
existing (Invalid, _) => (false, false) arm skips binding creation, so
there is no phantom binding for the unpacker to fail to cover.

Parser snapshots for three already-failing invalid-syntax fixtures are
updated to reflect the ctx change on the (rejected) recovery AST. Three
LSP e2e tests — two existing and one new completion-triggering test for
a, not x = ... — cover the regression.

Summary

Test Plan

…lid`

Root-cause fix. `helpers::set_expr_ctx` was propagating `Store` (or `Del`)
through `UnaryOp`'s operand when fixing up the ctx on a parsed assignment
target. But `UnaryOp` is never a valid assignment or deletion target in
Python, so propagating the target ctx into its operand told a lie: an
empty or otherwise-unrelated `Name` inside would then claim to be a
binding site.

That lie was the root of the LSP panic reported in astral-sh/ty#3283:
semantic indexing trusted the ctx and created a phantom binding; the
unpacker ignored `UnaryOp` in its `_ =>` arm and never recorded a type;
`binding_type` on the phantom binding then hit `expression_type()` with
no entry and panicked.

Marking the operand `Invalid` aligns the two walkers: semantic indexing's
existing `(Invalid, _) => (false, false)` arm skips binding creation, so
there is no phantom binding for the unpacker to fail to cover.

Parser snapshots for three already-failing invalid-syntax fixtures are
updated to reflect the ctx change on the (rejected) recovery AST. Three
LSP e2e tests — two existing and one new completion-triggering test for
`a, not x = ...` — cover the regression.
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented Apr 17, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant