c-next 0.1.67 → 0.1.69

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.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/transpiler/Transpiler.ts +5 -13
  3. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +41 -36
  4. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +29 -28
  5. package/src/transpiler/logic/analysis/__tests__/runAnalyzers.test.ts +32 -19
  6. package/src/transpiler/logic/analysis/runAnalyzers.ts +8 -14
  7. package/src/transpiler/output/codegen/CodeGenerator.ts +125 -135
  8. package/src/transpiler/output/codegen/TypeValidator.ts +2 -2
  9. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +2 -2
  10. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +29 -1
  11. package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +1 -3
  12. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +3 -1
  13. package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +49 -0
  14. package/src/transpiler/output/codegen/assignment/IAssignmentContext.ts +15 -0
  15. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +7 -0
  16. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +24 -17
  17. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +16 -5
  18. package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +9 -1
  19. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +18 -1
  20. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +9 -1
  21. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +9 -1
  22. package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +10 -1
  23. package/src/transpiler/output/codegen/assignment/handlers/__tests__/SpecialHandlers.test.ts +9 -1
  24. package/src/transpiler/output/codegen/assignment/handlers/__tests__/StringHandlers.test.ts +9 -1
  25. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +31 -10
  26. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +66 -4
  27. package/src/transpiler/output/codegen/helpers/MemberAccessValidator.ts +47 -6
  28. package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +6 -1
  29. package/src/transpiler/output/codegen/helpers/__tests__/MemberAccessValidator.test.ts +109 -3
  30. package/src/transpiler/state/CodeGenState.ts +75 -1
  31. package/src/transpiler/state/__tests__/CodeGenState.test.ts +124 -5
  32. package/src/transpiler/logic/analysis/AnalyzerContextBuilder.ts +0 -58
  33. package/src/transpiler/logic/analysis/__tests__/AnalyzerContextBuilder.test.ts +0 -137
