Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ea26c19
fix(#1290): a parameter that shadows a file-scope name moves like a l…
devin-ai-integration[bot] Oct 11, 2026
f785d4a
test(#1290): regenerate param-prefix-naming-valid
devin-ai-integration[bot] Oct 11, 2026
e6d447e
fix(#1290 review): one C-name decision for a bound parameter; root me…
devin-ai-integration[bot] Oct 11, 2026
a61ed85
fix(#1290 review): drop the source name ArgumentGenerator no longer r…
devin-ai-integration[bot] Oct 11, 2026
ee2b820
fix(#1290 review): decide the root op once on the chain head; typedef…
devin-ai-integration[bot] Oct 11, 2026
d980901
test(#1290 review): the opaque-callback typedef case builds through 1…
devin-ai-integration[bot] Oct 11, 2026
119f5e2
fix(#1290 review): a global./this. assignment target never marks a pa…
devin-ai-integration[bot] Oct 11, 2026
ddf1b80
test(#1290): each parameter-name invariant fails a test when removed
devin-ai-integration[bot] Oct 11, 2026
bd4f034
fix(#2001): a global./this. write never initializes a same-named local
devin-ai-integration[bot] Oct 11, 2026
40c60db
fix(#2003,#2004): initialization binds through 1.4; one statement wal…
devin-ai-integration[bot] Oct 11, 2026
0b8aaa6
docs: regenerate diagnostic manifest and scope-context matrix for the…
devin-ai-integration[bot] Oct 11, 2026
0a8501c
refactor(#2004): find a local's framed declaration by its span, not a…
devin-ai-integration[bot] Oct 11, 2026
a640d80
refactor(#2003): statement children come from the lowered tree only
devin-ai-integration[bot] Oct 11, 2026
14d9241
refactor(#2003): drop parse-tree helper rule entry and unused Express…
devin-ai-integration[bot] Oct 11, 2026
67d1559
fix(#2010): a for header's init and update are writes to 1.3 and the …
devin-ai-integration[bot] Oct 11, 2026
1d2c7cb
fix(#2011): a second declaration of a name in one C scope is E0425
devin-ai-integration[bot] Oct 11, 2026
6c18924
refactor(#2003): 2.1 listeners read statement parts from the lowered …
devin-ai-integration[bot] Oct 11, 2026
85f35c2
docs: regenerate diagnostic manifest for #2011 fixtures
devin-ai-integration[bot] Oct 11, 2026
7842444
test(#2003): artifact-lifetime control holds its own parse node; 2.1 …
devin-ai-integration[bot] Oct 11, 2026
4394e9d
Merge remote-tracking branch 'origin/main' into fix/1290-parameter-sh…
devin-ai-integration[bot] Oct 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .dependency-cruiser.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ const PARSE_TREE_TYPES = [
* fails exactly as if it had imported the grammar.
*/
const PARSE_TREE_HELPERS = [
"^src/cnx/utils/ast/(AssignmentTargetExtractor|ChildStatementCollector|StatementExpressionCollector)\\.ts$",
"^src/cnx/utils/(ChainRoot|ExpressionUnwrapper|ExpressionUtils|OverflowBehaviorUtils|ParserUtils|PostfixAnalysisUtils)\\.ts$",
"^src/cnx/types/TAssignmentSite\\.ts$",
];
Expand Down
12 changes: 7 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1016,13 +1016,15 @@ This used to name a third, `buildStructParamMemberAccess()`, "for chains". It ha

### Const Inference

`ModificationCollector.walkStatementForModifications()` (1.3 Declare, #1825) uses two
collectors in `src/cnx/utils/ast/`:
`ModificationCollector` (1.3 Declare, #1825) walks the lowered statements
(`TStatement`, from `StatementLowering`) with three helpers:

- `StatementExpressionCollector.collectAll()` — returns all expressions from any statement type
- `ChildStatementCollector.collectAll()` — returns child statements/blocks for recursion
- `StatementWrites.of()` (`src/cnx/utils/`) — the assignments a statement performs itself, a `for` header's init and update included (#2010)
- `ModificationCollector.expressionsOf()` — the expressions a statement holds itself, walked for calls
- `StatementChildren.of()` (`src/cnx/utils/`) — the statements nested directly inside, for recursion (#2003)

Update both when adding new statement types.
Each switches over every statement kind and ends in a `never` arm, so a new
kind does not compile until each places it.

---

Expand Down
16 changes: 5 additions & 11 deletions docs/architecture/parse-tree-sites.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ lexer-only include scan. Nothing from 2.2 Plan on.

| Layer | Modules |
| -------------------- | ------: |
| `src/cnx/TRANSPILE/` | 62 |
| `src/cnx/PARSE/` | 23 |
| `src/cnx/utils/` | 9 |
| `src/cnx/TRANSPILE/` | 61 |
| `src/cnx/PARSE/` | 21 |
| `src/cnx/utils/` | 6 |
| `src/cnx/types/` | 2 |
| **total** | **96** |
| **total** | **90** |

`src/cnx/TRANSPILE/3-Render/` is the render layer, and its share is the number
the issue singles out: the render layer holding parse nodes is how a
Expand All @@ -53,11 +53,9 @@ diagnostic can originate there at all, which is what #1322 relocates.
| `src/cnx/PARSE/3-Declare/c/index.ts` | grammar |
| `src/cnx/PARSE/3-Declare/c/utils/DeclaratorUtils.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/BitmapCollector.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/CallbackUseCollector.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/EnumCollector.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/FunctionCollector.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/LexicalScopeCollector.ts` | antlr4ng, grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/ModificationCollector.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/RegisterCollector.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/ScopeCollector.ts` | grammar |
| `src/cnx/PARSE/3-Declare/cnext/collectors/StructCollector.ts` | grammar |
Expand Down Expand Up @@ -115,7 +113,6 @@ diagnostic can originate there at all, which is what #1322 relocates.
| `src/cnx/TRANSPILE/1-Analyze/NullCheckAnalyzer.ts` | antlr4ng, grammar |
| `src/cnx/TRANSPILE/1-Analyze/ParameterNamingAnalyzer.ts` | antlr4ng, grammar |
| `src/cnx/TRANSPILE/1-Analyze/RegisterAccessAnalyzer.ts` | antlr4ng, grammar |
| `src/cnx/TRANSPILE/1-Analyze/ReturnPathAnalyzer.ts` | antlr4ng, grammar |
| `src/cnx/TRANSPILE/1-Analyze/ReturnValueUseAnalyzer.ts` | antlr4ng, grammar |
| `src/cnx/TRANSPILE/1-Analyze/runAnalyzers.ts` | grammar |
| `src/cnx/TRANSPILE/1-Analyze/SafeDivisionAnalyzer.ts` | antlr4ng, grammar |
Expand All @@ -133,14 +130,11 @@ diagnostic can originate there at all, which is what #1322 relocates.
| `src/cnx/TRANSPILE/1-Analyze/UndeclaredValueAnalyzer.ts` | antlr4ng, grammar |
| `src/cnx/types/IParsedFile.ts` | antlr4ng, grammar |
| `src/cnx/types/TAssignmentSite.ts` | grammar |
| `src/cnx/utils/ast/AssignmentTargetExtractor.ts` | grammar |
| `src/cnx/utils/ast/ChildStatementCollector.ts` | grammar |
| `src/cnx/utils/ast/StatementExpressionCollector.ts` | grammar |
| `src/cnx/utils/ChainRoot.ts` | grammar |
| `src/cnx/utils/ExpressionUnwrapper.ts` | grammar |
| `src/cnx/utils/ExpressionUtils.ts` | grammar |
| `src/cnx/utils/OverflowBehaviorUtils.ts` | grammar |
| `src/cnx/utils/ParserUtils.ts` | antlr4ng, grammar |
| `src/cnx/utils/PostfixAnalysisUtils.ts` | grammar |

96 module(s).
90 module(s).
13 changes: 9 additions & 4 deletions docs/decisions/TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
no recording site occupies nothing, so declare `error` and add the
recording site rather than declaring `warn` and calling it a tooling gap.

Two things the axes cannot see at all, worth knowing before you trust a
Three things the axes cannot see at all, worth knowing before you trust a
green row:

- SYNTACTIC FORM. A bare `read()` and a qualified `this.read()` inside the
Expand All @@ -136,9 +136,14 @@
- EXTERNAL-HEADER PROVENANCE. The relationship axis counts `.cnx` hops
only, so a symbol from a `.h` or a `.hpp` is indistinguishable from a
same-file one — and those are two separate symbol indexes.

If your feature can be written more than one way, or resolves symbols from
C or C++ headers, write fixtures for those forms anyway. They will not
- DECLARATION KIND. A local, a parameter and a `for` variable doing the
same thing in the same function derive the same cell, and they reach
codegen by different paths. ADR-057's scope-method row was green from
a shadowing local while a shadowing parameter was wrong (#1290).

If your feature can be written more than one way, can be done by more than
one kind of declaration, or resolves symbols from C or C++ headers, write
fixtures for those forms anyway. They will not
occupy distinct cells, but they will fail when someone breaks one spelling.

Worked example: ADR-051 section 5, including how it staged warn -> error.
Expand Down
16 changes: 9 additions & 7 deletions docs/decisions/adr-057-implicit-scope-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,20 +140,20 @@ uint32_t Counter__test(void) {
```

This applies to any local that would shadow a **file-scope** name — plain
variables, arrays, `string<N>` values and `for` init variables alike — and to
**writes** as well as reads. A write is the case that matters: `buf[0] <- 5` on a
variables, arrays, `string<N>` values, `for` init variables and **parameters**
alike — and to **writes** as well as reads. A parameter's moved name is the one
its `.c` signature, its `.h` prototype and every use in the body write (#1290). A write is the case that matters: `buf[0] <- 5` on a
shadowing local that kept its bare name lands on the global, compiles under
`-Wall -Wextra`, and is invisible until runtime. A local that shadows nothing,
or that shadows only an enclosing _local_, keeps its own name: C block scoping
already gives the right answer there, and neither `this.` nor `global.` can name
an enclosing local. The generated file is a certification artifact, so names stay
plain wherever the language does not require otherwise.

Two gaps are tracked rather than hidden: a **parameter** that shadows a
file-scope name is not yet covered (#1290), and the "is this name taken at file
One gap is tracked rather than hidden: the "is this name taken at file
scope" test is currently answered run-wide rather than per translation unit, so
an unrelated file in the same build can qualify a local that did not need it
(#1291). The second errs toward renaming on purpose — a missed rename is silently
(#1291). It errs toward renaming on purpose — a missed rename is silently
wrong code, an unnecessary one is only a longer name.

## Scope-Context Matrix (#1219)
Expand Down Expand Up @@ -265,5 +265,7 @@ None.
the execution test pinning all three levels at once),
`tests/bugs/issue-1210-bare-intra-scope-call/`,
`tests/bugs/issue-1244-adr057-scope-member-shadow/`,
`tests/bugs/issue-1724-sibling-scope-type-not-included/`
- Issue #1290 — a parameter shadowing a file-scope name is not yet covered
`tests/bugs/issue-1724-sibling-scope-type-not-included/`,
`tests/bugs/issue-1290-parameter-shadows-global/`,
`tests/bugs/issue-1994-pointer-local-shadow/`
- Issue #1290 — a parameter shadowing a file-scope name moves like a local
12 changes: 11 additions & 1 deletion docs/diagnostic-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ loses its `.expected.error`, or stops asserting a code listed here.
Removing a diagnostic on purpose means deleting its row in the same commit.
Adding one, or promoting `(uncoded)` to a real code, never fails the gate.

521 fixture(s) assert a diagnostic; 511 carry a code.
531 fixture(s) assert a diagnostic; 521 carry a code.

| Fixture | Codes |
| ------------------------------------------------------------------------------------------------ | ------------------- |
Expand Down Expand Up @@ -458,6 +458,16 @@ Adding one, or promoting `(uncoded)` to a real code, never fails the gate.
| tests/bugs/issue-1980-enum-read-before-assign/enum-read-before-assign.test.cnx | E0381 |
| tests/bugs/issue-1981-struct-cycle/direct.test.cnx | E0426 |
| tests/bugs/issue-1981-struct-cycle/mutual.test.cnx | E0426 |
| tests/bugs/issue-2001-qualified-write-initializes-local/global-write-local-shadow.test.cnx | E0381 |
| tests/bugs/issue-2001-qualified-write-initializes-local/global-write-scope-member.test.cnx | E0381 |
| tests/bugs/issue-2001-qualified-write-initializes-local/local-write-scope-member.test.cnx | E0381 |
| tests/bugs/issue-2001-qualified-write-initializes-local/parameter-write-scope-member.test.cnx | E0381 |
| tests/bugs/issue-2001-qualified-write-initializes-local/this-write-local-shadow.test.cnx | E0381 |
| tests/bugs/issue-2004-initialization-binds-through-1-4/for-variable-write-reader-after.test.cnx | E0381 |
| tests/bugs/issue-2004-initialization-binds-through-1-4/for-variable-write-reader-before.test.cnx | E0381 |
| tests/bugs/issue-2011-redeclaration-in-one-scope/local-redeclares-parameter.test.cnx | E0425 |
| tests/bugs/issue-2011-redeclaration-in-one-scope/local-redeclares-shadowing-parameter.test.cnx | E0425 |
| tests/bugs/issue-2011-redeclaration-in-one-scope/two-locals-one-block.test.cnx | E0425 |
| tests/bugs/issue-847-misra-17-7-lowering/bare-intra-scope-discard.test.cnx | E0708 |
| tests/bugs/issue-847-misra-17-7-lowering/cross-file-scope-discard.test.cnx | E0708 |
| tests/bugs/issue-847-misra-17-7-lowering/external-c-discard.test.cnx | E0708 |
Expand Down
2 changes: 1 addition & 1 deletion docs/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ second header and the program ran with a wrong value.
| E0422 | Function called before definition | Define function before calling it | `TRANSPILE/1-Analyze/FunctionCallAnalyzer.ts` |
| E0423 | Recursive function call (MISRA C:2012 Rule 17.2) | Remove recursive call | `TRANSPILE/1-Analyze/FunctionCallAnalyzer.ts` |
| E0424 | Unqualified enum member — did you mean `Enum.member`? | Use qualified enum member syntax | `TRANSPILE/1-Analyze/BareEnumMemberAnalyzer.ts`, `TRANSPILE/1-Analyze/SwitchStatementAnalyzer.ts` |
| E0425 | Symbol defined multiple times, or in multiple languages | Rename one definition | `PARSE/3-Declare/SymbolTable.ts`, `Transpiler.ts` |
| E0425 | Symbol defined multiple times, or in multiple languages | Rename one definition | `PARSE/3-Declare/SymbolTable.ts`, `Transpiler.ts`, `RedeclarationAnalyzer.ts` (a local, #2011) |
| E0426 | Type is not defined -- nowhere, or not yet: this file defines it below the use, or the use is inside it (ADR-030) | Declare the type above its first use, or #include the file that does | `TRANSPILE/1-Analyze/UndeclaredTypeAnalyzer.ts` |
| E0427 | Identifier is not defined | Declare it, or #include the file that does | `TRANSPILE/1-Analyze/UndeclaredValueAnalyzer.ts` |
| E0428 | Value assigned to an enum is not of that enum type | Assign one of the enum's members, or convert explicitly with a cast | `TRANSPILE/1-Analyze/EnumTypeSafetyAnalyzer.ts` |
Expand Down
3 changes: 2 additions & 1 deletion docs/scope-context-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ cell is declared by the ADR that owns it.
| scope member | ok | ok | ok | n/a | n/a |
| scope method | ok | ok | ok | n/a | n/a |

11 linked fixtures with no derivable context:
12 linked fixtures with no derivable context:

- `adr-057/local-shadows-scope.test.cnx`
- `adr-057/scope-variable-does-not-capture-type.test.cnx`
Expand All @@ -385,6 +385,7 @@ cell is declared by the ADR that owns it.
- `bugs/issue-1724-sibling-scope-type-not-included/sibling-declared-second.test.cnx`
- `bugs/issue-1724-sibling-scope-type-not-included/typer-sees-own-closure-error.test.cnx`
- `bugs/issue-1740-reopened-scope-this-call/this-call-across-files.test.cnx`
- `bugs/issue-2004-initialization-binds-through-1-4/for-variable-read.test.cnx`

## ADR-058

Expand Down
32 changes: 25 additions & 7 deletions scripts/__tests__/artifact-lifetime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,13 +372,31 @@ describe("artifact lifetime (#1445 box 2)", () => {
() => {
// The control, and it is load-bearing. Every assertion below is an
// emptiness claim, and an emptiness claim from a broken scan is
// indistinguishable from a true one. 2.1's listeners index parse nodes on
// instance fields while they walk -- that is what analysis is -- so the
// scan must see them. (The control used to be `Transpiler.retainedParses`,
// which #1932 deleted: the host no longer holds a tree.)
const control = storedParseNodes(/src\/cnx\/TRANSPILE\/1-Analyze\//);

expect(control.length).toBeGreaterThan(0);
// indistinguishable from a true one, so the scan must see a class that
// holds a parse node on a field. The control used to be production code:
// `Transpiler.retainedParses` until #1932 deleted it, then
// `NullCheckAnalyzer.currentIfCtx` until #2003 moved 2.1's listeners onto
// the lowered tree. Each time the code got better, the control broke. So
// the class is written here, in a file the scan reads like any other.
const probe = project.createSourceFile(
join(repoRoot, "src/cnx/TRANSPILE/1-Analyze/ParseNodeHolderProbe.ts"),
[
'import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";',
"class ParseNodeHolderProbe {",
" private held: Parser.IfStatementContext | null = null;",
"}",
"export default ParseNodeHolderProbe;",
].join("\n"),
);
try {
const control = storedParseNodes(/ParseNodeHolderProbe\.ts$/);

expect(control).toEqual([
"src/cnx/TRANSPILE/1-Analyze/ParseNodeHolderProbe.ts:3 ParseNodeHolderProbe.held",
]);
} finally {
project.removeSourceFile(probe);
}
},
WALK_TIMEOUT_MS,
);
Expand Down
Loading
Loading