c-next 0.2.9 → 0.2.10

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 (60) hide show
  1. package/dist/index.js +814 -206
  2. package/dist/index.js.map +4 -4
  3. package/package.json +1 -1
  4. package/src/transpiler/Transpiler.ts +97 -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 +84 -0
  10. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +43 -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/collectors/FunctionCollector.ts +23 -5
  14. package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +69 -2
  15. package/src/transpiler/logic/symbols/c/index.ts +85 -30
  16. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +18 -0
  17. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
  18. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
  19. package/src/transpiler/output/codegen/CodeGenerator.ts +23 -1
  20. package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
  21. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +19 -14
  22. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
  23. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
  24. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
  25. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
  26. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
  27. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
  28. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
  29. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
  30. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
  31. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +14 -0
  32. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +17 -2
  33. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +49 -0
  34. package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
  35. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +5 -0
  36. package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +16 -0
  37. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +41 -5
  38. package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
  39. package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
  40. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
  41. package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
  42. package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
  43. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -4
  44. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
  45. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -0
  46. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
  47. package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
  48. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
  49. package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
  50. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
  51. package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
  52. package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
  53. package/src/transpiler/state/CodeGenState.ts +49 -0
  54. package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
  55. package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
  56. package/src/transpiler/types/ICachedFileEntry.ts +2 -0
  57. package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
  58. package/src/utils/BitUtils.ts +33 -4
  59. package/src/utils/cache/CacheManager.ts +9 -1
  60. package/src/utils/cache/__tests__/CacheManager.test.ts +1 -1
@@ -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,15 @@ 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
+
507
531
  /**
508
532
  * Get type info for a variable.
509
533
  * Checks local typeRegistry first, then falls back to SymbolTable
@@ -1046,6 +1070,31 @@ export default class CodeGenState {
1046
1070
  return this.floatShadowCurrent.has(name);
1047
1071
  }
1048
1072
 
1073
+ // ===========================================================================
1074
+ // OPAQUE SCOPE VARIABLE HELPERS (Issue #948)
1075
+ // ===========================================================================
1076
+
1077
+ /**
1078
+ * Mark a scope variable as having an opaque (forward-declared) struct type.
1079
+ * These are generated as pointers with NULL initialization.
1080
+ *
1081
+ * @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
1082
+ */
1083
+ static markOpaqueScopeVariable(qualifiedName: string): void {
1084
+ this.opaqueScopeVariables.add(qualifiedName);
1085
+ }
1086
+
1087
+ /**
1088
+ * Check if a scope variable has an opaque type (and is thus a pointer).
1089
+ * Used during access generation to determine if & prefix is needed.
1090
+ *
1091
+ * @param qualifiedName - The fully qualified variable name (e.g., "MyScope_widget")
1092
+ * @returns true if this is an opaque scope variable (already a pointer)
1093
+ */
1094
+ static isOpaqueScopeVariable(qualifiedName: string): boolean {
1095
+ return this.opaqueScopeVariables.has(qualifiedName);
1096
+ }
1097
+
1049
1098
  // ===========================================================================
1050
1099
  // C++ MODE HELPERS
1051
1100
  // ===========================================================================
@@ -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,8 @@ 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[];
21
23
  }
22
24
 
23
25
  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 = 5; // Issue #948: Add opaqueTypes to cache
36
36
 
37
37
  const TRANSPILER_VERSION = packageJson.version;
38
38
 
@@ -134,6 +134,7 @@ class CacheManager {
134
134
  structFields: Map<string, Map<string, IStructFieldInfo>>;
135
135
  needsStructKeyword: string[];
136
136
  enumBitWidth: Map<string, number>;
137
+ opaqueTypes: string[];
137
138
  } | null {
138
139
  if (!this.cache) return null;
139
140
 
@@ -173,6 +174,7 @@ class CacheManager {
173
174
  structFields,
174
175
  needsStructKeyword: cachedEntry.needsStructKeyword ?? [],
175
176
  enumBitWidth,
177
+ opaqueTypes: cachedEntry.opaqueTypes ?? [],
176
178
  };
177
179
  }
178
180
 
@@ -186,6 +188,7 @@ class CacheManager {
186
188
  structFields: Map<string, Map<string, IStructFieldInfo>>,
187
189
  needsStructKeyword?: string[],
188
190
  enumBitWidth?: Map<string, number>,
191
+ opaqueTypes?: string[],
189
192
  ): void {
190
193
  if (!this.cache) return;
191
194
 
@@ -229,6 +232,7 @@ class CacheManager {
229
232
  structFields: serializedFields,
230
233
  needsStructKeyword,
231
234
  enumBitWidth: serializedEnumBitWidth,
235
+ opaqueTypes,
232
236
  };
233
237
 
234
238
  this.cache.setKey(filePath, entry);
@@ -266,6 +270,9 @@ class CacheManager {
266
270
  symbolTable,
267
271
  );
268
272
 
273
+ // Issue #948: Extract opaque types (forward-declared structs)
274
+ const opaqueTypes = symbolTable.getAllOpaqueTypes();
275
+
269
276
  // Delegate to existing setSymbols method
270
277
  this.setSymbols(
271
278
  filePath,
@@ -273,6 +280,7 @@ class CacheManager {
273
280
  structFields,
274
281
  needsStructKeyword,
275
282
  enumBitWidth,
283
+ opaqueTypes,
276
284
  );
277
285
  }
278
286
 
@@ -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(5); // Current CACHE_VERSION (Issue #948 opaqueTypes)
1496
1496
  });
1497
1497
 
1498
1498
  it("should not cache files that do not exist in IFileSystem", async () => {