@@ -20,26 +20,30 @@ function gen(): ICodeGenApi {
20
20
 
21
21
  /**
22
22
  * Handle simple array element: arr[i] <- value
23
+ *
24
+ * Uses resolvedTarget which includes scope prefix and subscript,
25
+ * e.g., "data[0]" inside scope ArrayBug -> "ArrayBug_data[0]"
23
26
  */
24
27
  function handleArrayElement(ctx: IAssignmentContext): string {
25
- const name = ctx.identifiers[0];
26
- const index = gen().generateExpression(ctx.subscripts[0]);
27
-
28
- return `${name}[${index}] ${ctx.cOp} ${ctx.generatedValue};`;
28
+ return `${ctx.resolvedTarget} ${ctx.cOp} ${ctx.generatedValue};`;
29
29
  }
30
30
 
31
31
  /**
32
32
  * Handle multi-dimensional array element: matrix[i][j] <- value
33
+ *
34
+ * Uses resolvedTarget which includes scope prefix and all subscripts.
35
+ * Uses resolvedBaseIdentifier for type lookups to support scoped arrays.
33
36
  */
34
37
  function handleMultiDimArrayElement(ctx: IAssignmentContext): string {
35
- const name = ctx.identifiers[0];
36
- const typeInfo = CodeGenState.typeRegistry.get(name);
38
+ // Use resolvedBaseIdentifier for type lookup (includes scope prefix)
39
+ // e.g., "ArrayBug_data" instead of "data"
40
+ const typeInfo = CodeGenState.typeRegistry.get(ctx.resolvedBaseIdentifier);
37
41
 
38
42
  // ADR-036: Compile-time bounds checking for constant indices
39
43
  if (typeInfo?.arrayDimensions) {
40
44
  const line = ctx.subscripts[0]?.start?.line ?? 0;
41
45
  TypeValidator.checkArrayBounds(
42
- name,
46
+ ctx.resolvedBaseIdentifier,
43
47
  [...typeInfo.arrayDimensions],
44
48
  [...ctx.subscripts],
45
49
  line,
@@ -47,11 +51,7 @@ function handleMultiDimArrayElement(ctx: IAssignmentContext): string {
47
51
  );
48
52
  }
49
53
 
50
- const indices = ctx.subscripts
51
- .map((e) => gen().generateExpression(e))
52
- .join("][");
53
-
54
- return `${name}[${indices}] ${ctx.cOp} ${ctx.generatedValue};`;
54
+ return `${ctx.resolvedTarget} ${ctx.cOp} ${ctx.generatedValue};`;
55
55
  }
56
56
 
57
57
  /**
@@ -69,7 +69,8 @@ function handleArraySlice(ctx: IAssignmentContext): string {
69
69
  );
70
70
  }
71
71
 
72
- const name = ctx.identifiers[0];
72
+ // Use resolvedBaseIdentifier for type lookup (includes scope prefix)
73
+ const name = ctx.resolvedBaseIdentifier;
73
74
  const typeInfo = CodeGenState.typeRegistry.get(name);
74
75
 
75
76
  // Get line number for error messages
@@ -77,10 +78,12 @@ function handleArraySlice(ctx: IAssignmentContext): string {
77
78
 
78
79
  // Validate 1D array only
79
80
  if (typeInfo?.arrayDimensions && typeInfo.arrayDimensions.length > 1) {
81
+ // Use raw identifier in error message for clarity
82
+ const rawName = ctx.identifiers[0];
80
83
  throw new Error(
81
84
  `${line}:0 Error: Slice assignment is only valid on one-dimensional arrays. ` +
82
- `'${name}' has ${typeInfo.arrayDimensions.length} dimensions. ` +
83
- `Access the innermost dimension first (e.g., ${name}[index][offset, length]).`,
85
+ `'${rawName}' has ${typeInfo.arrayDimensions.length} dimensions. ` +
86
+ `Access the innermost dimension first (e.g., ${rawName}[index][offset, length]).`,
84
87
  );
85
88
  }
86
89
 
@@ -109,17 +112,21 @@ function handleArraySlice(ctx: IAssignmentContext): string {
109
112
  } else if (typeInfo?.arrayDimensions?.[0]) {
110
113
  capacity = typeInfo.arrayDimensions[0];
111
114
  } else {
115
+ // Use raw identifier in error message for clarity
116
+ const rawName = ctx.identifiers[0];
112
117
  throw new Error(
113
- `${line}:0 Error: Cannot determine buffer size for '${name}' at compile time.`,
118
+ `${line}:0 Error: Cannot determine buffer size for '${rawName}' at compile time.`,
114
119
  );
115
120
  }
116
121
 
117
122
  // Bounds validation
118
123
  if (offsetValue + lengthValue > capacity) {
124
+ // Use raw identifier in error message for clarity
125
+ const rawName = ctx.identifiers[0];
119
126
  throw new Error(
120
127
  `${line}:0 Error: Slice assignment out of bounds: ` +
121
128
  `offset(${offsetValue}) + length(${lengthValue}) = ${offsetValue + lengthValue} ` +
122
- `exceeds buffer capacity(${capacity}) for '${name}'.`,
129
+ `exceeds buffer capacity(${capacity}) for '${rawName}'.`,
123
130
  );
124
131
  }
125
132
 
@@ -33,11 +33,14 @@ function validateNotCompound(ctx: IAssignmentContext): void {
33
33
  /**
34
34
  * Handle single bit on integer variable: flags[3] <- true
35
35
  * Also handles float bit indexing: f32Var[3] <- true
36
+ * Uses resolvedBaseIdentifier for proper scope prefix support.
36
37
  */
37
38
  function handleIntegerBit(ctx: IAssignmentContext): string {
38
39
  validateNotCompound(ctx);
39
40
 
40
- const name = ctx.identifiers[0];
41
+ // Use resolvedBaseIdentifier for type lookup and code generation
42
+ // e.g., "ArrayBug_flags" instead of "flags"
43
+ const name = ctx.resolvedBaseIdentifier;
41
44
  const bitIndex = gen().generateExpression(ctx.subscripts[0]);
42
45
  const typeInfo = CodeGenState.typeRegistry.get(name);
43
46
 
@@ -67,11 +70,13 @@ function handleIntegerBit(ctx: IAssignmentContext): string {
67
70
  /**
68
71
  * Handle bit range on integer variable: flags[0, 3] <- 5
69
72
  * Also handles float bit range: f32Var[0, 8] <- 0xFF
73
+ * Uses resolvedBaseIdentifier for proper scope prefix support.
70
74
  */
71
75
  function handleIntegerBitRange(ctx: IAssignmentContext): string {
72
76
  validateNotCompound(ctx);
73
77
 
74
- const name = ctx.identifiers[0];
78
+ // Use resolvedBaseIdentifier for type lookup and code generation
79
+ const name = ctx.resolvedBaseIdentifier;
75
80
  const start = gen().generateExpression(ctx.subscripts[0]);
76
81
  const width = gen().generateExpression(ctx.subscripts[1]);
77
82
  const typeInfo = CodeGenState.typeRegistry.get(name);
@@ -126,15 +131,19 @@ function handleStructMemberBit(ctx: IAssignmentContext): string {
126
131
 
127
132
  /**
128
133
  * Handle bit on multi-dimensional array element: matrix[i][j][FIELD_BIT] <- false
134
+ * Uses resolvedBaseIdentifier for proper scope prefix support.
129
135
  */
130
136
  function handleArrayElementBit(ctx: IAssignmentContext): string {
131
137
  validateNotCompound(ctx);
132
138
 
133
- const arrayName = ctx.identifiers[0];
139
+ // Use resolvedBaseIdentifier for type lookup and code generation
140
+ const arrayName = ctx.resolvedBaseIdentifier;
134
141
  const typeInfo = CodeGenState.typeRegistry.get(arrayName);
135
142
 
136
143
  if (!typeInfo?.arrayDimensions) {
137
- throw new Error(`Error: ${arrayName} is not an array`);
144
+ // Use raw identifier in error message for clarity
145
+ const rawName = ctx.identifiers[0];
146
+ throw new Error(`Error: ${rawName} is not an array`);
138
147
  }
139
148
 
140
149
  const numDims = typeInfo.arrayDimensions.length;
@@ -160,12 +169,14 @@ function handleArrayElementBit(ctx: IAssignmentContext): string {
160
169
  *
161
170
  * The target is a chain like array[idx].member or struct.field with a
162
171
  * bit range subscript [start, width] at the end.
172
+ * Uses resolvedBaseIdentifier for proper scope prefix support.
163
173
  */
164
174
  function handleStructChainBitRange(ctx: IAssignmentContext): string {
165
175
  validateNotCompound(ctx);
166
176
 
167
177
  // Build the base target from postfixOps, excluding the last one (the bit range)
168
- const baseId = ctx.identifiers[0];
178
+ // Use resolvedBaseIdentifier for the base to include scope prefix
179
+ const baseId = ctx.resolvedBaseIdentifier;
169
180
  const opsBeforeLast = ctx.postfixOps.slice(0, -1);
170
181
 
171
182
  let baseTarget = baseId;
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
16
16
  function createMockContext(
17
17
  overrides: Partial<IAssignmentContext> = {},
18
18
  ): IAssignmentContext {
19
+ // Default resolved values based on first identifier
20
+ const identifiers = overrides.identifiers ?? ["Counter", "value"];
21
+ const resolvedTarget = overrides.resolvedTarget ?? `${identifiers.join("_")}`;
22
+ const resolvedBaseIdentifier =
23
+ overrides.resolvedBaseIdentifier ?? identifiers[0];
24
+
19
25
  return {
20
- identifiers: ["Counter", "value"],
26
+ identifiers,
21
27
  subscripts: [],
22
28
  isCompound: false,
23
29
  cnextOp: "<-",
@@ -34,6 +40,8 @@ function createMockContext(
34
40
  isSimpleIdentifier: false,
35
41
  isSimpleThisAccess: false,
36
42
  isSimpleGlobalAccess: false,
43
+ resolvedTarget,
44
+ resolvedBaseIdentifier,
37
45
  ...overrides,
38
46
  } as IAssignmentContext;
39
47
  }
@@ -28,8 +28,14 @@ import HandlerTestUtils from "./handlerTestUtils";
28
28
  function createMockContext(
29
29
  overrides: Partial<IAssignmentContext> = {},
30
30
  ): IAssignmentContext {
31
+ // Default resolved values based on first identifier
32
+ const identifiers = overrides.identifiers ?? ["arr"];
33
+ const resolvedTarget = overrides.resolvedTarget ?? `${identifiers[0]}[i]`;
34
+ const resolvedBaseIdentifier =
35
+ overrides.resolvedBaseIdentifier ?? identifiers[0];
36
+
31
37
  return {
32
- identifiers: ["arr"],
38
+ identifiers,
33
39
  subscripts: [{ mockValue: "i", start: { line: 1 } } as never],
34
40
  isCompound: false,
35
41
  cnextOp: "<-",
@@ -46,6 +52,8 @@ function createMockContext(
46
52
  isSimpleIdentifier: false,
47
53
  isSimpleThisAccess: false,
48
54
  isSimpleGlobalAccess: false,
55
+ resolvedTarget,
56
+ resolvedBaseIdentifier,
49
57
  ...overrides,
50
58
  } as IAssignmentContext;
51
59
  }
@@ -96,6 +104,7 @@ describe("ArrayHandlers", () => {
96
104
  isCompound: true,
97
105
  cnextOp: "+<-",
98
106
  cOp: "+=",
107
+ resolvedTarget: "arr[0]",
99
108
  });
100
109
 
101
110
  const result = getHandler()!(ctx);
@@ -109,6 +118,8 @@ describe("ArrayHandlers", () => {
109
118
  });
110
119
  const ctx = createMockContext({
111
120
  identifiers: ["buffer"],
121
+ resolvedTarget: "buffer[idx]",
122
+ resolvedBaseIdentifier: "buffer",
112
123
  });
113
124
 
114
125
  const result = getHandler()!(ctx);
@@ -137,6 +148,8 @@ describe("ArrayHandlers", () => {
137
148
  { mockValue: "j", start: { line: 1 } } as never,
138
149
  ],
139
150
  subscriptDepth: 2,
151
+ resolvedTarget: "matrix[i][j]",
152
+ resolvedBaseIdentifier: "matrix",
140
153
  });
141
154
 
142
155
  const result = getHandler()!(ctx);
@@ -160,6 +173,8 @@ describe("ArrayHandlers", () => {
160
173
  { mockValue: "z", start: { line: 1 } } as never,
161
174
  ],
162
175
  subscriptDepth: 3,
176
+ resolvedTarget: "cube[x][y][z]",
177
+ resolvedBaseIdentifier: "cube",
163
178
  });
164
179
 
165
180
  const result = getHandler()!(ctx);
@@ -212,6 +227,8 @@ describe("ArrayHandlers", () => {
212
227
  ],
213
228
  isCompound: true,
214
229
  cOp: "-=",
230
+ resolvedTarget: "grid[0][1]",
231
+ resolvedBaseIdentifier: "grid",
215
232
  });
216
233
 
217
234
  const result = getHandler()!(ctx);
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
16
16
  function createMockContext(
17
17
  overrides: Partial<IAssignmentContext> = {},
18
18
  ): IAssignmentContext {
19
+ // Default resolved values based on first identifier
20
+ const identifiers = overrides.identifiers ?? ["flags"];
21
+ const resolvedTarget = overrides.resolvedTarget ?? `${identifiers[0]}[3]`;
22
+ const resolvedBaseIdentifier =
23
+ overrides.resolvedBaseIdentifier ?? identifiers[0];
24
+
19
25
  return {
20
- identifiers: ["flags"],
26
+ identifiers,
21
27
  subscripts: [{ mockValue: "3" } as never],
22
28
  isCompound: false,
23
29
  cnextOp: "<-",
@@ -34,6 +40,8 @@ function createMockContext(
34
40
  isSimpleIdentifier: false,
35
41
  isSimpleThisAccess: false,
36
42
  isSimpleGlobalAccess: false,
43
+ resolvedTarget,
44
+ resolvedBaseIdentifier,
37
45
  ...overrides,
38
46
  } as IAssignmentContext;
39
47
  }
@@ -25,8 +25,14 @@ import HandlerTestUtils from "./handlerTestUtils";
25
25
  function createMockContext(
26
26
  overrides: Partial<IAssignmentContext> = {},
27
27
  ): IAssignmentContext {
28
+ // Default resolved values based on first identifier
29
+ const identifiers = overrides.identifiers ?? ["flags", "Running"];
30
+ const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
31
+ const resolvedBaseIdentifier =
32
+ overrides.resolvedBaseIdentifier ?? identifiers[0];
33
+
28
34
  return {
29
- identifiers: ["flags", "Running"],
35
+ identifiers,
30
36
  subscripts: [],
31
37
  isCompound: false,
32
38
  cnextOp: "<-",
@@ -44,6 +50,8 @@ function createMockContext(
44
50
  isSimpleIdentifier: false,
45
51
  isSimpleThisAccess: false,
46
52
  isSimpleGlobalAccess: false,
53
+ resolvedTarget,
54
+ resolvedBaseIdentifier,
47
55
  ...overrides,
48
56
  } as IAssignmentContext;
49
57
  }
@@ -16,8 +16,15 @@ import HandlerTestUtils from "./handlerTestUtils";
16
16
  function createMockContext(
17
17
  overrides: Partial<IAssignmentContext> = {},
18
18
  ): IAssignmentContext {
19
+ // Default resolved values based on first identifier
20
+ const identifiers = overrides.identifiers ?? ["GPIO7", "DR_SET"];
21
+ const resolvedTarget =
22
+ overrides.resolvedTarget ?? `${identifiers.join("_")}[LED_BIT]`;
23
+ const resolvedBaseIdentifier =
24
+ overrides.resolvedBaseIdentifier ?? identifiers[0];
25
+
19
26
  return {
20
- identifiers: ["GPIO7", "DR_SET"],
27
+ identifiers,
21
28
  subscripts: [{ mockValue: "LED_BIT" } as never],
22
29
  isCompound: false,
23
30
  cnextOp: "<-",
@@ -34,6 +41,8 @@ function createMockContext(
34
41
  isSimpleIdentifier: false,
35
42
  isSimpleThisAccess: false,
36
43
  isSimpleGlobalAccess: false,
44
+ resolvedTarget,
45
+ resolvedBaseIdentifier,
37
46
  ...overrides,
38
47
  } as IAssignmentContext;
39
48
  }
@@ -16,8 +16,14 @@ import HandlerTestUtils from "./handlerTestUtils";
16
16
  function createMockContext(
17
17
  overrides: Partial<IAssignmentContext> = {},
18
18
  ): IAssignmentContext {
19
+ // Default resolved values based on first identifier
20
+ const identifiers = overrides.identifiers ?? ["counter"];
21
+ const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
22
+ const resolvedBaseIdentifier =
23
+ overrides.resolvedBaseIdentifier ?? identifiers[0];
24
+
19
25
  return {
20
- identifiers: ["counter"],
26
+ identifiers,
21
27
  subscripts: [],
22
28
  isCompound: true,
23
29
  cnextOp: "+<-",
@@ -34,6 +40,8 @@ function createMockContext(
34
40
  isSimpleIdentifier: true,
35
41
  isSimpleThisAccess: false,
36
42
  isSimpleGlobalAccess: false,
43
+ resolvedTarget,
44
+ resolvedBaseIdentifier,
37
45
  ...overrides,
38
46
  } as IAssignmentContext;
39
47
  }
@@ -16,14 +16,22 @@ import HandlerTestUtils from "./handlerTestUtils";
16
16
  function createMockContext(
17
17
  overrides: Partial<IAssignmentContext> = {},
18
18
  ): IAssignmentContext {
19
+ // Default resolved values based on first identifier
20
+ const identifiers = overrides.identifiers ?? ["testVar"];
21
+ const resolvedTarget = overrides.resolvedTarget ?? identifiers[0];
22
+ const resolvedBaseIdentifier =
23
+ overrides.resolvedBaseIdentifier ?? identifiers[0];
24
+
19
25
  return {
20
- identifiers: ["testVar"],
26
+ identifiers,
21
27
  subscripts: [],
22
28
  isCompound: false,
23
29
  cnextOp: "<-",
24
30
  cOp: "=",
25
31
  generatedValue: '"hello"',
26
32
  targetCtx: {} as never,
33
+ resolvedTarget,
34
+ resolvedBaseIdentifier,
27
35
  ...overrides,
28
36
  } as IAssignmentContext;
29
37
  }
@@ -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
 
@@ -89,7 +89,12 @@ class MemberSeparatorResolver {
89
89
  // Scope member access: Sensor.buffer -> Sensor_buffer
90
90
  // Works with or without global. prefix (both are valid syntax)
91
91
  if (deps.isKnownScope(identifierChain[0])) {
92
- deps.validateCrossScopeVisibility(identifierChain[0], memberName);
92
+ // Issue #779: Skip cross-scope validation for scoped register access
93
+ // Board.GPIO where Board_GPIO is a known register is valid
94
+ const scopedRegisterName = `${identifierChain[0]}_${memberName}`;
95
+ if (!deps.isKnownRegister(scopedRegisterName)) {
96
+ deps.validateCrossScopeVisibility(identifierChain[0], memberName);
97
+ }
93
98
  return "_";
94
99
  }
95
100