c-next 0.2.4 → 0.2.6

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 (51) hide show
  1. package/dist/index.js +561 -78
  2. package/dist/index.js.map +3 -3
  3. package/package.json +3 -1
  4. package/src/transpiler/Transpiler.ts +1 -1
  5. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +194 -8
  6. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +140 -0
  7. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +41 -0
  8. package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +11 -5
  9. package/src/transpiler/output/codegen/CodeGenerator.ts +195 -17
  10. package/src/transpiler/output/codegen/TypeResolver.ts +1 -1
  11. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +129 -0
  12. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +30 -5
  13. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +2 -2
  14. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +2 -2
  15. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +2 -2
  16. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +4 -4
  17. package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +4 -4
  18. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +8 -8
  19. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +5 -5
  20. package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +4 -4
  21. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +8 -1
  22. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +15 -3
  23. package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +5 -0
  24. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +63 -10
  25. package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +28 -6
  26. package/src/transpiler/output/codegen/helpers/ParameterDereferenceResolver.ts +12 -0
  27. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +30 -2
  28. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +15 -7
  29. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +8 -1
  30. package/src/transpiler/output/codegen/helpers/StringOperationsHelper.ts +1 -1
  31. package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +220 -0
  32. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +11 -0
  33. package/src/transpiler/output/codegen/helpers/VariableModifierBuilder.ts +16 -1
  34. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +5 -5
  35. package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +48 -36
  36. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +37 -0
  37. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +63 -0
  38. package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +209 -0
  39. package/src/transpiler/output/codegen/helpers/__tests__/VariableModifierBuilder.test.ts +34 -2
  40. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +1 -1
  41. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +1 -1
  42. package/src/transpiler/output/codegen/types/IParameterInput.ts +13 -0
  43. package/src/transpiler/output/codegen/types/ISeparatorContext.ts +7 -0
  44. package/src/transpiler/output/codegen/types/TParameterInfo.ts +12 -0
  45. package/src/transpiler/output/codegen/types/TTypeInfo.ts +1 -0
  46. package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +1 -1
  47. package/src/transpiler/state/CodeGenState.ts +21 -2
  48. package/src/utils/BitUtils.ts +17 -13
  49. package/src/{transpiler/output/codegen/utils → utils}/ExpressionUnwrapper.ts +1 -1
  50. package/src/utils/__tests__/BitUtils.test.ts +56 -56
  51. package/src/{transpiler/output/codegen/utils → utils}/__tests__/ExpressionUnwrapper.test.ts +2 -2
