c-next 0.2.8 → 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 (71) hide show
  1. package/dist/index.js +1019 -267
  2. package/dist/index.js.map +4 -4
  3. package/package.json +1 -1
  4. package/src/transpiler/Transpiler.ts +101 -8
  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/IncludeExtractor.ts +12 -6
  8. package/src/transpiler/logic/__tests__/IncludeExtractor.test.ts +24 -0
  9. package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
  10. package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
  11. package/src/transpiler/logic/symbols/SymbolTable.ts +84 -0
  12. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +43 -0
  13. package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
  14. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
  15. package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
  16. package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +69 -2
  17. package/src/transpiler/logic/symbols/c/index.ts +85 -30
  18. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +18 -0
  19. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
  20. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
  21. package/src/transpiler/output/codegen/CodeGenerator.ts +55 -25
  22. package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
  23. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +3 -3
  24. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +35 -30
  25. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
  26. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
  27. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
  28. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
  29. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
  30. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
  31. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
  32. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
  33. package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
  34. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +14 -0
  35. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +25 -3
  36. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +49 -0
  37. package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
  38. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +26 -6
  39. package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +31 -2
  40. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +72 -13
  41. package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
  42. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +2 -1
  43. package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
  44. package/src/transpiler/output/codegen/generators/support/IncludeGenerator.ts +19 -7
  45. package/src/transpiler/output/codegen/generators/support/__tests__/IncludeGenerator.test.ts +68 -0
  46. package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +14 -2
  47. package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
  48. package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +3 -5
  49. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +56 -8
  50. package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
  51. package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
  52. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -5
  53. package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
  54. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +61 -2
  55. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
  56. package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
  57. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
  58. package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
  59. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
  60. package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
  61. package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
  62. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +14 -16
  63. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +25 -0
  64. package/src/transpiler/state/CodeGenState.ts +89 -0
  65. package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
  66. package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
  67. package/src/transpiler/types/ICachedFileEntry.ts +2 -0
  68. package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
  69. package/src/utils/BitUtils.ts +33 -4
  70. package/src/utils/cache/CacheManager.ts +9 -1
  71. 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
  }
@@ -183,7 +184,8 @@ describe("AssignmentExpectedTypeResolver", () => {
183
184
  });
184
185
 
