c-next 0.1.67 → 0.1.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "packageManager": "npm@11.9.0",
6
6
  "type": "module",
@@ -53,7 +53,6 @@ import ITranspileError from "../lib/types/ITranspileError";
53
53
  import TranspilerState from "./types/TranspilerState";
54
54
  import runAnalyzers from "./logic/analysis/runAnalyzers";
55
55
  import ModificationAnalyzer from "./logic/analysis/ModificationAnalyzer";
56
- import AnalyzerContextBuilder from "./logic/analysis/AnalyzerContextBuilder";
57
56
  import CacheManager from "../utils/cache/CacheManager";
58
57
  import MapUtils from "../utils/MapUtils";
59
58
  import detectCppSyntax from "./logic/detectCppSyntax";
@@ -247,7 +246,7 @@ class Transpiler {
247
246
  *
248
247
  * Both run() and transpileSource() delegate here after file discovery.
249
248
  *
250
- * Stage 2: Collect symbols from C/C++ headers
249
+ * Stage 2: Collect symbols from C/C++ headers (includes building analyzer context)
251
250
  * Stage 3: Collect symbols from C-Next files
252
251
  * Stage 3b: Resolve external const array dimensions
253
252
  * Stage 4: Check for symbol conflicts
@@ -258,8 +257,9 @@ class Transpiler {
258
257
  input: IPipelineInput,
259
258
  result: ITranspilerResult,
260
259
  ): Promise<void> {
261
- // Stage 2: Collect symbols from C/C++ headers
260
+ // Stage 2: Collect symbols from C/C++ headers and build analyzer context
262
261
  this._collectAllHeaderSymbols(input.headerFiles, result);
262
+ CodeGenState.buildExternalStructFields();
263
263
 
264
264
  // Stage 3: Collect symbols from C-Next files
265
265
  if (!this._collectAllCNextSymbolsFromPipeline(input.cnextFiles, result)) {
@@ -395,16 +395,8 @@ class Transpiler {
395
395
  return this.buildParseOnlyResult(sourcePath, declarationCount);
396
396
  }
397
397
 
398
- // Run analyzers
399
- const externalStructFields =
400
- AnalyzerContextBuilder.buildExternalStructFields(
401
- CodeGenState.symbolTable,
402
- );
403
-
404
- const analyzerErrors = runAnalyzers(tree, tokenStream, {
405
- externalStructFields,
406
- symbolTable: CodeGenState.symbolTable,
407
- });
398
+ // Run analyzers (reads externalStructFields and symbolTable from CodeGenState)
399
+ const analyzerErrors = runAnalyzers(tree, tokenStream);
408
400
  if (analyzerErrors.length > 0) {
409
401
  return this.buildErrorResult(
410
402
  sourcePath,
@@ -21,6 +21,7 @@ import ExpressionUtils from "../../../utils/ExpressionUtils";
21
21
  import ParserUtils from "../../../utils/ParserUtils";
22
22
  import analyzePostfixOps from "../../../utils/PostfixAnalysisUtils";
23
23
  import SymbolTable from "../symbols/SymbolTable";
24
+ import CodeGenState from "../../state/CodeGenState";
24
25
  import ESourceLanguage from "../../../utils/types/ESourceLanguage";
25
26
  import ESymbolKind from "../../../utils/types/ESymbolKind";
26
27
 
@@ -408,8 +409,11 @@ class InitializationAnalyzer {
408
409
 
409
410
  private scopeStack: ScopeStack<IVariableState> = new ScopeStack();
410
411
 
411
- /** Known struct types and their fields */
412
- private readonly structFields: Map<string, Set<string>> = new Map();
412
+ /**
413
+ * C-Next struct fields from current file (collected at analysis time).
414
+ * External struct fields from C/C++ headers are accessed via CodeGenState.
415
+ */
416
+ private cnextStructFields: Map<string, Set<string>> = new Map();
413
417
 
414
418
  /** Track if we're processing a write target (left side of assignment) */
415
419
  private inWriteContext: boolean = false;
@@ -418,17 +422,25 @@ class InitializationAnalyzer {
418
422
  private symbolTable: SymbolTable | null = null;
419
423
 
420
424
  /**
421
- * Register external struct fields from C/C++ headers
422
- * This allows the analyzer to recognize types defined in headers
423
- *
424
- * @param externalFields Map of struct name -> Set of field names
425
+ * Get struct fields for a given struct type.
426
+ * Checks C-Next structs first, then falls back to CodeGenState for external structs.
425
427
  */
426
- public registerExternalStructFields(
427
- externalFields: Map<string, Set<string>>,
428
- ): void {
429
- for (const [structName, fields] of externalFields) {
430
- this.structFields.set(structName, fields);
428
+ private getStructFields(structName: string): Set<string> | undefined {
429
+ // Check C-Next structs from current file first
430
+ const cnextFields = this.cnextStructFields.get(structName);
431
+ if (cnextFields) {
432
+ return cnextFields;
431
433
  }
434
+
435
+ // Check external structs from CodeGenState
436
+ return CodeGenState.getExternalStructFields().get(structName);
437
+ }
438
+
439
+ /**
440
+ * Check if a type name is a known struct.
441
+ */
442
+ private isKnownStruct(typeName: string): boolean {
443
+ return this.getStructFields(typeName) !== undefined;
432
444
  }
433
445
 
434
446
  /**
@@ -469,9 +481,10 @@ class InitializationAnalyzer {
469
481
  this.errors = [];
470
482
  this.scopeStack = new ScopeStack();
471
483
  this.symbolTable = symbolTable ?? null;
472
- // Don't clear structFields - external fields may have been registered
484
+ // Clear C-Next struct fields from previous analysis (external fields come from CodeGenState)
485
+ this.cnextStructFields = new Map();
473
486
 
474
- // First pass: collect struct definitions
487
+ // First pass: collect struct definitions from current file
475
488
  this.collectStructDefinitions(tree);
476
489
 
477
490
  // Create global scope with all global/namespace variable declarations
@@ -553,7 +566,8 @@ class InitializationAnalyzer {
553
566
  }
554
567
 
555
568
  /**
556
- * Collect struct definitions to know their fields
569
+ * Collect struct definitions from current file to know their fields.
570
+ * This supplements the external struct fields from CodeGenState.
557
571
  */
558
572
  private collectStructDefinitions(tree: Parser.ProgramContext): void {
559
573
  for (const decl of tree.declaration()) {
@@ -567,7 +581,7 @@ class InitializationAnalyzer {
567
581
  fields.add(fieldName);
568
582
  }
569
583
 
570
- this.structFields.set(structName, fields);
584
+ this.cnextStructFields.set(structName, fields);
571
585
  }
572
586
  }
573
587
  }
@@ -610,9 +624,9 @@ class InitializationAnalyzer {
610
624
  this.enterScope();
611
625
  }
612
626
 
613
- const isStruct = typeName !== null && this.structFields.has(typeName);
627
+ const isStruct = typeName !== null && this.isKnownStruct(typeName);
614
628
  const fields = isStruct
615
- ? this.structFields.get(typeName)!
629
+ ? this.getStructFields(typeName)!
616
630
  : new Set<string>();
617
631
 
618
632
  // Issue #503: C++ classes with default constructors are automatically initialized
@@ -637,13 +651,11 @@ class InitializationAnalyzer {
637
651
  * SonarCloud S3776: Refactored to use helper methods.
638
652
  */
639
653
  public recordAssignment(name: string, field?: string): void {
640
- const structFields = this.structFields;
641
-
642
654
  this.scopeStack.update(name, (state) => {
643
655
  if (field) {
644
- this.recordFieldAssignment(state, field, structFields);
656
+ this.recordFieldAssignment(state, field);
645
657
  } else {
646
- this.recordWholeAssignment(state, structFields);
658
+ this.recordWholeAssignment(state);
647
659
  }
648
660
  return state;
649
661
  });
@@ -652,16 +664,12 @@ class InitializationAnalyzer {
652
664
  /**
653
665
  * Handle field-level assignment.
654
666
  */
655
- private recordFieldAssignment(
656
- state: IVariableState,
657
- field: string,
658
- structFields: Map<string, Set<string>>,
659
- ): void {
667
+ private recordFieldAssignment(state: IVariableState, field: string): void {
660
668
  state.initializedFields.add(field);
661
669
  // Check if all fields are now initialized
662
670
  if (!state.isStruct || !state.typeName) return;
663
671
 
664
- const allFields = structFields.get(state.typeName);
672
+ const allFields = this.getStructFields(state.typeName);
665
673
  if (!allFields) return;
666
674
 
667
675
  const allInitialized = [...allFields].every((f) =>
@@ -675,15 +683,12 @@ class InitializationAnalyzer {
675
683
  /**
676
684
  * Handle whole-variable assignment.
677
685
  */
678
- private recordWholeAssignment(
679
- state: IVariableState,
680
- structFields: Map<string, Set<string>>,
681
- ): void {
686
+ private recordWholeAssignment(state: IVariableState): void {
682
687
  state.initialized = true;
683
688
  // Mark all fields as initialized too
684
689
  if (!state.isStruct || !state.typeName) return;
685
690
 
686
- const fields = structFields.get(state.typeName);
691
+ const fields = this.getStructFields(state.typeName);
687
692
  if (fields) {
688
693
  state.initializedFields = new Set(fields);
689
694
  }
@@ -745,8 +750,8 @@ class InitializationAnalyzer {
745
750
  field: string,
746
751
  state: IVariableState,
747
752
  ): void {
748
- const structFields = this.structFields.get(state.typeName!);
749
- if (!structFields?.has(field)) return;
753
+ const structFieldSet = this.getStructFields(state.typeName!);
754
+ if (!structFieldSet?.has(field)) return;
750
755
 
751
756
  if (!state.initializedFields.has(field)) {
752
757
  this.addError(`${name}.${field}`, line, column, state.declaration, false);
@@ -874,9 +879,9 @@ class InitializationAnalyzer {
874
879
  this.enterScope();
875
880
  }
876
881
 
877
- const isStruct = typeName !== null && this.structFields.has(typeName);
882
+ const isStruct = typeName !== null && this.isKnownStruct(typeName);
878
883
  const fields = isStruct
879
- ? this.structFields.get(typeName)!
884
+ ? this.getStructFields(typeName)!
880
885
  : new Set<string>();
881
886
 
882
887
  const state: IVariableState = {
@@ -4,11 +4,12 @@
4
4
  */
5
5
 
6
6
  import { CharStream, CommonTokenStream } from "antlr4ng";
7
- import { describe, expect, it } from "vitest";
7
+ import { describe, expect, it, beforeEach } from "vitest";
8
8
  import { CNextLexer } from "../../parser/grammar/CNextLexer";
9
9
  import { CNextParser } from "../../parser/grammar/CNextParser";
10
10
  import InitializationAnalyzer from "../InitializationAnalyzer";
11
11
  import SymbolTable from "../../symbols/SymbolTable";
12
+ import CodeGenState from "../../../state/CodeGenState";
12
13
  import ESymbolKind from "../../../../utils/types/ESymbolKind";
13
14
  import ESourceLanguage from "../../../../utils/types/ESourceLanguage";
14
15
 
@@ -24,6 +25,12 @@ function parse(source: string) {
24
25
  }
25
26
 
26
27
  describe("InitializationAnalyzer", () => {
28
+ // Reset CodeGenState before each test
29
+ beforeEach(() => {
30
+ CodeGenState.reset();
31
+ CodeGenState.symbolTable.clear();
32
+ });
33
+
27
34
  // ========================================================================
28
35
  // Issue #503: C++ Class Initialization
29
36
  // ========================================================================
@@ -38,9 +45,8 @@ describe("InitializationAnalyzer", () => {
38
45
  `;
39
46
  const tree = parse(code);
40
47
 
41
- // Create symbol table with C++ class
42
- const symbolTable = new SymbolTable();
43
- symbolTable.addSymbol({
48
+ // Set up CodeGenState with C++ class
49
+ CodeGenState.symbolTable.addSymbol({
44
50
  name: "CppMessage",
45
51
  kind: ESymbolKind.Class,
46
52
  sourceLanguage: ESourceLanguage.Cpp,
@@ -49,14 +55,13 @@ describe("InitializationAnalyzer", () => {
49
55
  isExported: true,
50
56
  });
51
57
  // Add the field so the analyzer knows about it
52
- symbolTable.addStructField("CppMessage", "pgn", "u16");
58
+ CodeGenState.symbolTable.addStructField("CppMessage", "pgn", "u16");
53
59
 
54
- const analyzer = new InitializationAnalyzer();
55
- analyzer.registerExternalStructFields(
56
- new Map([["CppMessage", new Set(["pgn"])]]),
57
- );
60
+ // Build external struct fields from symbol table
61
+ CodeGenState.buildExternalStructFields();
58
62
 
59
- const errors = analyzer.analyze(tree, symbolTable);
63
+ const analyzer = new InitializationAnalyzer();
64
+ const errors = analyzer.analyze(tree, CodeGenState.symbolTable);
60
65
 
61
66
  // Should have NO errors - C++ class is initialized by constructor
62
67
  expect(errors).toHaveLength(0);
@@ -95,9 +100,8 @@ describe("InitializationAnalyzer", () => {
95
100
  `;
96
101
  const tree = parse(code);
97
102
 
98
- // Create symbol table with C++ struct (not class)
99
- const symbolTable = new SymbolTable();
100
- symbolTable.addSymbol({
103
+ // Set up CodeGenState with C++ struct (not class)
104
+ CodeGenState.symbolTable.addSymbol({
101
105
  name: "CppStruct",
102
106
  kind: ESymbolKind.Struct,
103
107
  sourceLanguage: ESourceLanguage.Cpp,
@@ -105,14 +109,13 @@ describe("InitializationAnalyzer", () => {
105
109
  sourceLine: 1,
106
110
  isExported: true,
107
111
  });
108
- symbolTable.addStructField("CppStruct", "value", "u32");
112
+ CodeGenState.symbolTable.addStructField("CppStruct", "value", "u32");
109
113
 
110
- const analyzer = new InitializationAnalyzer();
111
- analyzer.registerExternalStructFields(
112
- new Map([["CppStruct", new Set(["value"])]]),
113
- );
114
+ // Build external struct fields from symbol table
115
+ CodeGenState.buildExternalStructFields();
114
116
 
115
- const errors = analyzer.analyze(tree, symbolTable);
117
+ const analyzer = new InitializationAnalyzer();
118
+ const errors = analyzer.analyze(tree, CodeGenState.symbolTable);
116
119
 
117
120
  // Should have NO errors - C++ structs also have default constructors
118
121
  expect(errors).toHaveLength(0);
@@ -127,9 +130,8 @@ describe("InitializationAnalyzer", () => {
127
130
  `;
128
131
  const tree = parse(code);
129
132
 
130
- // Create symbol table with C struct (not C++)
131
- const symbolTable = new SymbolTable();
132
- symbolTable.addSymbol({
133
+ // Set up CodeGenState with C struct (not C++)
134
+ CodeGenState.symbolTable.addSymbol({
133
135
  name: "CStruct",
134
136
  kind: ESymbolKind.Struct,
135
137
  sourceLanguage: ESourceLanguage.C,
@@ -137,14 +139,13 @@ describe("InitializationAnalyzer", () => {
137
139
  sourceLine: 1,
138
140
  isExported: true,
139
141
  });
140
- symbolTable.addStructField("CStruct", "value", "u32");
142
+ CodeGenState.symbolTable.addStructField("CStruct", "value", "u32");
141
143
 
142
- const analyzer = new InitializationAnalyzer();
143
- analyzer.registerExternalStructFields(
144
- new Map([["CStruct", new Set(["value"])]]),
145
- );
144
+ // Build external struct fields from symbol table (as pipeline does)
145
+ CodeGenState.buildExternalStructFields();
146
146
 
147
- const errors = analyzer.analyze(tree, symbolTable);
147
+ const analyzer = new InitializationAnalyzer();
148
+ const errors = analyzer.analyze(tree, CodeGenState.symbolTable);
148
149
 
149
150
  // SHOULD have an error - C structs don't have constructors
150
151
  expect(errors.length).toBeGreaterThan(0);
@@ -2,12 +2,13 @@
2
2
  * Unit tests for runAnalyzers
3
3
  * Tests that all 8 analyzers run in sequence with early returns on errors
4
4
  */
5
- import { describe, it, expect } from "vitest";
5
+ import { describe, it, expect, beforeEach } from "vitest";
6
6
  import { CharStream, CommonTokenStream } from "antlr4ng";
7
7
  import { CNextLexer } from "../../parser/grammar/CNextLexer";
8
8
  import { CNextParser } from "../../parser/grammar/CNextParser";
9
9
  import runAnalyzers from "../runAnalyzers";
10
10
  import SymbolTable from "../../symbols/SymbolTable";
11
+ import CodeGenState from "../../../state/CodeGenState";
11
12
  import ESymbolKind from "../../../../utils/types/ESymbolKind";
12
13
  import ESourceLanguage from "../../../../utils/types/ESourceLanguage";
13
14
 
@@ -24,6 +25,12 @@ function parseWithStream(source: string) {
24
25
  }
25
26
 
26
27
  describe("runAnalyzers", () => {
28
+ // Reset CodeGenState before each test
29
+ beforeEach(() => {
30
+ CodeGenState.reset();
31
+ CodeGenState.symbolTable.clear();
32
+ });
33
+
27
34
  // ========================================================================
28
35
  // Happy Path
29
36
  // ========================================================================
@@ -206,24 +213,33 @@ describe("runAnalyzers", () => {
206
213
  });
207
214
 
208
215
  // ========================================================================
209
- // Options: externalStructFields and symbolTable
216
+ // Options: CodeGenState integration and symbolTable
210
217
  // ========================================================================
211
218
 
212
219
  describe("options", () => {
213
- it("should pass externalStructFields to InitializationAnalyzer", () => {
214
- // Code that uses a field from an external struct - the externalStructFields
215
- // option tells the analyzer about external struct types
220
+ it("should read externalStructFields from CodeGenState", () => {
221
+ // Code that uses a field from an external struct - externalStructFields
222
+ // are now read from CodeGenState
216
223
  const { tree, tokenStream } = parseWithStream(`
217
224
  void main() {
218
225
  u32 x <- 5;
219
226
  }
220
227
  `);
221
228
 
222
- const externalStructFields = new Map<string, Set<string>>([
223
- ["ExternalStruct", new Set(["field1", "field2"])],
224
- ]);
229
+ // Set up external struct fields in CodeGenState
230
+ CodeGenState.symbolTable.addStructField(
231
+ "ExternalStruct",
232
+ "field1",
233
+ "u32",
234
+ );
235
+ CodeGenState.symbolTable.addStructField(
236
+ "ExternalStruct",
237
+ "field2",
238
+ "u32",
239
+ );
240
+ CodeGenState.buildExternalStructFields();
225
241
 
226
- const errors = runAnalyzers(tree, tokenStream, { externalStructFields });
242
+ const errors = runAnalyzers(tree, tokenStream);
227
243
  expect(errors).toHaveLength(0);
228
244
  });
229
245
 
@@ -248,18 +264,15 @@ describe("runAnalyzers", () => {
248
264
  expect(errors).toHaveLength(0);
249
265
  });
250
266
 
251
- it("should pass both options together", () => {
267
+ it("should use CodeGenState.symbolTable by default", () => {
252
268
  const { tree, tokenStream } = parseWithStream(`
253
269
  void main() {
254
270
  u32 x <- 5;
255
271
  }
256
272
  `);
257
273
 
258
- const externalStructFields = new Map<string, Set<string>>([
259
- ["CppMessage", new Set(["pgn"])],
260
- ]);
261
- const symbolTable = new SymbolTable();
262
- symbolTable.addSymbol({
274
+ // Set up C++ class in CodeGenState.symbolTable
275
+ CodeGenState.symbolTable.addSymbol({
263
276
  name: "CppMessage",
264
277
  kind: ESymbolKind.Class,
265
278
  sourceLanguage: ESourceLanguage.Cpp,
@@ -267,11 +280,11 @@ describe("runAnalyzers", () => {
267
280
  sourceLine: 1,
268
281
  isExported: true,
269
282
  });
283
+ CodeGenState.symbolTable.addStructField("CppMessage", "pgn", "u16");
284
+ CodeGenState.buildExternalStructFields();
270
285
 
271
- const errors = runAnalyzers(tree, tokenStream, {
272
- externalStructFields,
273
- symbolTable,
274
- });
286
+ // No options passed - should use CodeGenState.symbolTable
287
+ const errors = runAnalyzers(tree, tokenStream);
275
288
  expect(errors).toHaveLength(0);
276
289
  });
277
290
  });
@@ -17,20 +17,16 @@ import FloatModuloAnalyzer from "./FloatModuloAnalyzer";
17
17
  import CommentExtractor from "./CommentExtractor";
18
18
  import ITranspileError from "../../../lib/types/ITranspileError";
19
19
  import SymbolTable from "../symbols/SymbolTable";
20
+ import CodeGenState from "../../state/CodeGenState";
20
21
 
21
22
  /**
22
23
  * Options for running analyzers
23
24
  */
24
25
  interface IAnalyzerOptions {
25
- /**
26
- * External struct field information from C/C++ headers
27
- * Maps struct name -> Set of field names
28
- */
29
- externalStructFields?: Map<string, Set<string>>;
30
-
31
26
  /**
32
27
  * Symbol table containing external function definitions from C/C++ headers
33
- * Used by FunctionCallAnalyzer to recognize external functions
28
+ * Used by FunctionCallAnalyzer to recognize external functions.
29
+ * Falls back to CodeGenState.symbolTable if not provided.
34
30
  */
35
31
  symbolTable?: SymbolTable;
36
32
  }
@@ -100,13 +96,11 @@ function runAnalyzers(
100
96
 
101
97
  // 3. Initialization analysis (Rust-style use-before-init detection)
102
98
  const initAnalyzer = new InitializationAnalyzer();
103
- if (options?.externalStructFields) {
104
- initAnalyzer.registerExternalStructFields(options.externalStructFields);
105
- }
106
- // Issue #503: Pass symbol table so C++ classes with default constructors are recognized
99
+ // External struct fields and symbolTable are read from CodeGenState directly
100
+ const symbolTable = options?.symbolTable ?? CodeGenState.symbolTable;
107
101
  if (
108
102
  collectErrors(
109
- initAnalyzer.analyze(tree, options?.symbolTable),
103
+ initAnalyzer.analyze(tree, symbolTable),
110
104
  errors,
111
105
  formatWithCode,
112
106
  )
@@ -115,10 +109,10 @@ function runAnalyzers(
115
109
  }
116
110
 
117
111
  // 4. Call analysis (ADR-030: define-before-use)
118
- const funcAnalyzer = new FunctionCallAnalyzer();
112
+ const callAnalyzer = new FunctionCallAnalyzer();
119
113
  if (
120
114
  collectErrors(
121
- funcAnalyzer.analyze(tree, options?.symbolTable),
115
+ callAnalyzer.analyze(tree, symbolTable),
122
116
  errors,
123
117
  formatWithCode,
124
118
  )
@@ -1441,16 +1441,33 @@ const generateMemberAccess = (
1441
1441
  state: IGeneratorState,
1442
1442
  orchestrator: IOrchestrator,
1443
1443
  effects: TGeneratorEffect[],
1444
- ): MemberAccessResult =>
1445
- tryBitmapFieldAccess(ctx, input, effects) ??
1446
- tryScopeMemberAccess(ctx, input, state, orchestrator) ??
1447
- tryKnownScopeAccess(ctx, input, state, orchestrator) ??
1448
- tryEnumMemberAccess(ctx, input, state, orchestrator) ??
1449
- tryRegisterMemberAccess(ctx, input, state) ??
1450
- tryStructParamAccess(ctx, orchestrator) ??
1451
- tryRegisterBitmapAccess(ctx, input, effects) ??
1452
- tryStructBitmapAccess(ctx, input, effects) ??
1453
- generateDefaultAccess(ctx, orchestrator);
1444
+ ): MemberAccessResult => {
1445
+ // Check for enum shadowing before dispatch - catches case where identifier
1446
+ // was resolved to a scope member that shadows a global enum
1447
+ MemberAccessValidator.validateGlobalEntityAccess(
1448
+ ctx.result,
1449
+ ctx.memberName,
1450
+ "enum",
1451
+ state.currentScope,
1452
+ ctx.isGlobalAccess,
1453
+ {
1454
+ rootIdentifier: ctx.rootIdentifier,
1455
+ knownEnums: input.symbols!.knownEnums,
1456
+ },
1457
+ );
1458
+
1459
+ return (
1460
+ tryBitmapFieldAccess(ctx, input, effects) ??
1461
+ tryScopeMemberAccess(ctx, input, state, orchestrator) ??
1462
+ tryKnownScopeAccess(ctx, input, state, orchestrator) ??
1463
+ tryEnumMemberAccess(ctx, input, state, orchestrator) ??
1464
+ tryRegisterMemberAccess(ctx, input, state) ??
1465
+ tryStructParamAccess(ctx, orchestrator) ??
1466
+ tryRegisterBitmapAccess(ctx, input, effects) ??
1467
+ tryStructBitmapAccess(ctx, input, effects) ??
1468
+ generateDefaultAccess(ctx, orchestrator)
1469
+ );
1470
+ };
1454
1471
 
1455
1472
  // ========================================================================
1456
1473
  // Member Access Handlers
@@ -1575,12 +1592,15 @@ const tryEnumMemberAccess = (
1575
1592
  return null;
1576
1593
  }
1577
1594
 
1595
+ // Shadowing check already done in generateMemberAccess; this catches
1596
+ // direct conflicts where ctx.result is the enum name (no resolution happened)
1578
1597
  MemberAccessValidator.validateGlobalEntityAccess(
1579
1598
  ctx.result,
1580
1599
  ctx.memberName,
1581
1600
  "enum",
1582
1601
  state.currentScope,
1583
1602
  ctx.isGlobalAccess,
1603
+ { scopeMembers: state.scopeMembers },
1584
1604
  );
1585
1605
 
1586
1606
  const output = initializeMemberOutput(ctx);
@@ -1606,6 +1626,7 @@ const tryRegisterMemberAccess = (
1606
1626
  "register",
1607
1627
  state.currentScope,
1608
1628
  ctx.isGlobalAccess,
1629
+ { scopeMembers: state.scopeMembers },
1609
1630
  );
1610
1631
 
1611
1632
  MemberAccessValidator.validateRegisterReadAccess(
@@ -1318,7 +1318,7 @@ describe("PostfixExpressionGenerator", () => {
1318
1318
  expect(result.code).toBe("Color_Red");
1319
1319
  });
1320
1320
 
1321
- it("throws when accessing enum without global prefix inside scope", () => {
1321
+ it("throws when accessing enum with naming conflict inside scope", () => {
1322
1322
  const symbols = createMockSymbols({
1323
1323
  knownEnums: new Set(["Color"]),
1324
1324
  });
@@ -1326,7 +1326,8 @@ describe("PostfixExpressionGenerator", () => {
1326
1326
  createMockPostfixOp({ identifier: "Red" }),
1327
1327
  ]);
1328
1328
  const input = createMockInput({ symbols });
1329
- const state = createMockState({ currentScope: "Motor" });
1329
+ const scopeMembers = new Map([["Motor", new Set(["Color"])]]);
1330
+ const state = createMockState({ currentScope: "Motor", scopeMembers });
1330
1331
  const orchestrator = createMockOrchestrator({
1331
1332
  generatePrimaryExpr: () => "Color",
1332
1333
  getScopeSeparator: () => "_",
@@ -1336,6 +1337,48 @@ describe("PostfixExpressionGenerator", () => {
1336
1337
  generatePostfixExpression(ctx, input, state, orchestrator),
1337
1338
  ).toThrow("Use 'global.Color.Red' to access enum 'Color'");
1338
1339
  });
1340
+
1341
+ it("allows enum access without global prefix when no naming conflict", () => {
1342
+ const symbols = createMockSymbols({
1343
+ knownEnums: new Set(["Color"]),
1344
+ });
1345
+ const ctx = createMockPostfixExpressionContext("Color", [
1346
+ createMockPostfixOp({ identifier: "Red" }),
1347
+ ]);
1348
+ const input = createMockInput({ symbols });
1349
+ const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
1350
+ const state = createMockState({ currentScope: "Motor", scopeMembers });
1351
+ const orchestrator = createMockOrchestrator({
1352
+ generatePrimaryExpr: () => "Color",
1353
+ getScopeSeparator: () => "_",
1354
+ });
1355
+
1356
+ const result = generatePostfixExpression(ctx, input, state, orchestrator);
1357
+ expect(result.code).toBe("Color_Red");
1358
+ });
1359
+
1360
+ it("throws when scope member shadows global enum (resolved identifier differs)", () => {
1361
+ const symbols = createMockSymbols({
1362
+ knownEnums: new Set(["Color"]),
1363
+ });
1364
+ const ctx = createMockPostfixExpressionContext("Color", [
1365
+ createMockPostfixOp({ identifier: "Red" }),
1366
+ ]);
1367
+ const input = createMockInput({ symbols });
1368
+ const scopeMembers = new Map([["Motor", new Set(["Color"])]]);
1369
+ const state = createMockState({ currentScope: "Motor", scopeMembers });
1370
+ const orchestrator = createMockOrchestrator({
1371
+ // Simulates identifier resolution: Color -> Motor_Color (scope member)
1372
+ generatePrimaryExpr: () => "Motor_Color",
1373
+ getScopeSeparator: () => "_",
1374
+ });
1375
+
1376
+ expect(() =>
1377
+ generatePostfixExpression(ctx, input, state, orchestrator),
1378
+ ).toThrow(
1379
+ "Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor' (scope member 'Color' shadows the global enum)",
1380
+ );
1381
+ });
1339
1382
  });
1340
1383
 
1341
1384
  describe("register member access", () => {
@@ -1375,7 +1418,7 @@ describe("PostfixExpressionGenerator", () => {
1375
1418
  ).toThrow("cannot read from write-only register member 'DATA'");
1376
1419
  });
1377
1420
 
1378
- it("throws when accessing register without global prefix inside scope", () => {
1421
+ it("throws when accessing register with naming conflict inside scope", () => {
1379
1422
  const symbols = createMockSymbols({
1380
1423
  knownRegisters: new Set(["GPIO"]),
1381
1424
  });
@@ -1383,7 +1426,8 @@ describe("PostfixExpressionGenerator", () => {
1383
1426
  createMockPostfixOp({ identifier: "PIN0" }),
1384
1427
  ]);
1385
1428
  const input = createMockInput({ symbols });
1386
- const state = createMockState({ currentScope: "Motor" });
1429
+ const scopeMembers = new Map([["Motor", new Set(["GPIO"])]]);
1430
+ const state = createMockState({ currentScope: "Motor", scopeMembers });
1387
1431
  const orchestrator = createMockOrchestrator({
1388
1432
  generatePrimaryExpr: () => "GPIO",
1389
1433
  });
@@ -1392,6 +1436,24 @@ describe("PostfixExpressionGenerator", () => {
1392
1436
  generatePostfixExpression(ctx, input, state, orchestrator),
1393
1437
  ).toThrow("Use 'global.GPIO.PIN0' to access register 'GPIO'");
1394
1438
  });
1439
+
1440
+ it("allows register access without global prefix when no naming conflict", () => {
1441
+ const symbols = createMockSymbols({
1442
+ knownRegisters: new Set(["GPIO"]),
1443
+ });
1444
+ const ctx = createMockPostfixExpressionContext("GPIO", [
1445
+ createMockPostfixOp({ identifier: "PIN0" }),
1446
+ ]);
1447
+ const input = createMockInput({ symbols });
1448
+ const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
1449
+ const state = createMockState({ currentScope: "Motor", scopeMembers });
1450
+ const orchestrator = createMockOrchestrator({
1451
+ generatePrimaryExpr: () => "GPIO",
1452
+ });
1453
+
1454
+ const result = generatePostfixExpression(ctx, input, state, orchestrator);
1455
+ expect(result.code).toBe("GPIO_PIN0");
1456
+ });
1395
1457
  });
1396
1458
 
1397
1459
  describe("struct parameter access", () => {
@@ -55,13 +55,22 @@ class MemberAccessValidator {
55
55
 
56
56
  /**
57
57
  * ADR-016/057: Validate that a global entity (enum or register) is accessed
58
- * with 'global.' prefix when inside a scope that doesn't own the entity.
58
+ * with 'global.' prefix when inside a scope that has a naming conflict.
59
59
  *
60
- * @param entityName - The entity being accessed (e.g., "Color", "GPIO")
60
+ * Detects two types of conflicts:
61
+ * 1. Scope member with same name as entity (via scopeMembers lookup)
62
+ * 2. Identifier was resolved to scope member, shadowing a global enum
63
+ * (via rootIdentifier != resolvedName comparison)
64
+ *
65
+ * @param entityName - The resolved entity name (e.g., "Color" or "Motor_Color")
61
66
  * @param memberName - The member after the entity (e.g., "Red", "PIN0")
62
67
  * @param entityType - "enum" or "register" (for error message)
63
68
  * @param currentScope - Active scope context (null = not in a scope)
64
69
  * @param isGlobalAccess - Whether the access used 'global.' prefix
70
+ * @param options - Optional conflict detection parameters
71
+ * @param options.scopeMembers - Map of scope names to their member names
72
+ * @param options.rootIdentifier - Original identifier before resolution (for shadowing detection)
73
+ * @param options.knownEnums - Set of known enum names (for shadowing detection)
65
74
  */
66
75
  static validateGlobalEntityAccess(
67
76
  entityName: string,
@@ -69,18 +78,50 @@ class MemberAccessValidator {
69
78
  entityType: string,
70
79
  currentScope: string | null,
71
80
  isGlobalAccess: boolean,
81
+ options?: {
82
+ scopeMembers?: ReadonlyMap<string, ReadonlySet<string>>;
83
+ rootIdentifier?: string;
84
+ knownEnums?: ReadonlySet<string>;
85
+ },
72
86
  ): void {
73
87
  if (isGlobalAccess) {
74
88
  return;
75
89
  }
76
- if (currentScope) {
77
- const belongsToCurrentScope = entityName.startsWith(currentScope + "_");
78
- if (!belongsToCurrentScope) {
90
+ if (!currentScope) {
91
+ return;
92
+ }
93
+
94
+ const { scopeMembers, rootIdentifier, knownEnums } = options ?? {};
95
+
96
+ // Check 1: Shadowing detection - identifier was resolved to a scope member
97
+ // that shadows a global enum. This produces invalid C (e.g., Motor_Color.RED).
98
+ // This check must run BEFORE the belongsToCurrentScope check because
99
+ // the resolved name (e.g., Motor_Color) will start with the scope prefix.
100
+ if (rootIdentifier && knownEnums) {
101
+ const wasResolved = entityName !== rootIdentifier;
102
+ const rootIsEnum = knownEnums.has(rootIdentifier);
103
+ const resolvedIsNotEnum = !knownEnums.has(entityName);
104
+ if (wasResolved && rootIsEnum && resolvedIsNotEnum) {
79
105
  throw new Error(
80
- `Error: Use 'global.${entityName}.${memberName}' to access ${entityType} '${entityName}' from inside scope '${currentScope}'`,
106
+ `Error: Use 'global.${rootIdentifier}.${memberName}' to access enum '${rootIdentifier}' from inside scope '${currentScope}' (scope member '${rootIdentifier}' shadows the global enum)`,
81
107
  );
82
108
  }
83
109
  }
110
+
111
+ // Skip check if entity belongs to current scope (e.g., Motor_State in scope Motor)
112
+ const belongsToCurrentScope = entityName.startsWith(currentScope + "_");
113
+ if (belongsToCurrentScope) {
114
+ return;
115
+ }
116
+
117
+ // Check 2: Direct conflict - scope has a member with the same name as the entity
118
+ const scopeMemberNames = scopeMembers?.get(currentScope);
119
+ const hasConflict = scopeMemberNames?.has(entityName) ?? false;
120
+ if (hasConflict) {
121
+ throw new Error(
122
+ `Error: Use 'global.${entityName}.${memberName}' to access ${entityType} '${entityName}' from inside scope '${currentScope}'`,
123
+ );
124
+ }
84
125
  }
85
126
  }
86
127
 
@@ -118,7 +118,8 @@ describe("MemberAccessValidator", () => {
118
118
  });
119
119
 
120
120
  describe("validateGlobalEntityAccess", () => {
121
- it("throws when in scope and entity does not belong to current scope", () => {
121
+ it("throws when in scope and entity name conflicts with scope member", () => {
122
+ const scopeMembers = new Map([["Motor", new Set(["Color", "speed"])]]);
122
123
  expect(() =>
123
124
  MemberAccessValidator.validateGlobalEntityAccess(
124
125
  "Color",
@@ -126,13 +127,29 @@ describe("MemberAccessValidator", () => {
126
127
  "enum",
127
128
  "Motor",
128
129
  false,
130
+ { scopeMembers },
129
131
  ),
130
132
  ).toThrow(
131
133
  "Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor'",
132
134
  );
133
135
  });
134
136
 
135
- it("does NOT throw when isGlobalAccess is true", () => {
137
+ it("does NOT throw when in scope but no naming conflict", () => {
138
+ const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
139
+ expect(() =>
140
+ MemberAccessValidator.validateGlobalEntityAccess(
141
+ "Color",
142
+ "Red",
143
+ "enum",
144
+ "Motor",
145
+ false,
146
+ { scopeMembers },
147
+ ),
148
+ ).not.toThrow();
149
+ });
150
+
151
+ it("does NOT throw when isGlobalAccess is true even with conflict", () => {
152
+ const scopeMembers = new Map([["Motor", new Set(["Color"])]]);
136
153
  expect(() =>
137
154
  MemberAccessValidator.validateGlobalEntityAccess(
138
155
  "Color",
@@ -140,6 +157,7 @@ describe("MemberAccessValidator", () => {
140
157
  "enum",
141
158
  "Motor",
142
159
  true,
160
+ { scopeMembers },
143
161
  ),
144
162
  ).not.toThrow();
145
163
  });
@@ -168,7 +186,8 @@ describe("MemberAccessValidator", () => {
168
186
  ).not.toThrow();
169
187
  });
170
188
 
171
- it("works with register entity type", () => {
189
+ it("throws for register with naming conflict", () => {
190
+ const scopeMembers = new Map([["Motor", new Set(["GPIO"])]]);
172
191
  expect(() =>
173
192
  MemberAccessValidator.validateGlobalEntityAccess(
174
193
  "GPIO",
@@ -176,10 +195,97 @@ describe("MemberAccessValidator", () => {
176
195
  "register",
177
196
  "Motor",
178
197
  false,
198
+ { scopeMembers },
179
199
  ),
180
200
  ).toThrow(
181
201
  "Use 'global.GPIO.PIN0' to access register 'GPIO' from inside scope 'Motor'",
182
202
  );
183
203
  });
204
+
205
+ it("does NOT throw for register without naming conflict", () => {
206
+ const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
207
+ expect(() =>
208
+ MemberAccessValidator.validateGlobalEntityAccess(
209
+ "GPIO",
210
+ "PIN0",
211
+ "register",
212
+ "Motor",
213
+ false,
214
+ { scopeMembers },
215
+ ),
216
+ ).not.toThrow();
217
+ });
218
+
219
+ it("does NOT throw when options is undefined", () => {
220
+ expect(() =>
221
+ MemberAccessValidator.validateGlobalEntityAccess(
222
+ "Color",
223
+ "Red",
224
+ "enum",
225
+ "Motor",
226
+ false,
227
+ undefined,
228
+ ),
229
+ ).not.toThrow();
230
+ });
231
+
232
+ // Shadowing detection tests (using rootIdentifier and knownEnums)
233
+ it("throws when scope member shadows global enum (resolved identifier differs)", () => {
234
+ const knownEnums = new Set(["Color"]);
235
+ expect(() =>
236
+ MemberAccessValidator.validateGlobalEntityAccess(
237
+ "Motor_Color", // resolved to scope member
238
+ "Red",
239
+ "enum",
240
+ "Motor",
241
+ false,
242
+ { rootIdentifier: "Color", knownEnums },
243
+ ),
244
+ ).toThrow(
245
+ "Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor' (scope member 'Color' shadows the global enum)",
246
+ );
247
+ });
248
+
249
+ it("does NOT throw for shadowing when isGlobalAccess is true", () => {
250
+ const knownEnums = new Set(["Color"]);
251
+ expect(() =>
252
+ MemberAccessValidator.validateGlobalEntityAccess(
253
+ "Motor_Color",
254
+ "Red",
255
+ "enum",
256
+ "Motor",
257
+ true,
258
+ { rootIdentifier: "Color", knownEnums },
259
+ ),
260
+ ).not.toThrow();
261
+ });
262
+
263
+ it("does NOT throw when root is not a known enum", () => {
264
+ const knownEnums = new Set(["OtherEnum"]);
265
+ expect(() =>
266
+ MemberAccessValidator.validateGlobalEntityAccess(
267
+ "Motor_Color",
268
+ "Red",
269
+ "enum",
270
+ "Motor",
271
+ false,
272
+ { rootIdentifier: "Color", knownEnums },
273
+ ),
274
+ ).not.toThrow();
275
+ });
276
+
277
+ it("does NOT throw when resolved name IS a known enum", () => {
278
+ const knownEnums = new Set(["Color", "Motor_Color"]);
279
+ expect(() =>
280
+ MemberAccessValidator.validateGlobalEntityAccess(
281
+ "Motor_Color",
282
+ "Red",
283
+ "enum",
284
+ "Motor",
285
+ false,
286
+ { rootIdentifier: "Color", knownEnums },
287
+ ),
288
+ ).not.toThrow();
289
+ });
184
290
  });
185
291
  });
@@ -90,6 +90,13 @@ export default class CodeGenState {
90
90
  */
91
91
  static symbolTable: SymbolTable = new SymbolTable();
92
92
 
93
+ /**
94
+ * External struct fields from C/C++ headers for initialization analysis.
95
+ * Maps struct name -> Set of non-array field names.
96
+ * Persists across per-file reset() calls, cleared at start of run.
97
+ */
98
+ static externalStructFields: Map<string, Set<string>> = new Map();
99
+
93
100
  // ===========================================================================
94
101
  // TYPE TRACKING
95
102
  // ===========================================================================
@@ -650,6 +657,39 @@ export default class CodeGenState {
650
657
  return this.symbols?.enumMembers.get(enumName);
651
658
  }
652
659
 
660
+ /**
661
+ * Build external struct fields from the symbol table.
662
+ * Called once per run after all headers are processed.
663
+ * Issue #355: Excludes array fields from init checking.
664
+ */
665
+ static buildExternalStructFields(): void {
666
+ this.externalStructFields.clear();
667
+ const allStructFields = this.symbolTable.getAllStructFields();
668
+
669
+ for (const [structName, fieldMap] of allStructFields) {
670
+ const nonArrayFields = new Set<string>();
671
+ for (const [fieldName, fieldInfo] of fieldMap) {
672
+ // Only include non-array fields in init checking
673
+ if (
674
+ !fieldInfo.arrayDimensions ||
675
+ fieldInfo.arrayDimensions.length === 0
676
+ ) {
677
+ nonArrayFields.add(fieldName);
678
+ }
679
+ }
680
+ if (nonArrayFields.size > 0) {
681
+ this.externalStructFields.set(structName, nonArrayFields);
682
+ }
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Get external struct fields for initialization analysis.
688
+ */
689
+ static getExternalStructFields(): Map<string, Set<string>> {
690
+ return this.externalStructFields;
691
+ }
692
+
653
693
  /**
654
694
  * Get function return type.
655
695
  */
@@ -453,4 +453,123 @@ describe("CodeGenState", () => {
453
453
  expect(CodeGenState.isLocalArray("myArr")).toBe(true);
454
454
  });
455
455
  });
456
+
457
+ describe("buildExternalStructFields", () => {
458
+ it("returns empty map when no struct fields exist", () => {
459
+ CodeGenState.buildExternalStructFields();
460
+ const result = CodeGenState.getExternalStructFields();
461
+ expect(result.size).toBe(0);
462
+ });
463
+
464
+ it("includes non-array fields in result", () => {
465
+ // Manually add struct fields to the symbol table
466
+ const structFields = new Map<
467
+ string,
468
+ Map<string, { type: string; arrayDimensions?: number[] }>
469
+ >();
470
+ const pointFields = new Map<
471
+ string,
472
+ { type: string; arrayDimensions?: number[] }
473
+ >();
474
+ pointFields.set("x", { type: "i32" });
475
+ pointFields.set("y", { type: "i32" });
476
+ structFields.set("Point", pointFields);
477
+
478
+ // Use restoreStructFields to populate the symbol table
479
+ CodeGenState.symbolTable.restoreStructFields(structFields);
480
+
481
+ CodeGenState.buildExternalStructFields();
482
+ const result = CodeGenState.getExternalStructFields();
483
+
484
+ expect(result.has("Point")).toBe(true);
485
+ const fields = result.get("Point");
486
+ expect(fields?.has("x")).toBe(true);
487
+ expect(fields?.has("y")).toBe(true);
488
+ });
489
+
490
+ it("excludes array fields from result (Issue #355)", () => {
491
+ const structFields = new Map<
492
+ string,
493
+ Map<string, { type: string; arrayDimensions?: number[] }>
494
+ >();
495
+ const bufferFields = new Map<
496
+ string,
497
+ { type: string; arrayDimensions?: number[] }
498
+ >();
499
+ bufferFields.set("size", { type: "u32" }); // Non-array
500
+ bufferFields.set("data", { type: "u8", arrayDimensions: [256] }); // Array
501
+
502
+ structFields.set("Buffer", bufferFields);
503
+ CodeGenState.symbolTable.restoreStructFields(structFields);
504
+
505
+ CodeGenState.buildExternalStructFields();
506
+ const result = CodeGenState.getExternalStructFields();
507
+
508
+ expect(result.has("Buffer")).toBe(true);
509
+ const fields = result.get("Buffer");
510
+ expect(fields?.has("size")).toBe(true); // Non-array included
511
+ expect(fields?.has("data")).toBe(false); // Array excluded
512
+ });
513
+
514
+ it("excludes structs with only array fields", () => {
515
+ const structFields = new Map<
516
+ string,
517
+ Map<string, { type: string; arrayDimensions?: number[] }>
518
+ >();
519
+ const arrayOnlyFields = new Map<
520
+ string,
521
+ { type: string; arrayDimensions?: number[] }
522
+ >();
523
+ arrayOnlyFields.set("items", { type: "u8", arrayDimensions: [10] });
524
+ arrayOnlyFields.set("values", { type: "i32", arrayDimensions: [5] });
525
+
526
+ structFields.set("ArrayOnly", arrayOnlyFields);
527
+ CodeGenState.symbolTable.restoreStructFields(structFields);
528
+
529
+ CodeGenState.buildExternalStructFields();
530
+ const result = CodeGenState.getExternalStructFields();
531
+
532
+ // Struct should not be included since all fields are arrays
533
+ expect(result.has("ArrayOnly")).toBe(false);
534
+ });
535
+
536
+ it("handles mixed structs correctly", () => {
537
+ const structFields = new Map<
538
+ string,
539
+ Map<string, { type: string; arrayDimensions?: number[] }>
540
+ >();
541
+
542
+ // Struct with mixed fields
543
+ const mixedFields = new Map<
544
+ string,
545
+ { type: string; arrayDimensions?: number[] }
546
+ >();
547
+ mixedFields.set("id", { type: "u32" });
548
+ mixedFields.set("name", { type: "string", arrayDimensions: [32] });
549
+ mixedFields.set("count", { type: "u16" });
550
+
551
+ // Struct with only non-array
552
+ const simpleFields = new Map<
553
+ string,
554
+ { type: string; arrayDimensions?: number[] }
555
+ >();
556
+ simpleFields.set("value", { type: "f32" });
557
+
558
+ structFields.set("Mixed", mixedFields);
559
+ structFields.set("Simple", simpleFields);
560
+ CodeGenState.symbolTable.restoreStructFields(structFields);
561
+
562
+ CodeGenState.buildExternalStructFields();
563
+ const result = CodeGenState.getExternalStructFields();
564
+
565
+ // Mixed struct should have only non-array fields
566
+ expect(result.get("Mixed")?.size).toBe(2);
567
+ expect(result.get("Mixed")?.has("id")).toBe(true);
568
+ expect(result.get("Mixed")?.has("count")).toBe(true);
569
+ expect(result.get("Mixed")?.has("name")).toBe(false);
570
+
571
+ // Simple struct should have its field
572
+ expect(result.get("Simple")?.has("value")).toBe(true);
573
+ });
574
+ });
456
575
  });
@@ -1,58 +0,0 @@
1
- /**
2
- * AnalyzerContextBuilder
3
- * Issue #591: Extracted from Transpiler.transpileSource() to reduce complexity
4
- *
5
- * Builds the context needed for running semantic analyzers, specifically
6
- * converting struct field information from the symbol table format to
7
- * the analyzer-compatible format.
8
- */
9
-
10
- import SymbolTable from "../symbols/SymbolTable";
11
-
12
- /**
13
- * Builds analyzer context from symbol table data.
14
- *
15
- * The analyzer requires struct fields as Map<string, Set<string>> (structName -> fieldNames)
16
- * while the symbol table stores more detailed IStructFieldInfo. This builder handles
17
- * the conversion, including Issue #355's requirement to exclude array fields.
18
- */
19
- class AnalyzerContextBuilder {
20
- /**
21
- * Build external struct fields map for analyzer from symbol table.
22
- *
23
- * Converts the symbol table's detailed struct field info to the simpler
24
- * format needed by InitializationAnalyzer.
25
- *
26
- * Issue #355: Excludes array fields from init checking since the analyzer
27
- * can't prove loop-based array initialization is complete.
28
- *
29
- * @param symbolTable - Symbol table containing struct definitions
30
- * @returns Map of struct name to set of non-array field names
31
- */
32
- static buildExternalStructFields(
33
- symbolTable: SymbolTable,
34
- ): Map<string, Set<string>> {
35
- const externalStructFields = new Map<string, Set<string>>();
36
- const allStructFields = symbolTable.getAllStructFields();
37
-
38
- for (const [structName, fieldMap] of allStructFields) {
39
- const nonArrayFields = new Set<string>();
40
- for (const [fieldName, fieldInfo] of fieldMap) {
41
- // Only include non-array fields in init checking
42
- if (
43
- !fieldInfo.arrayDimensions ||
44
- fieldInfo.arrayDimensions.length === 0
45
- ) {
46
- nonArrayFields.add(fieldName);
47
- }
48
- }
49
- if (nonArrayFields.size > 0) {
50
- externalStructFields.set(structName, nonArrayFields);
51
- }
52
- }
53
-
54
- return externalStructFields;
55
- }
56
- }
57
-
58
- export default AnalyzerContextBuilder;
@@ -1,137 +0,0 @@
1
- /**
2
- * Unit tests for AnalyzerContextBuilder
3
- * Issue #591: Tests for struct field conversion utility
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from "vitest";
7
-
8
- import AnalyzerContextBuilder from "../AnalyzerContextBuilder";
9
- import SymbolTable from "../../symbols/SymbolTable";
10
-
11
- describe("AnalyzerContextBuilder", () => {
12
- let symbolTable: SymbolTable;
13
-
14
- beforeEach(() => {
15
- symbolTable = new SymbolTable();
16
- });
17
-
18
- describe("buildExternalStructFields", () => {
19
- it("returns empty map when no struct fields exist", () => {
20
- const result =
21
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
22
-
23
- expect(result.size).toBe(0);
24
- });
25
-
26
- it("includes non-array fields in result", () => {
27
- // Manually add struct fields to the symbol table
28
- const structFields = new Map<
29
- string,
30
- Map<string, { type: string; arrayDimensions?: number[] }>
31
- >();
32
- const pointFields = new Map<
33
- string,
34
- { type: string; arrayDimensions?: number[] }
35
- >();
36
- pointFields.set("x", { type: "i32" });
37
- pointFields.set("y", { type: "i32" });
38
- structFields.set("Point", pointFields);
39
-
40
- // Use restoreStructFields to populate the symbol table
41
- symbolTable.restoreStructFields(structFields);
42
-
43
- const result =
44
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
45
-
46
- expect(result.has("Point")).toBe(true);
47
- const fields = result.get("Point");
48
- expect(fields?.has("x")).toBe(true);
49
- expect(fields?.has("y")).toBe(true);
50
- });
51
-
52
- it("excludes array fields from result (Issue #355)", () => {
53
- const structFields = new Map<
54
- string,
55
- Map<string, { type: string; arrayDimensions?: number[] }>
56
- >();
57
- const bufferFields = new Map<
58
- string,
59
- { type: string; arrayDimensions?: number[] }
60
- >();
61
- bufferFields.set("size", { type: "u32" }); // Non-array
62
- bufferFields.set("data", { type: "u8", arrayDimensions: [256] }); // Array
63
-
64
- structFields.set("Buffer", bufferFields);
65
- symbolTable.restoreStructFields(structFields);
66
-
67
- const result =
68
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
69
-
70
- expect(result.has("Buffer")).toBe(true);
71
- const fields = result.get("Buffer");
72
- expect(fields?.has("size")).toBe(true); // Non-array included
73
- expect(fields?.has("data")).toBe(false); // Array excluded
74
- });
75
-
76
- it("excludes structs with only array fields", () => {
77
- const structFields = new Map<
78
- string,
79
- Map<string, { type: string; arrayDimensions?: number[] }>
80
- >();
81
- const arrayOnlyFields = new Map<
82
- string,
83
- { type: string; arrayDimensions?: number[] }
84
- >();
85
- arrayOnlyFields.set("items", { type: "u8", arrayDimensions: [10] });
86
- arrayOnlyFields.set("values", { type: "i32", arrayDimensions: [5] });
87
-
88
- structFields.set("ArrayOnly", arrayOnlyFields);
89
- symbolTable.restoreStructFields(structFields);
90
-
91
- const result =
92
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
93
-
94
- // Struct should not be included since all fields are arrays
95
- expect(result.has("ArrayOnly")).toBe(false);
96
- });
97
-
98
- it("handles mixed structs correctly", () => {
99
- const structFields = new Map<
100
- string,
101
- Map<string, { type: string; arrayDimensions?: number[] }>
102
- >();
103
-
104
- // Struct with mixed fields
105
- const mixedFields = new Map<
106
- string,
107
- { type: string; arrayDimensions?: number[] }
108
- >();
109
- mixedFields.set("id", { type: "u32" });
110
- mixedFields.set("name", { type: "string", arrayDimensions: [32] });
111
- mixedFields.set("count", { type: "u16" });
112
-
113
- // Struct with only non-array
114
- const simpleFields = new Map<
115
- string,
116
- { type: string; arrayDimensions?: number[] }
117
- >();
118
- simpleFields.set("value", { type: "f32" });
119
-
120
- structFields.set("Mixed", mixedFields);
121
- structFields.set("Simple", simpleFields);
122
- symbolTable.restoreStructFields(structFields);
123
-
124
- const result =
125
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
126
-
127
- // Mixed struct should have only non-array fields
128
- expect(result.get("Mixed")?.size).toBe(2);
129
- expect(result.get("Mixed")?.has("id")).toBe(true);
130
- expect(result.get("Mixed")?.has("count")).toBe(true);
131
- expect(result.get("Mixed")?.has("name")).toBe(false);
132
-
133
- // Simple struct should have its field
134
- expect(result.get("Simple")?.has("value")).toBe(true);
135
- });
136
- });
137
- });