@@ -0,0 +1,220 @@
1
+ /**
2
+ * TypedefParamParser - Parses C function pointer typedef signatures
3
+ *
4
+ * Extracts parameter types from typedef strings like:
5
+ * "void (*)(widget_t *, const rect_t *, uint8_t *)"
6
+ * "void (*)(Point p)"
7
+ *
8
+ * Used by Issue #895 to determine if callback params should be pointers or values.
9
+ */
10
+
11
+ /**
12
+ * Parsed parameter info from a typedef.
13
+ */
14
+ interface ITypedefParam {
15
+ /** Full type string (e.g., "widget_t *", "const rect_t *", "uint8_t *") */
16
+ type: string;
17
+ /** Whether this is a pointer type */
18
+ isPointer: boolean;
19
+ /** Whether this has const qualifier */
20
+ isConst: boolean;
21
+ /** Base type without pointer/const (e.g., "widget_t", "rect_t", "uint8_t") */
22
+ baseType: string;
23
+ }
24
+
25
+ /**
26
+ * Parse result for a typedef signature.
27
+ */
28
+ interface ITypedefParseResult {
29
+ /** Return type */
30
+ returnType: string;
31
+ /** Parsed parameters */
32
+ params: ITypedefParam[];
33
+ }
34
+
35
+ class TypedefParamParser {
36
+ /**
37
+ * Parse a function pointer typedef type string.
38
+ *
39
+ * @param typedefType - The type string, e.g., "void (*)(widget_t *, const rect_t *, uint8_t *)"
40
+ * @returns Parsed result with return type and parameters, or null if parsing fails
41
+ */
42
+ static parse(typedefType: string): ITypedefParseResult | null {
43
+ // Expected format: "return_type (*)(param1, param2, ...)"
44
+ // Find the (*) marker first
45
+ const funcPtrIndex = typedefType.indexOf("(*)");
46
+ if (funcPtrIndex === -1) {
47
+ return null;
48
+ }
49
+
50
+ const returnType = typedefType.substring(0, funcPtrIndex).trim();
51
+
52
+ // Find the opening paren after (*)
53
+ const afterFuncPtr = typedefType.substring(funcPtrIndex + 3).trim();
54
+ if (!afterFuncPtr.startsWith("(")) {
55
+ return null;
56
+ }
57
+
58
+ // Extract params by finding the matching closing paren
59
+ const paramsStr = TypedefParamParser.extractParenContent(afterFuncPtr);
60
+ if (paramsStr === null) {
61
+ return null;
62
+ }
63
+
64
+ // Handle void or empty params
65
+ if (!paramsStr || paramsStr === "void") {
66
+ return { returnType, params: [] };
67
+ }
68
+
69
+ // Split by comma, handling nested parentheses
70
+ const paramStrings = TypedefParamParser.splitParams(paramsStr);
71
+ const params = paramStrings.map((p) => TypedefParamParser.parseParam(p));
72
+
73
+ return { returnType, params };
74
+ }
75
+
76
+ /**
77
+ * Extract content between matching parentheses, handling arbitrary nesting.
78
+ * @param str - String starting with '('
79
+ * @returns Content between outer parens, or null if no match
80
+ */
81
+ private static extractParenContent(str: string): string | null {
82
+ if (!str.startsWith("(")) {
83
+ return null;
84
+ }
85
+
86
+ let depth = 0;
87
+ for (let i = 0; i < str.length; i++) {
88
+ if (str[i] === "(") {
89
+ depth++;
90
+ } else if (str[i] === ")") {
91
+ depth--;
92
+ if (depth === 0) {
93
+ // Found matching close paren - return content between
94
+ return str.substring(1, i);
95
+ }
96
+ }
97
+ }
98
+
99
+ // No matching close paren found
100
+ return null;
101
+ }
102
+
103
+ /**
104
+ * Split parameter string by commas, respecting nested parentheses.
105
+ */
106
+ private static splitParams(paramsStr: string): string[] {
107
+ const params: string[] = [];
108
+ let current = "";
109
+ let depth = 0;
110
+
111
+ for (const char of paramsStr) {
112
+ if (char === "(") {
113
+ depth++;
114
+ current += char;
115
+ } else if (char === ")") {
116
+ depth--;
117
+ current += char;
118
+ } else if (char === "," && depth === 0) {
119
+ params.push(current.trim());
120
+ current = "";
121
+ } else {
122
+ current += char;
123
+ }
124
+ }
125
+
126
+ if (current.trim()) {
127
+ params.push(current.trim());
128
+ }
129
+
130
+ return params;
131
+ }
132
+
133
+ /**
134
+ * Parse a single parameter type string.
135
+ */
136
+ private static parseParam(paramStr: string): ITypedefParam {
137
+ const trimmed = paramStr.trim();
138
+
139
+ // Check for pointer
140
+ const isPointer = trimmed.includes("*");
141
+
142
+ // Check for const - handles both "const " (with space) and merged forms
143
+ // C grammar getText() strips spaces, so "const rect_t*" may appear merged
144
+ const isConst = /\bconst\b/.test(trimmed) || trimmed.startsWith("const");
145
+
146
+ // Extract base type (remove const, *, and param name if present)
147
+ let baseType = trimmed
148
+ .replaceAll(/\bconst\b/g, "") // Remove const (with word boundary)
149
+ .replace(/^const/, "") // Remove const at start (no space case) - only once
150
+ .replaceAll("*", "") // Remove pointers
151
+ .replaceAll(/\s+/g, " ") // Normalize whitespace
152
+ .trim();
153
+
154
+ // Remove trailing param name if present (e.g., "rect_t area" -> "rect_t")
155
+ // Only remove if there are multiple words (space-separated)
156
+ if (baseType.includes(" ")) {
157
+ baseType = baseType.replace(/\s+\w+$/, "");
158
+ }
159
+
160
+ // Handle struct keyword
161
+ if (baseType.startsWith("struct ")) {
162
+ baseType = baseType.substring(7);
163
+ }
164
+
165
+ return {
166
+ type: trimmed,
167
+ isPointer,
168
+ isConst,
169
+ baseType,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Get the parameter info at a given index, or null if not found.
175
+ */
176
+ private static getParamAt(
177
+ typedefType: string,
178
+ paramIndex: number,
179
+ ): ITypedefParam | null {
180
+ const parsed = TypedefParamParser.parse(typedefType);
181
+ if (!parsed || paramIndex >= parsed.params.length) {
182
+ return null;
183
+ }
184
+ return parsed.params[paramIndex];
185
+ }
186
+
187
+ /**
188
+ * Check if a parameter at a given index should be a pointer based on the typedef.
189
+ *
190
+ * @param typedefType - The typedef type string
191
+ * @param paramIndex - The parameter index (0-based)
192
+ * @returns true if the param should be a pointer, false for value, null if unknown
193
+ */
194
+ static shouldBePointer(
195
+ typedefType: string,
196
+ paramIndex: number,
197
+ ): boolean | null {
198
+ return (
199
+ TypedefParamParser.getParamAt(typedefType, paramIndex)?.isPointer ?? null
200
+ );
201
+ }
202
+
203
+ /**
204
+ * Check if a parameter at a given index should be const based on the typedef.
205
+ *
206
+ * @param typedefType - The typedef type string
207
+ * @param paramIndex - The parameter index (0-based)
208
+ * @returns true if the param should be const, false otherwise, null if unknown
209
+ */
210
+ static shouldBeConst(
211
+ typedefType: string,
212
+ paramIndex: number,
213
+ ): boolean | null {
214
+ return (
215
+ TypedefParamParser.getParamAt(typedefType, paramIndex)?.isConst ?? null
216
+ );
217
+ }
218
+ }
219
+
220
+ export default TypedefParamParser;
@@ -139,6 +139,8 @@ interface IVariableDeclCallbacks {
139
139
  ctx: Parser.VariableDeclarationContext,
140
140
  name: string,
141
141
  ) => void;
142
+ /** Issue #895 Bug B: Mark variable as pointer in type registry */
143
+ markVariableAsPointer: (name: string) => void;
142
144
  /** Get string concatenation operands */
143
145
  getStringConcatOperands: (
144
146
  ctx: Parser.ExpressionContext,
@@ -571,9 +573,13 @@ class VariableDeclHelper {
571
573
  }
572
574
 
573
575
  // Issue #696: Use helper for modifier extraction and validation
576
+ // Issue #852 (MISRA Rule 8.5): Pass hasInitializer and cppMode for correct extern behavior
577
+ const hasInitializer = ctx.expression() !== null;
574
578
  const modifiers = VariableModifierBuilder.build(
575
579
  ctx,
576
580
  CodeGenState.inFunctionBody,
581
+ hasInitializer,
582
+ CodeGenState.cppMode,
577
583
  );
578
584
 
579
585
  const name = ctx.IDENTIFIER().getText();
@@ -587,6 +593,11 @@ class VariableDeclHelper {
587
593
  // Track local variable metadata
588
594
  callbacks.trackLocalVariable(ctx, name);
589
595
 
596
+ // Issue #895 Bug B: If type was inferred as pointer, mark it in the registry
597
+ if (type.endsWith("*")) {
598
+ callbacks.markVariableAsPointer(name);
599
+ }
600
+
590
601
  // ADR-045: Handle bounded string type specially - early return
591
602
  const stringResult = StringDeclHelper.generateStringDecl(
592
603
  typeCtx,
@@ -46,12 +46,16 @@ class VariableModifierBuilder {
46
46
  *
47
47
  * @param ctx - Parser context with modifier methods
48
48
  * @param inFunctionBody - Whether we're inside a function body (affects extern)
49
+ * @param hasInitializer - Whether the variable has an initializer (affects extern in C mode)
50
+ * @param cppMode - Whether we're generating C++ code (affects extern behavior)
49
51
  * @returns Modifier strings ready for use in generated code
50
52
  * @throws Error if both atomic and volatile are specified
51
53
  */
52
54
  static build(
53
55
  ctx: IModifierContext,
54
56
  inFunctionBody: boolean,
57
+ hasInitializer: boolean = false,
58
+ cppMode: boolean = false,
55
59
  ): IVariableModifiers {
56
60
  const hasConst = ctx.constModifier?.() ?? false;
57
61
  const constMod = hasConst ? "const " : "";
@@ -59,7 +63,18 @@ class VariableModifierBuilder {
59
63
  const volatileMod = ctx.volatileModifier() ? "volatile " : "";
60
64
 
61
65
  // Issue #525: Add extern for top-level const in C++ for external linkage
62
- const externMod = hasConst && !inFunctionBody ? "extern " : "";
66
+ // In C++, const at file scope has internal linkage by default, so extern is needed.
67
+ //
68
+ // Issue #852 (MISRA Rule 8.5): In C mode, do NOT add extern to definitions
69
+ // (variables with initializers). The extern declaration comes from the header.
70
+ //
71
+ // Summary:
72
+ // - C mode + no initializer: extern (declaration)
73
+ // - C mode + initializer: NO extern (definition - MISRA 8.5)
74
+ // - C++ mode: ALWAYS extern for external linkage (both declarations and definitions)
75
+ const needsExtern =
76
+ hasConst && !inFunctionBody && (cppMode || !hasInitializer);
77
+ const externMod = needsExtern ? "extern " : "";
63
78
 
64
79
  // Validate: cannot use both atomic and volatile
65
80
  if (ctx.atomicModifier() && ctx.volatileModifier()) {
@@ -347,7 +347,7 @@ describe("FunctionContextManager", () => {
347
347
  const callbacks = createMockCallbacks();
348
348
  const param = createMockParam("x", "u32", { isPrimitive: true });
349
349
 
350
- FunctionContextManager.processParameter(param, callbacks);
350
+ FunctionContextManager.processParameter(param, callbacks, 0);
351
351
 
352
352
  const paramInfo = CodeGenState.currentParameters.get("x");
353
353
  expect(paramInfo).toBeDefined();
@@ -363,7 +363,7 @@ describe("FunctionContextManager", () => {
363
363
  isArray: true,
364
364
  });
365
365
 
366
- FunctionContextManager.processParameter(param, callbacks);
366
+ FunctionContextManager.processParameter(param, callbacks, 0);
367
367
 
368
368
  const paramInfo = CodeGenState.currentParameters.get("arr");
369
369
  expect(paramInfo).toBeDefined();
@@ -377,7 +377,7 @@ describe("FunctionContextManager", () => {
377
377
  isConst: true,
378
378
  });
379
379
 
380
- FunctionContextManager.processParameter(param, callbacks);
380
+ FunctionContextManager.processParameter(param, callbacks, 0);
381
381
 
382
382
  const paramInfo = CodeGenState.currentParameters.get("x");
383
383
  expect(paramInfo).toBeDefined();
@@ -391,7 +391,7 @@ describe("FunctionContextManager", () => {
391
391
  );
392
392
  const param = createMockParam("point", "Point", { isUserType: true });
393
393
 
394
- FunctionContextManager.processParameter(param, callbacks);
394
+ FunctionContextManager.processParameter(param, callbacks, 0);
395
395
 
396
396
  const paramInfo = CodeGenState.currentParameters.get("point");
397
397
  expect(paramInfo).toBeDefined();
@@ -406,7 +406,7 @@ describe("FunctionContextManager", () => {
406
406
  stringCapacity: 32,
407
407
  });
408
408
 
409
- FunctionContextManager.processParameter(param, callbacks);
409
+ FunctionContextManager.processParameter(param, callbacks, 0);
410
410
 
411
411
  const paramInfo = CodeGenState.currentParameters.get("name");
412
412
  expect(paramInfo).toBeDefined();
@@ -46,13 +46,15 @@ describe("MemberSeparatorResolver", () => {
46
46
  });
47
47
 
48
48
  const ctx = MemberSeparatorResolver.buildContext(
49
- "Motor",
50
- true, // hasGlobal
51
- false, // hasThis
52
- null,
53
- false, // isStructParam
49
+ {
50
+ firstId: "Motor",
51
+ hasGlobal: true,
52
+ hasThis: false,
53
+ currentScope: null,
54
+ isStructParam: false,
55
+ isCppAccess: false,
56
+ },
54
57
  deps,
55
- false, // isCppAccess
56
58
  );
57
59
 
58
60
  expect(ctx.isCrossScope).toBe(true);
@@ -65,13 +67,15 @@ describe("MemberSeparatorResolver", () => {
65
67
  });
66
68
 
67
69
  const ctx = MemberSeparatorResolver.buildContext(
68
- "GPIO7",
69
- true, // hasGlobal
70
- false, // hasThis
71
- null,
72
- false,
70
+ {
71
+ firstId: "GPIO7",
72
+ hasGlobal: true,
73
+ hasThis: false,
74
+ currentScope: null,
75
+ isStructParam: false,
76
+ isCppAccess: false,
77
+ },
73
78
  deps,
74
- false,
75
79
  );
76
80
 
77
81
  expect(ctx.isCrossScope).toBe(true);
@@ -83,13 +87,15 @@ describe("MemberSeparatorResolver", () => {
83
87
  });
84
88
 
85
89
  const ctx = MemberSeparatorResolver.buildContext(
86
- "CONTROL_REG",
87
- false, // hasGlobal
88
- true, // hasThis
89
- "Motor", // currentScope
90
- false,
90
+ {
91
+ firstId: "CONTROL_REG",
92
+ hasGlobal: false,
93
+ hasThis: true,
94
+ currentScope: "Motor",
95
+ isStructParam: false,
96
+ isCppAccess: false,
97
+ },
91
98
  deps,
92
- false,
93
99
  );
94
100
 
95
101
  expect(ctx.scopedRegName).toBe("Motor_CONTROL_REG");
@@ -100,13 +106,15 @@ describe("MemberSeparatorResolver", () => {
100
106
  const deps = createMockDeps();
101
107
 
102
108
  const ctx = MemberSeparatorResolver.buildContext(
103
- "CONTROL_REG",
104
- false,
105
- false, // no this
106
- "Motor",
107
- false,
109
+ {
110
+ firstId: "CONTROL_REG",
111
+ hasGlobal: false,
112
+ hasThis: false,
113
+ currentScope: "Motor",
114
+ isStructParam: false,
115
+ isCppAccess: false,
116
+ },
108
117
  deps,
109
- false,
110
118
  );
111
119
 
112
120
  expect(ctx.scopedRegName).toBe(null);
@@ -117,13 +125,15 @@ describe("MemberSeparatorResolver", () => {
117
125
  const deps = createMockDeps();
118
126
 
119
127
  const ctx = MemberSeparatorResolver.buildContext(
120
- "point",
121
- false,
122
- false,
123
- null,
124
- true, // isStructParam
128
+ {
129
+ firstId: "point",
130
+ hasGlobal: false,
131
+ hasThis: false,
132
+ currentScope: null,
133
+ isStructParam: true,
134
+ isCppAccess: false,
135
+ },
125
136
  deps,
126
- false,
127
137
  );
128
138
 
129
139
  expect(ctx.isStructParam).toBe(true);
@@ -133,13 +143,15 @@ describe("MemberSeparatorResolver", () => {
133
143
  const deps = createMockDeps();
134
144
 
135
145
  const ctx = MemberSeparatorResolver.buildContext(
136
- "SeaDash",
137
- true,
138
- false,
139
- null,
140
- false,
146
+ {
147
+ firstId: "SeaDash",
148
+ hasGlobal: true,
149
+ hasThis: false,
150
+ currentScope: null,
151
+ isStructParam: false,
152
+ isCppAccess: true,
153
+ },
141
154
  deps,
142
- true, // isCppAccess
143
155
  );
144
156
 
145
157
  expect(ctx.isCppAccess).toBe(true);
@@ -61,6 +61,7 @@ function createDefaultASTDeps(overrides?: {
61
61
  typeMap,
62
62
  isModified: overrides?.isModified ?? false,
63
63
  isPassByValue: overrides?.isPassByValue ?? false,
64
+ isCallbackCompatible: false,
64
65
  };
65
66
  }
66
67
 
@@ -422,5 +423,41 @@ describe("ParameterInputAdapter", () => {
422
423
  expect(result.arrayDimensions).toEqual(["5", "33"]);
423
424
  expect(result.isPassByReference).toBe(false);
424
425
  });
426
+
427
+ it("passes through forceConst from deps (Issue #895)", () => {
428
+ const ctx = getParameterContext("void foo(Point area) {}");
429
+ const deps = {
430
+ ...createDefaultASTDeps({ isKnownStruct: true }),
431
+ forceConst: true,
432
+ };
433
+
434
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
435
+
436
+ expect(result.forceConst).toBe(true);
437
+ });
438
+
439
+ it("passes through forcePassByReference and forceConst together", () => {
440
+ const ctx = getParameterContext("void foo(Point area) {}");
441
+ const deps = {
442
+ ...createDefaultASTDeps({ isKnownStruct: true }),
443
+ forcePassByReference: true,
444
+ forceConst: true,
445
+ };
446
+
447
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
448
+
449
+ expect(result.forcePointerSyntax).toBe(true);
450
+ expect(result.forceConst).toBe(true);
451
+ expect(result.isPassByReference).toBe(true);
452
+ });
453
+
454
+ it("forceConst defaults to undefined when not provided", () => {
455
+ const ctx = getParameterContext("void foo(Point area) {}");
456
+ const deps = createDefaultASTDeps({ isKnownStruct: true });
457
+
458
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
459
+
460
+ expect(result.forceConst).toBeUndefined();
461
+ });
425
462
  });
426
463
  });
@@ -312,4 +312,67 @@ describe("ParameterSignatureBuilder", () => {
312
312
  expect(result).toBe("const UnknownType data");
313
313
  });
314
314
  });
315
+
316
+ describe("callback-compatible parameters (Issue #895)", () => {
317
+ it("forceConst adds const from typedef signature", () => {
318
+ const input = createInput({
319
+ name: "area",
320
+ baseType: "rect_t",
321
+ mappedType: "rect_t",
322
+ isPassByReference: true,
323
+ forcePointerSyntax: true,
324
+ forceConst: true,
325
+ });
326
+
327
+ const result = ParameterSignatureBuilder.build(input, "&");
328
+
329
+ // Should use pointer (not reference) and add const from typedef
330
+ expect(result).toBe("const rect_t* area");
331
+ });
332
+
333
+ it("forcePointerSyntax uses pointer in C++ mode", () => {
334
+ const input = createInput({
335
+ name: "w",
336
+ baseType: "widget_t",
337
+ mappedType: "widget_t",
338
+ isPassByReference: true,
339
+ forcePointerSyntax: true,
340
+ });
341
+
342
+ const result = ParameterSignatureBuilder.build(input, "&");
343
+
344
+ // Should use pointer even in C++ mode (& suffix)
345
+ expect(result).toBe("widget_t* w");
346
+ });
347
+
348
+ it("forceConst without forcePointerSyntax still adds const", () => {
349
+ const input = createInput({
350
+ name: "data",
351
+ baseType: "Data",
352
+ mappedType: "Data",
353
+ isPassByReference: true,
354
+ forceConst: true,
355
+ });
356
+
357
+ const result = ParameterSignatureBuilder.build(input, "*");
358
+
359
+ expect(result).toBe("const Data* data");
360
+ });
361
+
362
+ it("forceConst combines with forcePointerSyntax in C++ mode", () => {
363
+ const input = createInput({
364
+ name: "buf",
365
+ baseType: "uint8_t",
366
+ mappedType: "uint8_t",
367
+ isPassByReference: true,
368
+ forcePointerSyntax: true,
369
+ forceConst: false, // typedef doesn't have const
370
+ });
371
+
372
+ const result = ParameterSignatureBuilder.build(input, "&");
373
+
374
+ // No const, but still uses pointer
375
+ expect(result).toBe("uint8_t* buf");
376
+ });
377
+ });
315
378
  });