185
186
  describe("array access", () => {
186
- it("should return null for array access target", () => {
187
+ // Issue #872: Array element assignments need expectedType for MISRA 7.2 U suffix
188
+ it("should resolve expected type for simple array element access", () => {
187
189
  CodeGenState.setVariableTypeInfo("arr", {
188
190
  baseType: "u32",
189
191
  bitWidth: 32,
@@ -194,8 +196,65 @@ describe("AssignmentExpectedTypeResolver", () => {
194
196
 
195
197
  const result = AssignmentExpectedTypeResolver.resolve(target);
196
198
 
199
+ expect(result.expectedType).toBe("u32");
200
+ });
201
+
202
+ it("should resolve expected type for u8 array element access", () => {
203
+ CodeGenState.setVariableTypeInfo("buffer", {
204
+ baseType: "u8",
205
+ bitWidth: 8,
206
+ isArray: true,
207
+ isConst: false,
208
+ });
209
+ const target = parseAssignmentTarget("buffer[5]");
210
+
211
+ const result = AssignmentExpectedTypeResolver.resolve(target);
212
+
213
+ expect(result.expectedType).toBe("u8");
214
+ });
215
+
216
+ it("should resolve expected type for struct member array access", () => {
217
+ CodeGenState.setVariableTypeInfo("pkt", {
218
+ baseType: "Packet",
219
+ bitWidth: 0,
220
+ isArray: false,
221
+ isConst: false,
222
+ });
223
+ setupStructFields("Packet", new Map([["header", "u8"]]));
224
+ // Mark header as an array field
225
+ if (CodeGenState.symbols) {
226
+ (
227
+ CodeGenState.symbols.structFieldArrays as Map<string, Set<string>>
228
+ ).set("Packet", new Set(["header"]));
229
+ }
230
+ const target = parseAssignmentTarget("pkt.header[0]");
231
+
232
+ const result = AssignmentExpectedTypeResolver.resolve(target);
233
+
234
+ expect(result.expectedType).toBe("u8");
235
+ });
236
+
237
+ it("should resolve expected type for multi-dimensional array element", () => {
238
+ CodeGenState.setVariableTypeInfo("matrix", {
239
+ baseType: "u8",
240
+ bitWidth: 8,
241
+ isArray: true,
242
+ arrayDimensions: [4, 8],
243
+ isConst: false,
244
+ });
245
+ const target = parseAssignmentTarget("matrix[0][0]");
246
+
247
+ const result = AssignmentExpectedTypeResolver.resolve(target);
248
+
249
+ expect(result.expectedType).toBe("u8");
250
+ });
251
+
252
+ it("should return null for unknown array variable", () => {
253
+ const target = parseAssignmentTarget("unknown[0]");
254
+
255
+ const result = AssignmentExpectedTypeResolver.resolve(target);
256
+
197
257
  expect(result.expectedType).toBeNull();
198
- expect(result.assignmentContext).toBeNull();
199
258
  });
200
259
  });
201
260
  });
@@ -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
  };
@@ -277,29 +277,27 @@ class HeaderGeneratorUtils {
277
277
  lines.push("#include <stdint.h>", "#include <stdbool.h>");
278
278
  }
279
279
 
280
- // User includes
281
- // Issue #933: Transform .h to .hpp in C++ mode so headers include correct files
280
+ // User includes (already have correct extension from IncludeExtractor)
282
281
  if (options.userIncludes && options.userIncludes.length > 0) {
283
282
  for (const include of options.userIncludes) {
284
- const transformedInclude = options.cppMode
285
- ? include.replace(/\.h"/, '.hpp"').replace(/\.h>/, ".hpp>")
286
- : include;
287
- lines.push(transformedInclude);
283
+ lines.push(include);
288
284
  }
289
285
  }
290
286
 
291
287
  // External type header includes (skip duplicates of user includes)
292
- // Note: External C headers stay as .h - only user includes from .cnx files get
293
- // transformed to .hpp in C++ mode (those are C-Next generated headers)
294
- const userIncludeSet = new Set(
295
- options.userIncludes?.map((inc) =>
296
- options.cppMode
297
- ? inc.replace(/\.h"/, '.hpp"').replace(/\.h>/, ".hpp>")
298
- : inc,
299
- ) ?? [],
300
- );
288
+ // Note: headersToInclude comes from IncludeResolver which always produces .h
289
+ // (it runs before cppDetected is set). We need to match against userIncludes
290
+ // which may have .hpp in C++ mode, so normalize both for deduplication.
291
+ const userIncludeSet = new Set(options.userIncludes ?? []);
292
+ // Issue #941: Also add .h variants for dedup since headersToInclude uses .h
293
+ if (options.cppMode && options.userIncludes) {
294
+ for (const inc of options.userIncludes) {
295
+ // Add the .h version so we can match against headersToInclude
296
+ const hVersion = inc.replace(/\.hpp"/, '.h"').replace(/\.hpp>/, ".h>");
297
+ userIncludeSet.add(hVersion);
298
+ }
299
+ }
301
300
  for (const directive of headersToInclude) {
302
- // Don't transform external C headers - they should stay as .h
303
301
  if (!userIncludeSet.has(directive)) {
304
302
  lines.push(directive);
305
303
  }
@@ -542,6 +542,31 @@ describe("HeaderGeneratorUtils", () => {
542
542
 
543
543
  expect(lines[lines.length - 1]).toBe("");
544
544
  });
545
+
546
+ it("passes through userIncludes as-is (extension already correct from IncludeExtractor)", () => {
547
+ const lines = HeaderGeneratorUtils.generateIncludes(
548
+ {
549
+ userIncludes: ['#include "types.hpp"'],
550
+ cppMode: true,
551
+ },
552
+ new Set(),
553
+ );
554
+
555
+ expect(lines).toContain('#include "types.hpp"');
556
+ });
557
+
558
+ it("deduplicates headersToInclude against userIncludes with .h/.hpp normalization in C++ mode", () => {
559
+ const result = HeaderGeneratorUtils.generateIncludes(
560
+ {
561
+ userIncludes: ['#include "types.hpp"'],
562
+ cppMode: true,
563
+ },
564
+ new Set(['#include "types.h"']),
565
+ );
566
+ // Should NOT have duplicate - the .h version should be filtered
567
+ const typeIncludes = result.filter((l) => l.includes("types"));
568
+ expect(typeIncludes).toEqual(['#include "types.hpp"']);
569
+ });
545
570
  });
546
571
 
547
572
  describe("generateCppWrapperStart", () => {
@@ -220,6 +220,14 @@ export default class CodeGenState {
220
220
  /** Expected type for struct initializers and enum inference */
221
221
  static expectedType: string | null = null;
222
222
 
223
+ /**
224
+ * Suppress bare enum resolution even when expectedType is set.
225
+ * Issue #872: MISRA 7.2 requires expectedType for U suffix on function args,
226
+ * but bare enum resolution in function args was never allowed and changing
227
+ * that would require ADR approval.
228
+ */
229
+ static suppressBareEnumResolution: boolean = false;
230
+
223
231
  /** Track args parameter name for main() translation */
224
232
  static mainArgsName: string | null = null;
225
233
 
@@ -270,6 +278,18 @@ export default class CodeGenState {
270
278
  /** Issue #473: IRQ wrappers for critical sections */
271
279
  static needsIrqWrappers: boolean = false;
272
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
+
273
293
  // ===========================================================================
274
294
  // C++ MODE STATE (Issue #250)
275
295
  // ===========================================================================
@@ -362,6 +382,7 @@ export default class CodeGenState {
362
382
  this.indentLevel = 0;
363
383
  this.inFunctionBody = false;
364
384
  this.expectedType = null;
385
+ this.suppressBareEnumResolution = false;
365
386
  this.mainArgsName = null;
366
387
  this.assignmentContext = {
367
388
  targetName: null,
@@ -391,6 +412,9 @@ export default class CodeGenState {
391
412
  this.pendingCppClassAssignments = [];
392
413
  this.selfIncludeAdded = false;
393
414
 
415
+ // Issue #948: Opaque scope variables (reset per-file)
416
+ this.opaqueScopeVariables = new Set();
417
+
394
418
  // Source paths
395
419
  this.sourcePath = null;
396
420
  this.includeDirs = [];
@@ -421,6 +445,37 @@ export default class CodeGenState {
421
445
  this.floatShadowCurrent.clear();
422
446
  }
423
447
 
448
+ /**
449
+ * Execute a function with a temporary expectedType, restoring on completion.
450
+ * Issue #872: Extracted to eliminate duplicate save/restore pattern and add exception safety.
451
+ *
452
+ * @param type - The expected type to set (if falsy, no change is made)
453
+ * @param fn - The function to execute
454
+ * @param suppressEnumResolution - If true, suppress bare enum resolution (for MISRA-only contexts)
455
+ * @returns The result of the function
456
+ */
457
+ static withExpectedType<T>(
458
+ type: string | undefined | null,
459
+ fn: () => T,
460
+ suppressEnumResolution: boolean = false,
461
+ ): T {
462
+ if (!type) {
463
+ return fn();
464
+ }
465
+ const savedType = this.expectedType;
466
+ const savedSuppress = this.suppressBareEnumResolution;
467
+ this.expectedType = type;
468
+ if (suppressEnumResolution) {
469
+ this.suppressBareEnumResolution = true;
470
+ }
471
+ try {
472
+ return fn();
473
+ } finally {
474
+ this.expectedType = savedType;
475
+ this.suppressBareEnumResolution = savedSuppress;
476
+ }
477
+ }
478
+
424
479
  // ===========================================================================
425
480
  // CONVENIENCE LOOKUP METHODS
426
481
  // ===========================================================================
@@ -464,6 +519,15 @@ export default class CodeGenState {
464
519
  return this.symbols?.knownRegisters.has(name) ?? false;
465
520
  }
466
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
+
467
531
  /**
468
532
  * Get type info for a variable.
469
533
  * Checks local typeRegistry first, then falls back to SymbolTable
@@ -1006,6 +1070,31 @@ export default class CodeGenState {
1006
1070
  return this.floatShadowCurrent.has(name);
1007
1071
  }
1008
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
+
1009
1098
  // ===========================================================================
1010
1099
  // C++ MODE HELPERS
1011
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
  }