c-next 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +213 -69
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +4 -1
- package/src/transpiler/logic/IncludeExtractor.ts +12 -6
- package/src/transpiler/logic/__tests__/IncludeExtractor.test.ts +24 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +32 -24
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +3 -3
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +16 -16
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +8 -1
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +21 -6
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +15 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +34 -11
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +2 -1
- package/src/transpiler/output/codegen/generators/support/IncludeGenerator.ts +19 -7
- package/src/transpiler/output/codegen/generators/support/__tests__/IncludeGenerator.test.ts +68 -0
- package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +14 -2
- package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +3 -5
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +56 -8
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +5 -6
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +60 -2
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +14 -16
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +25 -0
- package/src/transpiler/state/CodeGenState.ts +40 -0
|
@@ -156,6 +156,44 @@ describe("IncludeGenerator", () => {
|
|
|
156
156
|
|
|
157
157
|
expect(result).toBe("#include <lib.h>");
|
|
158
158
|
});
|
|
159
|
+
|
|
160
|
+
it("transforms angle bracket .cnx include to .hpp in C++ mode", () => {
|
|
161
|
+
const result = transformIncludeDirective("#include <utils.cnx>", {
|
|
162
|
+
sourcePath: null,
|
|
163
|
+
cppMode: true,
|
|
164
|
+
});
|
|
165
|
+
expect(result).toBe("#include <utils.hpp>");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("resolves path from inputs with .hpp in C++ mode", () => {
|
|
169
|
+
vi.mocked(CnxFileResolver.findCnxFile).mockReturnValue(
|
|
170
|
+
"/project/src/Display/utils.cnx",
|
|
171
|
+
);
|
|
172
|
+
vi.mocked(CnxFileResolver.getRelativePathFromInputs).mockReturnValue(
|
|
173
|
+
"Display/utils.cnx",
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const result = transformIncludeDirective("#include <utils.cnx>", {
|
|
177
|
+
sourcePath: "/project/src/main.cnx",
|
|
178
|
+
includeDirs: ["/project/src/Display"],
|
|
179
|
+
inputs: ["/project/src"],
|
|
180
|
+
cppMode: true,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(result).toBe("#include <Display/utils.hpp>");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("falls back to .hpp in C++ mode when file not found", () => {
|
|
187
|
+
vi.mocked(CnxFileResolver.findCnxFile).mockReturnValue(null);
|
|
188
|
+
|
|
189
|
+
const result = transformIncludeDirective("#include <missing.cnx>", {
|
|
190
|
+
sourcePath: "/project/src/main.cnx",
|
|
191
|
+
inputs: ["/project/src"],
|
|
192
|
+
cppMode: true,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(result).toBe("#include <missing.hpp>");
|
|
196
|
+
});
|
|
159
197
|
});
|
|
160
198
|
|
|
161
199
|
// ==========================================================================
|
|
@@ -221,6 +259,28 @@ describe("IncludeGenerator", () => {
|
|
|
221
259
|
}),
|
|
222
260
|
).toThrow(/Referenced in:.*main\.cnx/);
|
|
223
261
|
});
|
|
262
|
+
|
|
263
|
+
it("transforms quoted .cnx include to .hpp in C++ mode", () => {
|
|
264
|
+
vi.mocked(CnxFileResolver.cnxFileExists).mockReturnValue(true);
|
|
265
|
+
|
|
266
|
+
const result = transformIncludeDirective('#include "helper.cnx"', {
|
|
267
|
+
sourcePath: "/project/src/main.cnx",
|
|
268
|
+
cppMode: true,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
expect(result).toBe('#include "helper.hpp"');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("transforms quoted include with relative path to .hpp in C++ mode", () => {
|
|
275
|
+
vi.mocked(CnxFileResolver.cnxFileExists).mockReturnValue(true);
|
|
276
|
+
|
|
277
|
+
const result = transformIncludeDirective('#include "../lib/utils.cnx"', {
|
|
278
|
+
sourcePath: "/project/src/main.cnx",
|
|
279
|
+
cppMode: true,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(result).toBe('#include "../lib/utils.hpp"');
|
|
283
|
+
});
|
|
224
284
|
});
|
|
225
285
|
|
|
226
286
|
// ==========================================================================
|
|
@@ -262,6 +322,14 @@ describe("IncludeGenerator", () => {
|
|
|
262
322
|
});
|
|
263
323
|
expect(result).toBe("int x = 5;");
|
|
264
324
|
});
|
|
325
|
+
|
|
326
|
+
it("passes through .h includes unchanged even in C++ mode", () => {
|
|
327
|
+
const result = transformIncludeDirective('#include "myheader.h"', {
|
|
328
|
+
sourcePath: "/project/main.cnx",
|
|
329
|
+
cppMode: true,
|
|
330
|
+
});
|
|
331
|
+
expect(result).toBe('#include "myheader.h"');
|
|
332
|
+
});
|
|
265
333
|
});
|
|
266
334
|
|
|
267
335
|
// ==========================================================================
|
|
@@ -66,22 +66,34 @@ class ArgumentGenerator {
|
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Handle rvalue argument (literals or complex expressions).
|
|
69
|
+
* Issue #872: Sets expectedType for MISRA 7.2 U suffix on unsigned literals.
|
|
69
70
|
*/
|
|
70
71
|
static handleRvalueArg(
|
|
71
72
|
ctx: Parser.ExpressionContext,
|
|
72
73
|
targetParamBaseType: string | undefined,
|
|
73
74
|
callbacks: IArgumentGeneratorCallbacks,
|
|
74
75
|
): string {
|
|
76
|
+
// Issue #872: Early return when no target type - no state management needed
|
|
75
77
|
if (!targetParamBaseType) {
|
|
76
78
|
return callbacks.generateExpression(ctx);
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
const cType = TYPE_MAP[targetParamBaseType];
|
|
80
82
|
if (!cType || cType === "void") {
|
|
81
|
-
|
|
83
|
+
// Issue #872: Suppress bare enum resolution in function args (requires ADR to change)
|
|
84
|
+
return CodeGenState.withExpectedType(
|
|
85
|
+
targetParamBaseType,
|
|
86
|
+
() => callbacks.generateExpression(ctx),
|
|
87
|
+
true, // suppressEnumResolution
|
|
88
|
+
);
|
|
82
89
|
}
|
|
83
90
|
|
|
84
|
-
|
|
91
|
+
// Issue #872: Suppress bare enum resolution in function args (requires ADR to change)
|
|
92
|
+
const value = CodeGenState.withExpectedType(
|
|
93
|
+
targetParamBaseType,
|
|
94
|
+
() => callbacks.generateExpression(ctx),
|
|
95
|
+
true, // suppressEnumResolution
|
|
96
|
+
);
|
|
85
97
|
|
|
86
98
|
// C++ mode: rvalues can bind to const T&
|
|
87
99
|
if (CodeGenState.cppMode) {
|
|
@@ -106,11 +106,9 @@ class ArrayInitHelper {
|
|
|
106
106
|
callbacks: IArrayInitCallbacks,
|
|
107
107
|
): string {
|
|
108
108
|
const typeName = callbacks.getTypeName(typeCtx);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
CodeGenState.expectedType = savedExpectedType;
|
|
113
|
-
return initValue;
|
|
109
|
+
return CodeGenState.withExpectedType(typeName, () =>
|
|
110
|
+
callbacks.generateExpression(expression),
|
|
111
|
+
);
|
|
114
112
|
}
|
|
115
113
|
|
|
116
114
|
/**
|
|
@@ -54,22 +54,36 @@ class AssignmentExpectedTypeResolver {
|
|
|
54
54
|
return AssignmentExpectedTypeResolver.resolveForSimpleIdentifier(baseId);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Case 2: Has
|
|
57
|
+
// Case 2: Has postfix ops - extract identifiers from chain
|
|
58
58
|
if (baseId && postfixOps.length > 0) {
|
|
59
59
|
const { identifiers, hasSubscript } = analyzePostfixOps(
|
|
60
60
|
baseId,
|
|
61
61
|
postfixOps,
|
|
62
62
|
);
|
|
63
63
|
|
|
64
|
-
//
|
|
64
|
+
// Case 2a: Member access only (no subscript)
|
|
65
65
|
if (identifiers.length >= 2 && !hasSubscript) {
|
|
66
66
|
return AssignmentExpectedTypeResolver.resolveForMemberChain(
|
|
67
67
|
identifiers,
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
// Case 2b: Simple array element access (arr[i] <- value)
|
|
72
|
+
// Issue #872: Resolve element type for MISRA 7.2 U suffix
|
|
73
|
+
if (identifiers.length === 1 && hasSubscript) {
|
|
74
|
+
return AssignmentExpectedTypeResolver.resolveForArrayElement(baseId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Case 2c: Member chain with array access (struct.arr[i] <- value)
|
|
78
|
+
// Issue #872: Walk chain and resolve element type
|
|
79
|
+
if (identifiers.length >= 2 && hasSubscript) {
|
|
80
|
+
return AssignmentExpectedTypeResolver.resolveForMemberArrayElement(
|
|
81
|
+
identifiers,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
70
84
|
}
|
|
71
85
|
|
|
72
|
-
// Case 3:
|
|
86
|
+
// Case 3: Complex patterns we can't resolve
|
|
73
87
|
return { expectedType: null, assignmentContext: null };
|
|
74
88
|
}
|
|
75
89
|
|
|
@@ -98,10 +112,49 @@ class AssignmentExpectedTypeResolver {
|
|
|
98
112
|
*
|
|
99
113
|
* Issue #452: Enables type-aware resolution of unqualified enum members
|
|
100
114
|
* for nested access (e.g., config.nested.field).
|
|
115
|
+
*
|
|
116
|
+
* Delegates to walkMemberChain shared implementation.
|
|
101
117
|
*/
|
|
102
118
|
private static resolveForMemberChain(
|
|
103
119
|
identifiers: string[],
|
|
104
120
|
): IExpectedTypeResult {
|
|
121
|
+
return AssignmentExpectedTypeResolver.walkMemberChain(identifiers);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve expected type for array element access.
|
|
126
|
+
* Issue #872: arr[i] <- value needs baseType for MISRA 7.2 U suffix.
|
|
127
|
+
*/
|
|
128
|
+
private static resolveForArrayElement(id: string): IExpectedTypeResult {
|
|
129
|
+
const typeInfo = CodeGenState.getVariableTypeInfo(id);
|
|
130
|
+
if (!typeInfo?.isArray) {
|
|
131
|
+
return { expectedType: null, assignmentContext: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Element type is the baseType (e.g., u8[10] -> "u8")
|
|
135
|
+
return { expectedType: typeInfo.baseType, assignmentContext: null };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Resolve expected type for member chain ending with array access.
|
|
140
|
+
* Issue #872: struct.arr[i] <- value needs element type for MISRA 7.2.
|
|
141
|
+
*
|
|
142
|
+
* Delegates to walkMemberChain which handles both member chain and
|
|
143
|
+
* member-array-element patterns identically (both return final field type).
|
|
144
|
+
*/
|
|
145
|
+
private static resolveForMemberArrayElement(
|
|
146
|
+
identifiers: string[],
|
|
147
|
+
): IExpectedTypeResult {
|
|
148
|
+
return AssignmentExpectedTypeResolver.walkMemberChain(identifiers);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Walk a struct member chain to find the final field's type.
|
|
153
|
+
* Shared implementation for both member chain and member-array-element patterns.
|
|
154
|
+
*
|
|
155
|
+
* Issue #831: Uses SymbolTable as single source of truth for struct fields.
|
|
156
|
+
*/
|
|
157
|
+
private static walkMemberChain(identifiers: string[]): IExpectedTypeResult {
|
|
105
158
|
if (identifiers.length < 2) {
|
|
106
159
|
return { expectedType: null, assignmentContext: null };
|
|
107
160
|
}
|
|
@@ -115,8 +168,6 @@ class AssignmentExpectedTypeResolver {
|
|
|
115
168
|
|
|
116
169
|
let currentStructType: string | undefined = rootTypeInfo.baseType;
|
|
117
170
|
|
|
118
|
-
// Walk through each member in the chain to find the final field's type
|
|
119
|
-
// Issue #831: Use SymbolTable as single source of truth for struct fields
|
|
120
171
|
for (let i = 1; i < identifiers.length && currentStructType; i++) {
|
|
121
172
|
const memberName = identifiers[i];
|
|
122
173
|
const memberType = CodeGenState.symbolTable?.getStructFieldType(
|
|
@@ -129,13 +180,10 @@ class AssignmentExpectedTypeResolver {
|
|
|
129
180
|
}
|
|
130
181
|
|
|
131
182
|
if (i === identifiers.length - 1) {
|
|
132
|
-
// Last field in chain - this is the assignment target's type
|
|
133
183
|
return { expectedType: memberType, assignmentContext: null };
|
|
134
184
|
} else if (CodeGenState.isKnownStruct(memberType)) {
|
|
135
|
-
// Intermediate field - continue walking if it's a struct
|
|
136
185
|
currentStructType = memberType;
|
|
137
186
|
} else {
|
|
138
|
-
// Intermediate field is not a struct - can't walk further
|
|
139
187
|
break;
|
|
140
188
|
}
|
|
141
189
|
}
|
|
@@ -522,8 +522,6 @@ class VariableDeclHelper {
|
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
const typeName = callbacks.getTypeName(typeCtx);
|
|
525
|
-
const savedExpectedType = CodeGenState.expectedType;
|
|
526
|
-
CodeGenState.expectedType = typeName;
|
|
527
525
|
|
|
528
526
|
// ADR-017: Validate enum type for initialization
|
|
529
527
|
EnumAssignmentValidator.validateEnumAssignment(typeName, ctx.expression()!);
|
|
@@ -533,10 +531,11 @@ class VariableDeclHelper {
|
|
|
533
531
|
getExpressionType: callbacks.getExpressionType,
|
|
534
532
|
});
|
|
535
533
|
|
|
536
|
-
|
|
537
|
-
CodeGenState.
|
|
538
|
-
|
|
539
|
-
|
|
534
|
+
// Issue #872: Set expectedType for MISRA 7.2 U suffix compliance
|
|
535
|
+
return CodeGenState.withExpectedType(
|
|
536
|
+
typeName,
|
|
537
|
+
() => `${decl} = ${callbacks.generateExpression(ctx.expression()!)}`,
|
|
538
|
+
);
|
|
540
539
|
}
|
|
541
540
|
|
|
542
541
|
// ========================================================================
|
package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts
CHANGED
|
@@ -183,7 +183,8 @@ describe("AssignmentExpectedTypeResolver", () => {
|
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
describe("array access", () => {
|
|
186
|
-
|
|
186
|
+
// Issue #872: Array element assignments need expectedType for MISRA 7.2 U suffix
|
|
187
|
+
it("should resolve expected type for simple array element access", () => {
|
|
187
188
|
CodeGenState.setVariableTypeInfo("arr", {
|
|
188
189
|
baseType: "u32",
|
|
189
190
|
bitWidth: 32,
|
|
@@ -194,8 +195,65 @@ describe("AssignmentExpectedTypeResolver", () => {
|
|
|
194
195
|
|
|
195
196
|
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
196
197
|
|
|
198
|
+
expect(result.expectedType).toBe("u32");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should resolve expected type for u8 array element access", () => {
|
|
202
|
+
CodeGenState.setVariableTypeInfo("buffer", {
|
|
203
|
+
baseType: "u8",
|
|
204
|
+
bitWidth: 8,
|
|
205
|
+
isArray: true,
|
|
206
|
+
isConst: false,
|
|
207
|
+
});
|
|
208
|
+
const target = parseAssignmentTarget("buffer[5]");
|
|
209
|
+
|
|
210
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
211
|
+
|
|
212
|
+
expect(result.expectedType).toBe("u8");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("should resolve expected type for struct member array access", () => {
|
|
216
|
+
CodeGenState.setVariableTypeInfo("pkt", {
|
|
217
|
+
baseType: "Packet",
|
|
218
|
+
bitWidth: 0,
|
|
219
|
+
isArray: false,
|
|
220
|
+
isConst: false,
|
|
221
|
+
});
|
|
222
|
+
setupStructFields("Packet", new Map([["header", "u8"]]));
|
|
223
|
+
// Mark header as an array field
|
|
224
|
+
if (CodeGenState.symbols) {
|
|
225
|
+
(
|
|
226
|
+
CodeGenState.symbols.structFieldArrays as Map<string, Set<string>>
|
|
227
|
+
).set("Packet", new Set(["header"]));
|
|
228
|
+
}
|
|
229
|
+
const target = parseAssignmentTarget("pkt.header[0]");
|
|
230
|
+
|
|
231
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
232
|
+
|
|
233
|
+
expect(result.expectedType).toBe("u8");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should resolve expected type for multi-dimensional array element", () => {
|
|
237
|
+
CodeGenState.setVariableTypeInfo("matrix", {
|
|
238
|
+
baseType: "u8",
|
|
239
|
+
bitWidth: 8,
|
|
240
|
+
isArray: true,
|
|
241
|
+
arrayDimensions: [4, 8],
|
|
242
|
+
isConst: false,
|
|
243
|
+
});
|
|
244
|
+
const target = parseAssignmentTarget("matrix[0][0]");
|
|
245
|
+
|
|
246
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
247
|
+
|
|
248
|
+
expect(result.expectedType).toBe("u8");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should return null for unknown array variable", () => {
|
|
252
|
+
const target = parseAssignmentTarget("unknown[0]");
|
|
253
|
+
|
|
254
|
+
const result = AssignmentExpectedTypeResolver.resolve(target);
|
|
255
|
+
|
|
197
256
|
expect(result.expectedType).toBeNull();
|
|
198
|
-
expect(result.assignmentContext).toBeNull();
|
|
199
257
|
});
|
|
200
258
|
});
|
|
201
259
|
});
|
|
@@ -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
|
-
|
|
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:
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
|
|
@@ -362,6 +370,7 @@ export default class CodeGenState {
|
|
|
362
370
|
this.indentLevel = 0;
|
|
363
371
|
this.inFunctionBody = false;
|
|
364
372
|
this.expectedType = null;
|
|
373
|
+
this.suppressBareEnumResolution = false;
|
|
365
374
|
this.mainArgsName = null;
|
|
366
375
|
this.assignmentContext = {
|
|
367
376
|
targetName: null,
|
|
@@ -421,6 +430,37 @@ export default class CodeGenState {
|
|
|
421
430
|
this.floatShadowCurrent.clear();
|
|
422
431
|
}
|
|
423
432
|
|
|
433
|
+
/**
|
|
434
|
+
* Execute a function with a temporary expectedType, restoring on completion.
|
|
435
|
+
* Issue #872: Extracted to eliminate duplicate save/restore pattern and add exception safety.
|
|
436
|
+
*
|
|
437
|
+
* @param type - The expected type to set (if falsy, no change is made)
|
|
438
|
+
* @param fn - The function to execute
|
|
439
|
+
* @param suppressEnumResolution - If true, suppress bare enum resolution (for MISRA-only contexts)
|
|
440
|
+
* @returns The result of the function
|
|
441
|
+
*/
|
|
442
|
+
static withExpectedType<T>(
|
|
443
|
+
type: string | undefined | null,
|
|
444
|
+
fn: () => T,
|
|
445
|
+
suppressEnumResolution: boolean = false,
|
|
446
|
+
): T {
|
|
447
|
+
if (!type) {
|
|
448
|
+
return fn();
|
|
449
|
+
}
|
|
450
|
+
const savedType = this.expectedType;
|
|
451
|
+
const savedSuppress = this.suppressBareEnumResolution;
|
|
452
|
+
this.expectedType = type;
|
|
453
|
+
if (suppressEnumResolution) {
|
|
454
|
+
this.suppressBareEnumResolution = true;
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
return fn();
|
|
458
|
+
} finally {
|
|
459
|
+
this.expectedType = savedType;
|
|
460
|
+
this.suppressBareEnumResolution = savedSuppress;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
424
464
|
// ===========================================================================
|
|
425
465
|
// CONVENIENCE LOOKUP METHODS
|
|
426
466
|
// ===========================================================================
|