---
generated: true
model: claude-opus-4-7
generated_at: 2026-05-20T15:00:00+00:00
source_commit: 74db89b9f77cdced9c4d0c47f377b38fffb9180b
watched_paths_digest: 4b0540420709071d4ae8e6fb49464229e1ed4854c95ebaf2ab3f36c22ad3b022
source_doc: docs/generated/design/pipeline/02-parse-ast.md
source_doc_digest: 977f6e495e28bc2ba271733427de33d6aa497d32ec2fd71b39151f0476b5887b
warning: "Auto-generated. May drift from source. Do not edit by hand."
---

# Tests for pipeline/02-parse-ast

## Intent
Tests verify the parser-stage behaviors described in
[`docs/generated/design/pipeline/02-parse-ast.md`](../../../design/pipeline/02-parse-ast.md):
two-stage parsing (decl-stage vs body-stage), syntax-as-declaration
via the `SyntaxParseInfo` table, error recovery, the major AST node
families (`Decl`, `Expr`, `Stmt`, `Type`, `Modifier`), the
`<` disambiguation strategy at the heart of `## Generics ambiguity`,
modifier and attribute parsing, and the `## Failure modes` parse-time
diagnostics. The bundle deliberately does not duplicate
`syntax-reference/tokens` (lex-surface) or
`syntax-reference/keywords-and-builtins` (keyword spellings); claims
here all hinge on something the parser does after token assembly.

Coverage strategy: at least one functional test for every named
sub-section in the source doc that yields a slangc/slangi-observable
claim. Most tests use `//TEST:INTERPRET` because parser claims are
target-independent — multi-backend repetition would only add runtime
cost. Failure-mode and error-recovery claims use
`//DIAGNOSTIC_TEST:SIMPLE` with the `non-exhaustive` argument so the
test pins the diagnostic the doc names without overcommitting to
neighbouring messages the doc does not promise.


