c-next 0.2.9 → 0.2.11

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 (61) hide show
  1. package/dist/index.js +941 -210
  2. package/dist/index.js.map +4 -4
  3. package/package.json +1 -1
  4. package/src/transpiler/Transpiler.ts +102 -7
  5. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
  6. package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
  7. package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
  8. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
  9. package/src/transpiler/logic/symbols/SymbolTable.ts +150 -0
  10. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +98 -0
  11. package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
  12. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
  13. package/src/transpiler/logic/symbols/c/__tests__/PointerTypedef.test.ts +47 -0
  14. package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
  15. package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +118 -8
  16. package/src/transpiler/logic/symbols/c/index.ts +107 -30
  17. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +35 -0
  18. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
  19. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
  20. package/src/transpiler/output/codegen/CodeGenerator.ts +32 -1
  21. package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
  22. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +19 -14
  23. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
  24. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
  25. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
  26. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
  27. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
  28. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
  29. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
  30. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
  31. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
  32. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +20 -0
  33. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +23 -2
  34. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +51 -0
  35. package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
  36. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +5 -0
  37. package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +16 -0
  38. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +41 -5
  39. package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
  40. package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
  41. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
  42. package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
  43. package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
  44. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -4
  45. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
  46. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -0
  47. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
  48. package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
  49. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
  50. package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
  51. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
  52. package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
  53. package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
  54. package/src/transpiler/state/CodeGenState.ts +58 -0
  55. package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
  56. package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
  57. package/src/transpiler/types/ICachedFileEntry.ts +4 -0
  58. package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
  59. package/src/utils/BitUtils.ts +33 -4
  60. package/src/utils/cache/CacheManager.ts +17 -1
  61. package/src/utils/cache/__tests__/CacheManager.test.ts +1 -1
@@ -63,6 +63,7 @@ function setupStructFields(
63
63
  scopePrivateConstValues: new Map(),
64
64
  functionReturnTypes: new Map(),
65
65
  getSingleFunctionForVariable: () => null,
66
+ opaqueTypes: new Set(),
66
67
  hasPublicSymbols: () => false,
67
68
  };
68
69
  }
@@ -64,6 +64,7 @@ function setupSymbols(
64
64
  scopePrivateConstValues: new Map(),
65
65
  functionReturnTypes: new Map(),
66
66
  getSingleFunctionForVariable: () => null,
67
+ opaqueTypes: new Set(),
67
68
  hasPublicSymbols: () => false,
68
69
  };
69
70
  }
@@ -2,8 +2,9 @@
2
2
  * Unit tests for BitRangeHelper utility.
3
3
  * Tests bit range access code generation patterns.
4
4
  */
5
- import { describe, it, expect } from "vitest";
6
- import BitRangeHelper from "../BitRangeHelper";
5
+ import { describe, it, expect, beforeEach } from "vitest";
6
+ import BitRangeHelper from "../BitRangeHelper.js";
7
+ import CodeGenState from "../../../../state/CodeGenState.js";
7
8
 