## Functional coverage
| Claim | Intent | Anchor | Tests |
| --- | --- | --- | --- |
| On an unexpected token the parser emits a diagnostic, then uses skip-to-synchronization-point heuristics to resume, so a second later error in the same file is also reported. | negative | [#error-recovery](../../../design/pipeline/02-parse-ast.md#error-recovery) | [`error-recovery-continues-after-syntax-error.slang`](error-recovery-continues-after-syntax-error.slang) |
| Token-level errors not previously reported by the lexer (here, an `@` punctuator inside a struct declaration) surface at parse time as parse errors via the DiagnosticSink. | negative | [#failure-modes](../../../design/pipeline/02-parse-ast.md#failure-modes) | [`failure-mode-unrecognized-punctuator.slang`](failure-mode-unrecognized-punctuator.slang) |
| A generic declaration after a decl keyword is unambiguous; the parser collects the parameter list into a GenericDecl and continues with the inner declaration. | functional | [#generics-ambiguity](../../../design/pipeline/02-parse-ast.md#generics-ambiguity) | [`generic-decl-after-keyword.slang`](generic-decl-after-keyword.slang) |
| In body position the parser attempts a generic-application parse for `<...>` after an identifier and commits if it parses cleanly, so `pickMax<int>(3, 7)` is treated as a generic call. | functional | [#generics-ambiguity](../../../design/pipeline/02-parse-ast.md#generics-ambiguity) | [`generic-application-in-body.slang`](generic-application-in-body.slang) |
| When the generic-application parse does not commit cleanly the parser rolls back and parses `<` as the less-than operator, so `a < b` in a body context yields a boolean comparison Expr. | functional | [#generics-ambiguity](../../../design/pipeline/02-parse-ast.md#generics-ambiguity) | [`less-than-in-body-is-comparison.slang`](less-than-in-body-is-comparison.slang) |
| Attribute parsing is modifier parsing in disguise — the `[name(args)]` token sequence becomes an `AttributeBase` modifier attached to the declaration that follows. | functional | [#modifier-parsing](../../../design/pipeline/02-parse-ast.md#modifier-parsing) | [`attribute-bracket-form.slang`](attribute-bracket-form.slang) |
| Attribute parsing — `[name(args)]` — works wherever modifier parsing does; the `[unroll]` attribute attaches to a for-loop Stmt and the loop still executes correctly. | functional | [#modifier-parsing](../../../design/pipeline/02-parse-ast.md#modifier-parsing) | [`attribute-on-stmt.slang`](attribute-on-stmt.slang) |
| Modifier tokens collected before a decl keyword attach to the resulting Decl through the Modifier chain rooted at ModifiableSyntaxNode; `static const int K = 21;` declares a usable compile-time-known binding. | functional | [#modifier-parsing](../../../design/pipeline/02-parse-ast.md#modifier-parsing) | [`modifier-static-const-on-decl.slang`](modifier-static-const-on-decl.slang) |
| Modifier keywords like `nointerpolation` are not hard-coded in the parser but bound to syntax via the SyntaxParseInfo table (populated by populateBaseLanguageModule and the core module); the parser dispatches to the right callback by identifier lookup. | functional | [#syntax-as-declaration](../../../design/pipeline/02-parse-ast.md#syntax-as-declaration) | [`modifier-keyword-from-syntax-table.slang`](modifier-keyword-from-syntax-table.slang) |
| A `for (init; cond; update) { body }` form parses into a Stmt with three distinct sub-expressions and a body block. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`stmt-for-loop.slang`](stmt-for-loop.slang) |
| A `struct` declaration parses into a Decl node whose members are themselves Decls; field accesses through `.` resolve to the parsed member. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`decl-struct-with-fields.slang`](decl-struct-with-fields.slang) |
| A `{ ... }` block parses into a Stmt that holds an ordered sequence of sub-statements, executed in source order. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`stmt-block-sequence.slang`](stmt-block-sequence.slang) |
| Assignment is parsed as a right-associative Expr, so `a = b = c = 7` reduces as `a = (b = (c = 7))` and all three names receive the value. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`expr-assign-right-associative.slang`](expr-assign-right-associative.slang) |
| At parse time both function calls and type constructions take the unified form `A(B)` — the `## AST data model` note explicitly says expressions and types share the Expr representation in a freshly-parsed AST. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`expr-invoke-call-and-type-construction.slang`](expr-invoke-call-and-type-construction.slang) |
| Postfix `.` member access binds tightly and left-associatively, so `o.f.g + 1` parses as `((o.f).g) + 1`. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`expr-postfix-binds-tight.slang`](expr-postfix-binds-tight.slang) |
| The parser builds Expr nodes that honor operator precedence: `*` binds tighter than `+`, so `2 + 3 * 4` parses as `2 + (3 * 4)` and evaluates to 14. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`expr-precedence-mul-over-add.slang`](expr-precedence-mul-over-add.slang) |
| `extension <Type> { ... }` parses into an ExtensionDecl whose members become callable on the target type. | functional | [#the-major-node-families](../../../design/pipeline/02-parse-ast.md#the-major-node-families) | [`decl-extension-adds-method.slang`](decl-extension-adds-method.slang) |
| Because function bodies are deferred to a second parse pass that runs after decl-stage has populated the outer scope, a body can forward-reference an outer declaration that appears later in the source file. | functional | [#two-stage-parsing](../../../design/pipeline/02-parse-ast.md#two-stage-parsing) | [`body-forward-references-outer-decl.slang`](body-forward-references-outer-decl.slang) |
| Function bodies are captured at decl-stage as UnparsedStmt and re-parsed in body-parse mode, so a body that references a generic call (which depends on body-stage `<` disambiguation) still parses and runs. | functional | [#two-stage-parsing](../../../design/pipeline/02-parse-ast.md#two-stage-parsing) | [`function-body-deferred-to-check-stage.slang`](function-body-deferred-to-check-stage.slang) |
| The Type production includes the array form `T '[' Expr? ']'` (grammar.md handing off from the parse-AST doc's `## AST data model` Type family note); `int arr[3]` parses into an array-typed VarDecl. | functional | [#types](../../../design/pipeline/02-parse-ast.md#types) | [`type-array-spelling.slang`](type-array-spelling.slang) |


## Untested claims
NA


## Doc gaps observed
| Anchor | Kind | Gap | Suggested addition |
| --- | --- | --- | --- |
| [#generics-ambiguity](../../../design/pipeline/02-parse-ast.md#generics-ambiguity) | ambiguous-claim | The `## Generics ambiguity` section describes the disambiguation strategy in narrative form ("try a generic-application parse and roll back if it does not commit cleanly") but does not enumerate the **set of follower tokens** that decides commit-vs-rollback. Grammar.md's `### \`<\` disambiguation` lists them explicitly (`::`, `.`, `(`, `)`, `[`, `]`, `:`, `,`, `?`, `;`, `==`, `!=`, `>`, `>>`). The parse-AST doc should either copy that table or unambiguously hand off to it; right now a reader could not write a rollback test from the parse-AST doc alone. |  |
| [#ast-data-model](../../../design/pipeline/02-parse-ast.md#ast-data-model) | undocumented-behavior | The `## AST data model` family list mentions `Val` as a sixth family ("compile-time values used by generics") but gives no parse-stage observable that distinguishes a `Val` from a `Type`. No test anchored against `Val` here; it would need a semantics-stage handoff to make a verifiable claim. |  |
| [#two-stage-parsing](../../../design/pipeline/02-parse-ast.md#two-stage-parsing) | missing-example | `## Two-stage parsing` mentions the body parser carries a back-pointer to `SemanticsVisitor` and asks the checker whether the token before `<` resolves to a generic; the doc does not give an *observable* failure case when the heuristic is *wrong*. The recovery from a wrong guess ("parser prefers to produce *some* AST and let the checker emit a more specific error") is named in `## Failure modes` but there is no concrete example the doc promises to the user. A small example pinning the recovery behavior would let this bundle add a regression test. |  |
| [#modifier-parsing](../../../design/pipeline/02-parse-ast.md#modifier-parsing) | undocumented-behavior | `## Modifier parsing` does not enumerate which contexts accept which attributes (e.g. which attributes are valid on a `Stmt` vs a `Decl`). The bundle tests one attribute on a stmt (`[unroll]`) and one on a decl (`[ForceInline]`) but cannot make a general claim about attribute applicability without further doc detail. Could be filed against the modifier-parsing section. |  |
| [#error-recovery](../../../design/pipeline/02-parse-ast.md#error-recovery) | undocumented-behavior | `## Error recovery` says the parser uses "simple skip-to-synchronization-point heuristics (looking for `;`, `}`, or the next declaration keyword)" but does not promise that any specific input produces any specific number of diagnostics. The bundle's error-recovery test relies on the observable fact that two adjacent function bodies each report their own error; if the recovery heuristic changes to merge or suppress one of them the test will need to be updated even though the doc claim hasn't changed. Recorded as fragility; a tighter doc commitment would help. |  |
| [#ast-data-model](../../../design/pipeline/02-parse-ast.md#ast-data-model) | undocumented-behavior | `## AST data model` describes `ASTBuilder` ownership and hash- consing of types, but neither has a slangc/slangi-observable surface (hash-consing identity is hidden by the IR / emit pipelines). Recorded as architecture-only, not testable here. |  |
| [#function-and-method-bodies-are-still-unparsed-at-this-stage](../../../design/pipeline/02-parse-ast.md#function-and-method-bodies-are-still-unparsed-at-this-stage) | undocumented-behavior | The pure-decl-stage observable that the doc calls out — "Function and method bodies are still unparsed at this stage" — is hard to pin without IR-dump inspection that the agentic suite explicitly excludes (and the doc does not promise any user-visible diagnostic for it). The bundle's `function-body-deferred-to-check-stage.slang` and `body-forward-references-outer-decl.slang` cover the consequence; the cleanest direct test would need a doc-promised observable for "this body has not been parsed yet". |  |