8
9
  describe("BitRangeHelper", () => {
9
10
  describe("buildFloatBitReadExpr", () => {
@@ -114,6 +115,56 @@ describe("BitRangeHelper", () => {
114
115
  });
115
116
  });
116
117
 
118
+ describe("buildIntegerBitReadExpr with target type", () => {
119
+ beforeEach(() => {
120
+ CodeGenState.reset();
121
+ CodeGenState.cppMode = false;
122
+ });
123
+
124
+ it("adds cast when target type is narrower than source", () => {
125
+ const result = BitRangeHelper.buildIntegerBitReadExpr({
126
+ varName: "value",
127
+ start: "0",
128
+ mask: "0xFFU",
129
+ sourceType: "u32",
130
+ targetType: "u8",
131
+ });
132
+ expect(result).toBe("(uint8_t)((value) & 0xFFU)");
133
+ });
134
+
135
+ it("returns plain expression when no narrowing", () => {
136
+ const result = BitRangeHelper.buildIntegerBitReadExpr({
137
+ varName: "value",
138
+ start: "8",
139
+ mask: "0xFFFFU",
140
+ sourceType: "u32",
141
+ targetType: "u32",
142
+ });
143
+ expect(result).toBe("((value >> 8) & 0xFFFFU)");
144
+ });
145
+
146
+ it("returns plain expression when types not provided (backward compatible)", () => {
147
+ const result = BitRangeHelper.buildIntegerBitReadExpr({
148
+ varName: "value",
149
+ start: "0",
150
+ mask: "0xFFU",
151
+ });
152
+ expect(result).toBe("((value) & 0xFFU)");
153
+ });
154
+
155
+ it("uses static_cast in C++ mode", () => {
156
+ CodeGenState.cppMode = true;
157
+ const result = BitRangeHelper.buildIntegerBitReadExpr({
158
+ varName: "value",
159
+ start: "0",
160
+ mask: "0xFFU",
161
+ sourceType: "u32",
162
+ targetType: "u8",
163
+ });
164
+ expect(result).toBe("static_cast<uint8_t>(((value) & 0xFFU))");
165
+ });
166
+ });
167
+
117
168
  describe("getShadowVarName", () => {
118
169
  it("should prefix with __bits_", () => {
119
170
  expect(BitRangeHelper.getShadowVarName("value")).toBe("__bits_value");
@@ -44,6 +44,7 @@ function setupSymbols(
44
44
  scopePrivateConstValues: new Map(),
45
45
  functionReturnTypes: new Map(),
46
46
  getSingleFunctionForVariable: () => null,
47
+ opaqueTypes: new Set(),
47
48
  hasPublicSymbols: () => false,
48
49
  };
49
50
  }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Unit tests for NarrowingCastHelper
3
+ * Issue #845: MISRA C:2012 Rule 10.3 compliance
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach } from "vitest";
7
+ import NarrowingCastHelper from "../NarrowingCastHelper.js";
8
+ import CodeGenState from "../../../../state/CodeGenState.js";
9
+
10
+ describe("NarrowingCastHelper", () => {
11
+ beforeEach(() => {
12
+ CodeGenState.reset();
13
+ });
14
+
15
+ describe("needsCast", () => {
16
+ it("returns false for same type", () => {
17
+ expect(NarrowingCastHelper.needsCast("u32", "u32")).toBe(false);
18
+ expect(NarrowingCastHelper.needsCast("u8", "u8")).toBe(false);
19
+ expect(NarrowingCastHelper.needsCast("bool", "bool")).toBe(false);
20
+ });
21
+
22
+ it("returns false for widening (u8 -> u32)", () => {
23
+ expect(NarrowingCastHelper.needsCast("u8", "u32")).toBe(false);
24
+ expect(NarrowingCastHelper.needsCast("u16", "u32")).toBe(false);
25
+ expect(NarrowingCastHelper.needsCast("i8", "i32")).toBe(false);
26
+ });
27
+
28
+ it("returns true for narrowing (u32 -> u8)", () => {
29
+ expect(NarrowingCastHelper.needsCast("u32", "u8")).toBe(true);
30
+ expect(NarrowingCastHelper.needsCast("u32", "u16")).toBe(true);
31
+ expect(NarrowingCastHelper.needsCast("i32", "i8")).toBe(true);
32
+ });
33
+
34
+ it("returns true for int -> smaller unsigned (C promotion result)", () => {
35
+ expect(NarrowingCastHelper.needsCast("int", "u8")).toBe(true);
36
+ expect(NarrowingCastHelper.needsCast("int", "u16")).toBe(true);
37
+ expect(NarrowingCastHelper.needsCast("int", "i8")).toBe(true);
38
+ });
39
+
40
+ it("returns true for int -> bool (different essential type)", () => {
41
+ expect(NarrowingCastHelper.needsCast("int", "bool")).toBe(true);
42
+ expect(NarrowingCastHelper.needsCast("u32", "bool")).toBe(true);
43
+ });
44
+
45
+ it("returns true for char literal type -> u8", () => {
46
+ expect(NarrowingCastHelper.needsCast("int", "u8")).toBe(true);
47
+ });
48
+ });
49
+
50
+ describe("wrap (C mode)", () => {
51
+ beforeEach(() => {
52
+ CodeGenState.cppMode = false;
53
+ });
54
+
55
+ it("returns expression unchanged for same type", () => {
56
+ expect(NarrowingCastHelper.wrap("x", "u32", "u32")).toBe("x");
57
+ });
58
+
59
+ it("returns expression unchanged for widening", () => {
60
+ expect(NarrowingCastHelper.wrap("x", "u8", "u32")).toBe("x");
61
+ });
62
+
63
+ it("adds C cast for narrowing u32 -> u8", () => {
64
+ const expr = "((value >> 0U) & 0xFFU)";
65
+ expect(NarrowingCastHelper.wrap(expr, "u32", "u8")).toBe(
66
+ "(uint8_t)((value >> 0U) & 0xFFU)",
67
+ );
68
+ });
69
+
70
+ it("adds C cast for narrowing u32 -> u16", () => {
71
+ const expr = "((value >> 0U) & 0xFFFFU)";
72
+ expect(NarrowingCastHelper.wrap(expr, "u32", "u16")).toBe(
73
+ "(uint16_t)((value >> 0U) & 0xFFFFU)",
74
+ );
75
+ });
76
+
77
+ it("adds C cast for int -> u8 (C promotion result)", () => {
78
+ const expr = "((flags >> 3) & 0x7)";
79
+ expect(NarrowingCastHelper.wrap(expr, "int", "u8")).toBe(
80
+ "(uint8_t)((flags >> 3) & 0x7)",
81
+ );
82
+ });
83
+
84
+ it("uses != 0U comparison for bool target (MISRA 10.5)", () => {
85
+ const expr = "((flags >> 0) & 1)";
86
+ expect(NarrowingCastHelper.wrap(expr, "int", "bool")).toBe(
87
+ "((((flags >> 0) & 1)) != 0U)",
88
+ );
89
+ });
90
+
91
+ it("adds cast for char literal to u8", () => {
92
+ expect(NarrowingCastHelper.wrap("'A'", "int", "u8")).toBe("(uint8_t)'A'");
93
+ });
94
+ });
95
+
96
+ describe("wrap (C++ mode)", () => {
97
+ beforeEach(() => {
98
+ CodeGenState.cppMode = true;
99
+ });
100
+
101
+ it("uses static_cast for narrowing", () => {
102
+ const expr = "((value >> 0U) & 0xFFU)";
103
+ expect(NarrowingCastHelper.wrap(expr, "u32", "u8")).toBe(
104
+ "static_cast<uint8_t>(((value >> 0U) & 0xFFU))",
105
+ );
106
+ });
107
+
108
+ it("uses != 0U for bool (same as C mode)", () => {
109
+ const expr = "((flags >> 0) & 1)";
110
+ expect(NarrowingCastHelper.wrap(expr, "int", "bool")).toBe(
111
+ "((((flags >> 0) & 1)) != 0U)",
112
+ );
113
+ });
114
+ });
115
+
116
+ describe("getPromotedType", () => {
117
+ it("returns 'int' for u8 (promoted)", () => {
118
+ expect(NarrowingCastHelper.getPromotedType("u8")).toBe("int");
119
+ });
120
+
121
+ it("returns 'int' for i8 (promoted)", () => {
122
+ expect(NarrowingCastHelper.getPromotedType("i8")).toBe("int");
123
+ });
124
+
125
+ it("returns 'int' for u16 (promoted)", () => {
126
+ expect(NarrowingCastHelper.getPromotedType("u16")).toBe("int");
127
+ });
128
+
129
+ it("returns 'int' for i16 (promoted)", () => {
130
+ expect(NarrowingCastHelper.getPromotedType("i16")).toBe("int");
131
+ });
132
+
133
+ it("returns same type for u32 (no promotion)", () => {
134
+ expect(NarrowingCastHelper.getPromotedType("u32")).toBe("u32");
135
+ });
136
+
137
+ it("returns same type for i32 (no promotion)", () => {
138
+ expect(NarrowingCastHelper.getPromotedType("i32")).toBe("i32");
139
+ });
140
+
141
+ it("returns same type for u64 (no promotion)", () => {
142
+ expect(NarrowingCastHelper.getPromotedType("u64")).toBe("u64");
143
+ });
144
+
145
+ it("returns same type for i64 (no promotion)", () => {
146
+ expect(NarrowingCastHelper.getPromotedType("i64")).toBe("i64");
147
+ });
148
+
149
+ it("returns 'int' for bool (promoted)", () => {
150
+ expect(NarrowingCastHelper.getPromotedType("bool")).toBe("int");
151
+ });
152
+
153
+ it("returns same type for unknown types (conservative)", () => {
154
+ expect(NarrowingCastHelper.getPromotedType("custom_type")).toBe(
155
+ "custom_type",
156
+ );
157
+ });
158
+ });
159
+ });
@@ -39,6 +39,7 @@ function createMockSymbols(): ICodeGenSymbols {
39
39
  scopePrivateConstValues: new Map(),
40
40
  functionReturnTypes: new Map(),
41
41
  getSingleFunctionForVariable: () => null,
42
+ opaqueTypes: new Set(),
42
43
  hasPublicSymbols: () => false,
43
44
  };
44
45
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Map compound assignment operator to its binary operator equivalent.
3
+ *
4
+ * Shared constant used by:
5
+ * - SimpleHandler.ts: MISRA 10.3 compound assignment expansion
6
+ * - AtomicGenerator.ts: Atomic compound operation expansion
7
+ */
8
+ const COMPOUND_TO_BINARY: Record<string, string> = {
9
+ "+=": "+",
10
+ "-=": "-",
11
+ "*=": "*",
12
+ "/=": "/",
13
+ "%=": "%",
14
+ "&=": "&",
15
+ "|=": "|",
16
+ "^=": "^",
17
+ "<<=": "<<",
18
+ ">>=": ">>",
19
+ };
20
+
21
+ export default COMPOUND_TO_BINARY;
@@ -19,6 +19,8 @@ type IArrayAccessInfo = {
19
19
  widthExpr?: string;
20
20
  /** Type info for the variable being accessed */
21
21
  typeInfo?: TTypeInfo;
22
+ /** Target type for narrowing cast (e.g., "u8" when extracting to uint8_t) */
23
+ targetType?: string;
22
24
  /** Source line for error messages */
23
25
  line: number;
24
26
  };
@@ -278,6 +278,18 @@ export default class CodeGenState {
278
278
  /** Issue #473: IRQ wrappers for critical sections */
279
279
  static needsIrqWrappers: boolean = false;
280
280
 
281
+ // ===========================================================================
282
+ // OPAQUE TYPE SCOPE VARIABLES (Issue #948)
283
+ // ===========================================================================
284
+
285
+ /**
286
+ * Tracks scope variables with opaque (forward-declared) struct types.
287
+ * These are generated as pointers with NULL initialization and should
288
+ * be passed directly (not with &) since they're already pointers.
289
+ * Maps qualified name (e.g., "MyScope_widget") to true.
290
+ */
291
+ private static opaqueScopeVariables: Set<string> = new Set();
292
+
281
293
  // ===========================================================================
282
294
  // C++ MODE STATE (Issue #250)
283
295
  // ===========================================================================
@@ -400,6 +412,9 @@ export default class CodeGenState {
400
412
  this.pendingCppClassAssignments = [];
401
413
  this.selfIncludeAdded = false;
402
414
 
415
+ // Issue #948: Opaque scope variables (reset per-file)
416
+ this.opaqueScopeVariables = new Set();
417
+
403
418
  // Source paths
404
419
  this.sourcePath = null;
405
420
  this.includeDirs = [];
@@ -504,6 +519,24 @@ export default class CodeGenState {
504
519
  return this.symbols?.knownRegisters.has(name) ?? false;
505
520
  }
506
521
 
522
+ /**
523
+ * Issue #948: Check if a type name is an opaque (forward-declared) struct type.
524
+ * Opaque types are incomplete types that can only be used as pointers.
525
+ * Example: `typedef struct _widget_t widget_t;` without a body makes `widget_t` opaque.
526
+ */
527
+ static isOpaqueType(typeName: string): boolean {
528
+ return this.symbols?.opaqueTypes.has(typeName) ?? false;
529
+ }
530
+
531
+ /**
532
+ * Issue #958: Check if a type name is an external typedef struct type.
533
+ * External typedef struct types should use pointer semantics for scope variables.
534
+ * Unlike isOpaqueType, this returns true for both forward-declared and complete structs.
535
+ */
536
+ static isTypedefStructType(typeName: string): boolean {
537
+ return this.symbolTable?.isTypedefStructType(typeName) ?? false;
538
+ }
539
+
507
540
  /**
508
541
  * Get type info for a variable.
509
542
  * Checks local typeRegistry first, then falls back to SymbolTable
@@ -1046,6 +1079,31 @@ export default class CodeGenState {
1046
1079
  return this.floatShadowCurrent.has(name);
1047
1080
  }
1048
1081
 
1082
+ // ===========================================================================
1083
+ // OPAQUE SCOPE VARIABLE HELPERS (Issue #948)
1084
+ // ===========================================================================
1085
+
1086
+ /**
1087
+ * Mark a scope variable as having an opaque (forward-declared) struct type.
1088
+ * These are generated as pointers with NULL initialization.
1089
+ *
1090
+ * @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
1091
+ */
1092
+ static markOpaqueScopeVariable(qualifiedName: string): void {
1093
+ this.opaqueScopeVariables.add(qualifiedName);
1094
+ }
1095
+
1096
+ /**
1097
+ * Check if a scope variable has an opaque type (and is thus a pointer).
1098
+ * Used during access generation to determine if & prefix is needed.
1099
+ *
1100
+ * @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
1101
+ * @returns true if this is an opaque scope variable (already a pointer)
1102
+ */
1103
+ static isOpaqueScopeVariable(qualifiedName: string): boolean {
1104
+ return this.opaqueScopeVariables.has(qualifiedName);
1105
+ }
1106
+
1049
1107
  // ===========================================================================
1050
1108
  // C++ MODE HELPERS
1051
1109
  // ===========================================================================
@@ -69,6 +69,7 @@ function createMockSymbols(
69
69
  structFieldArrays: Map<string, Set<string>>;
70
70
  functionReturnTypes: Map<string, string>;
71
71
  scopeMemberVisibility: Map<string, Map<string, "public" | "private">>;
72
+ opaqueTypes: Set<string>;
72
73
  }> = {},
73
74
  ): ICodeGenSymbols {
74
75
  return {
@@ -95,6 +96,7 @@ function createMockSymbols(
95
96
  scopeVariableUsage: new Map(),
96
97
  scopePrivateConstValues: new Map(),
97
98
  functionReturnTypes: overrides.functionReturnTypes ?? new Map(),
99
+ opaqueTypes: overrides.opaqueTypes ?? new Set(),
98
100
  getSingleFunctionForVariable: () => null,
99
101
  hasPublicSymbols: () => false,
100
102
  };
@@ -714,6 +716,21 @@ describe("CodeGenState", () => {
714
716
  expect(CodeGenState.isKnownScope("MyScope")).toBe(true);
715
717
  expect(CodeGenState.isKnownScope("UnknownScope")).toBe(false);
716
718
  });
719
+
720
+ it("isOpaqueType returns false without symbols", () => {
721
+ CodeGenState.symbols = null;
722
+ expect(CodeGenState.isOpaqueType("widget_t")).toBe(false);
723
+ });
724
+
725
+ it("isOpaqueType returns true for opaque type", () => {
726
+ CodeGenState.symbols = createMockSymbols({
727
+ opaqueTypes: new Set(["widget_t", "display_t"]),
728
+ });
729
+
730
+ expect(CodeGenState.isOpaqueType("widget_t")).toBe(true);
731
+ expect(CodeGenState.isOpaqueType("display_t")).toBe(true);
732
+ expect(CodeGenState.isOpaqueType("Point")).toBe(false);
733
+ });
717
734
  });
718
735
 
719
736
  describe("Local Variable Helpers", () => {
@@ -848,4 +865,40 @@ describe("CodeGenState", () => {
848
865
  expect(result.get("Simple")?.has("value")).toBe(true);
849
866
  });
850
867
  });
868
+
869
+ describe("Opaque Scope Variable Helpers (Issue #948)", () => {
870
+ it("markOpaqueScopeVariable adds to opaqueScopeVariables", () => {
871
+ CodeGenState.markOpaqueScopeVariable("MyScope_widget");
872
+ expect(CodeGenState.isOpaqueScopeVariable("MyScope_widget")).toBe(true);
873
+ });
874
+
875
+ it("isOpaqueScopeVariable returns false for unknown variable", () => {
876
+ expect(CodeGenState.isOpaqueScopeVariable("Unknown_var")).toBe(false);
877
+ });
878
+
879
+ it("isOpaqueScopeVariable returns true for marked variable", () => {
880
+ CodeGenState.markOpaqueScopeVariable("Gui_display");
881
+ expect(CodeGenState.isOpaqueScopeVariable("Gui_display")).toBe(true);
882
+ });
883
+
884
+ it("reset clears opaqueScopeVariables", () => {
885
+ CodeGenState.markOpaqueScopeVariable("Test_opaque");
886
+ expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(true);
887
+
888
+ CodeGenState.reset();
889
+
890
+ expect(CodeGenState.isOpaqueScopeVariable("Test_opaque")).toBe(false);
891
+ });
892
+
893
+ it("handles multiple opaque scope variables", () => {
894
+ CodeGenState.markOpaqueScopeVariable("Scope1_widget");
895
+ CodeGenState.markOpaqueScopeVariable("Scope1_display");
896
+ CodeGenState.markOpaqueScopeVariable("Scope2_handle");
897
+
898
+ expect(CodeGenState.isOpaqueScopeVariable("Scope1_widget")).toBe(true);
899
+ expect(CodeGenState.isOpaqueScopeVariable("Scope1_display")).toBe(true);
900
+ expect(CodeGenState.isOpaqueScopeVariable("Scope2_handle")).toBe(true);
901
+ expect(CodeGenState.isOpaqueScopeVariable("Scope1_other")).toBe(false);
902
+ });
903
+ });
851
904
  });
@@ -35,6 +35,7 @@ function createMockSymbolInfo(enumName?: string): ICodeGenSymbols {
35
35
  scopePrivateConstValues: new Map(),
36
36
  functionReturnTypes: new Map(),
37
37
  getSingleFunctionForVariable: () => null,
38
+ opaqueTypes: new Set(),
38
39
  hasPublicSymbols: () => false,
39
40
  };
40
41
  }
@@ -18,6 +18,10 @@ interface ICachedFileEntry {
18
18
  needsStructKeyword?: string[];
19
19
  /** Issue #208: Enum bit widths from typed enums (enum name -> bit width) */
20
20
  enumBitWidth?: Record<string, number>;
21
+ /** Issue #948: Opaque types (forward-declared struct types) */
22
+ opaqueTypes?: string[];
23
+ /** Issue #958: Typedef struct types with source files ([typeName, sourceFile] pairs) */
24
+ typedefStructTypes?: Array<[string, string]>;
21
25
  }
22
26
 
23
27
  export default ICachedFileEntry;
@@ -113,6 +113,14 @@ interface ICodeGenSymbols {
113
113
  */
114
114
  readonly functionReturnTypes: ReadonlyMap<string, string>;
115
115
 
116
+ // === Opaque Types (Issue #948) ===
117
+
118
+ /**
119
+ * Issue #948: Types that are opaque (forward-declared structs).
120
+ * Variables of these types should be generated as pointers.
121
+ */
122
+ readonly opaqueTypes: ReadonlySet<string>;
123
+
116
124
  /**
117
125
  * Check if a scope variable is used in only one function.
118
126
  * Returns the function name if single-function, null otherwise.
@@ -1,3 +1,12 @@
1
+ import TYPE_MAP from "../transpiler/output/codegen/types/TYPE_MAP";
2
+
3
+ /**
4
+ * Types that need explicit cast for MISRA 10.3 compliance.
5
+ * These types are narrower than int (32-bit) and need casts after
6
+ * bit manipulation operations which promote to int.
7
+ */
8
+ const NARROW_TYPES = new Set(["u8", "u16", "i8", "i16"]);
9
+
1
10
  /**
2
11
  * Bit manipulation utilities for C code generation.
3
12
  * Pure functions that generate C code strings for bit operations.
@@ -102,6 +111,22 @@ class BitUtils {
102
111
  return `0x${value.toString(16).toUpperCase()}U`;
103
112
  }
104
113
 
114
+ /**
115
+ * Wrap an expression with a cast for MISRA 10.3 compliance on narrow types.
116
+ * Bit manipulation operations promote to int; this casts back to the target type.
117
+ *
118
+ * @param expr - The expression to potentially wrap
119
+ * @param targetType - The C-Next type name (e.g., "u8", "u16")
120
+ * @returns Expression wrapped with cast if narrow, or original expression
121
+ */
122
+ private static wrapNarrowCast(expr: string, targetType?: string): string {
123
+ if (!targetType || !NARROW_TYPES.has(targetType)) {
124
+ return expr;
125
+ }
126
+ const cType = TYPE_MAP[targetType] ?? targetType;
127
+ return `(${cType})(${expr})`;
128
+ }
129
+
105
130
  /**
106
131
  * Generate code to read a single bit from a value.
107
132
  * Pattern: ((target >> offset) & 1)
@@ -162,7 +187,8 @@ class BitUtils {
162
187
  const valueShift = is64Bit
163
188
  ? `((uint64_t)${intValue} << ${offset})`
164
189
  : `(${intValue} << ${offset})`;
165
- return `${target} = (${target} & ~(${one} << ${offset})) | ${valueShift};`;
190
+ const rhs = `(${target} & ~(${one} << ${offset})) | ${valueShift}`;
191
+ return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
166
192
  }
167
193
 
168
194
  /**
@@ -184,7 +210,8 @@ class BitUtils {
184
210
  targetType?: string,
185
211
  ): string {
186
212
  const mask = BitUtils.generateMask(width, targetType);
187
- return `${target} = (${target} & ~(${mask} << ${offset})) | ((${value} & ${mask}) << ${offset});`;
213
+ const rhs = `(${target} & ~(${mask} << ${offset})) | ((${value} & ${mask}) << ${offset})`;
214
+ return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
188
215
  }
189
216
 
190
217
  /**
@@ -209,7 +236,8 @@ class BitUtils {
209
236
  // boolToInt already returns unsigned values (1U/0U) for MISRA 10.1 compliance
210
237
  const castPrefix =
211
238
  targetType === "u64" || targetType === "i64" ? "(uint64_t)" : "";
212
- return `${target} = (${castPrefix}${intValue} << ${offset});`;
239
+ const rhs = `(${castPrefix}${intValue} << ${offset})`;
240
+ return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
213
241
  }
214
242
 
215
243
  /**
@@ -232,7 +260,8 @@ class BitUtils {
232
260
  targetType?: string,
233
261
  ): string {
234
262
  const mask = BitUtils.generateMask(width, targetType);
235
- return `${target} = ((${value} & ${mask}) << ${offset});`;
263
+ const rhs = `((${value} & ${mask}) << ${offset})`;
264
+ return `${target} = ${BitUtils.wrapNarrowCast(rhs, targetType)};`;
236
265
  }
237
266
  }
238
267
 
@@ -32,7 +32,7 @@ import ESourceLanguage from "../types/ESourceLanguage";
32
32
  const defaultFs = NodeFileSystem.instance;
33
33
 
34
34
  /** Current cache format version - increment when serialization format changes */
35
- const CACHE_VERSION = 4; // ADR-055 Phase 7: TAnySymbol replaces ISymbol
35
+ const CACHE_VERSION = 6; // Issue #958: Add typedefStructTypes to cache
36
36
 
37
37
  const TRANSPILER_VERSION = packageJson.version;
38
38
 
@@ -134,6 +134,8 @@ class CacheManager {
134
134
  structFields: Map<string, Map<string, IStructFieldInfo>>;
135
135
  needsStructKeyword: string[];
136
136
  enumBitWidth: Map<string, number>;
137
+ opaqueTypes: string[];
138
+ typedefStructTypes: Array<[string, string]>;
137
139
  } | null {
138
140
  if (!this.cache) return null;
139
141
 
@@ -173,6 +175,8 @@ class CacheManager {
173
175
  structFields,
174
176
  needsStructKeyword: cachedEntry.needsStructKeyword ?? [],
175
177
  enumBitWidth,
178
+ opaqueTypes: cachedEntry.opaqueTypes ?? [],
179
+ typedefStructTypes: cachedEntry.typedefStructTypes ?? [],
176
180
  };
177
181
  }
178
182
 
@@ -186,6 +190,8 @@ class CacheManager {
186
190
  structFields: Map<string, Map<string, IStructFieldInfo>>,
187
191
  needsStructKeyword?: string[],
188
192
  enumBitWidth?: Map<string, number>,
193
+ opaqueTypes?: string[],
194
+ typedefStructTypes?: Array<[string, string]>,
189
195
  ): void {
190
196
  if (!this.cache) return;
191
197
 
@@ -229,6 +235,8 @@ class CacheManager {
229
235
  structFields: serializedFields,
230
236
  needsStructKeyword,
231
237
  enumBitWidth: serializedEnumBitWidth,
238
+ opaqueTypes,
239
+ typedefStructTypes,
232
240
  };
233
241
 
234
242
  this.cache.setKey(filePath, entry);
@@ -266,6 +274,12 @@ class CacheManager {
266
274
  symbolTable,
267
275
  );
268
276
 
277
+ // Issue #948: Extract opaque types (forward-declared structs)
278
+ const opaqueTypes = symbolTable.getAllOpaqueTypes();
279
+
280
+ // Issue #958: Extract typedef struct types (all typedef'd structs)
281
+ const typedefStructTypes = symbolTable.getAllTypedefStructTypes();
282
+
269
283
  // Delegate to existing setSymbols method
270
284
  this.setSymbols(
271
285
  filePath,
@@ -273,6 +287,8 @@ class CacheManager {
273
287
  structFields,
274
288
  needsStructKeyword,
275
289
  enumBitWidth,
290
+ opaqueTypes,
291
+ typedefStructTypes,
276
292
  );
277
293
  }
278
294
 
@@ -1492,7 +1492,7 @@ describe("CacheManager", () => {
1492
1492
  const content = mockFs.getWrittenContent("/project/.cnx/config.json");
1493
1493
  expect(content).toBeDefined();
1494
1494
  const newConfig = JSON.parse(content!);
1495
- expect(newConfig.version).toBe(4); // Current CACHE_VERSION (ADR-055 Phase 7)
1495
+ expect(newConfig.version).toBe(6); // Current CACHE_VERSION (Issue #958 typedefStructTypes)
1496
1496
  });
1497
1497
 
1498
1498
  it("should not cache files that do not exist in IFileSystem", async () => {