c-next 0.2.15 → 0.2.17

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 (67) hide show
  1. package/README.md +16 -0
  2. package/dist/index.js +1403 -427
  3. package/dist/index.js.map +3 -3
  4. package/grammar/CNext.g4 +4 -0
  5. package/package.json +5 -1
  6. package/src/transpiler/Transpiler.ts +90 -22
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
  9. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  10. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  11. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  12. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  13. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  14. package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
  15. package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
  16. package/src/transpiler/logic/symbols/SymbolTable.ts +54 -12
  17. package/src/transpiler/logic/symbols/SymbolUtils.ts +21 -8
  18. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +39 -4
  19. package/src/transpiler/logic/symbols/__tests__/SymbolUtils.test.ts +2 -1
  20. package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +2 -1
  21. package/src/transpiler/logic/symbols/cnext/__tests__/CNextResolver.integration.test.ts +5 -2
  22. package/src/transpiler/logic/symbols/cnext/__tests__/ScopeCollector.test.ts +5 -2
  23. package/src/transpiler/logic/symbols/cnext/collectors/ScopeCollector.ts +7 -2
  24. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  25. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  26. package/src/transpiler/logic/symbols/cpp/utils/DeclaratorUtils.ts +4 -2
  27. package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
  28. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +167 -18
  29. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
  30. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  31. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
  32. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +32 -8
  33. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +91 -1
  34. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  35. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
  36. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  37. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  38. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  39. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  40. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  41. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
  42. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  43. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  44. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  45. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  46. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  47. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  48. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
  49. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  50. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  51. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  52. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  53. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  54. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  55. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
  56. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  57. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  58. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  59. package/src/transpiler/state/CodeGenState.ts +71 -6
  60. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  61. package/src/transpiler/types/symbols/c/ICFieldInfo.ts +6 -2
  62. package/src/transpiler/types/symbols/cpp/ICppFieldInfo.ts +5 -2
  63. package/src/utils/LiteralUtils.ts +23 -0
  64. package/src/utils/ScopeUtils.ts +19 -0
  65. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  66. package/src/utils/__tests__/ScopeUtils.test.ts +10 -0
  67. package/src/utils/types/IParameterSymbol.ts +1 -0
@@ -18,6 +18,7 @@ import IGeneratorInput from "../IGeneratorInput";
18
18
  import IGeneratorState from "../IGeneratorState";
19
19
  import IOrchestrator from "../IOrchestrator";
20
20
  import BinaryExprUtils from "./BinaryExprUtils";
21
+ import CodeGenState from "../../../../state/CodeGenState";
21
22
 
22
23
  /**
23
24
  * Generator context passed to child generators.
@@ -116,6 +117,7 @@ const generateAndExpr = (
116
117
  * ADR-001: = becomes == in C
117
118
  * ADR-017: Enum type safety validation
118
119
  * ADR-045: String comparison via strcmp()
120
+ * Issue #1032: Clear expectedType for comparison operands
119
121
  */
120
122
  const generateEqualityExpr = (
121
123
  node: Parser.EqualityExpressionContext,
@@ -151,18 +153,14 @@ const generateEqualityExpr = (
151
153
  // Generate strcmp for string comparison - needs string.h
152
154
  effects.push({ type: "include", header: "string" });
153
155
 
154
- const leftResult = generateRelationalExpr(
155
- exprs[0],
156
- input,
157
- state,
158
- orchestrator,
159
- );
160
- const rightResult = generateRelationalExpr(
161
- exprs[1],
162
- input,
163
- state,
164
- orchestrator,
165
- );
156
+ // Issue #1032: Clear expectedType for equality comparisons.
157
+ // Use CodeGenState.withoutExpectedType() to clear the global state that
158
+ // generators read via getState(). The passed state is not used for
159
+ // expectedType lookup - generators read from CodeGenState directly.
160
+ const [leftResult, rightResult] = CodeGenState.withoutExpectedType(() => [
161
+ generateRelationalExpr(exprs[0], input, state, orchestrator),
162
+ generateRelationalExpr(exprs[1], input, state, orchestrator),
163
+ ]);
166
164
  effects.push(...leftResult.effects, ...rightResult.effects);
167
165
 
168
166
  const fullText = node.getText();
@@ -183,18 +181,29 @@ const generateEqualityExpr = (
183
181
  // Issue #152: Extract operators in order from parse tree children
184
182
  // ADR-001: C-Next uses = for equality, transpile to ==
185
183
  const operators = orchestrator.getOperatorsFromChildren(node);
186
- return accumulateBinaryExprs(
187
- exprs,
188
- operators,
189
- "=",
190
- generateRelationalExpr,
191
- { input, state, orchestrator },
192
- BinaryExprUtils.mapEqualityOperator,
184
+
185
+ // Issue #1032: Clear expectedType for equality comparisons.
186
+ // The U suffix for MISRA 7.2 compliance applies to assignments, not comparisons.
187
+ // Use CodeGenState.withoutExpectedType() to clear the global state that
188
+ // generators read via getState(). The passed state is not used for
189
+ // expectedType lookup - generators read from CodeGenState directly.
190
+ return CodeGenState.withoutExpectedType(() =>
191
+ accumulateBinaryExprs(
192
+ exprs,
193
+ operators,
194
+ "=",
195
+ generateRelationalExpr,
196
+ { input, state, orchestrator },
197
+ BinaryExprUtils.mapEqualityOperator,
198
+ ),
193
199
  );
194
200
  };
195
201
 
196
202
  /**
197
203
  * Generate C code for a relational expression.
204
+ * Issue #1032: Clear expectedType for comparison operands - MISRA 7.2 suffix
205
+ * should not apply to comparisons, only to assignments. This prevents
206
+ * `i32 < 0` from becoming `signedIdx < 0U` which changes comparison semantics.
198
207
  */
199
208
  const generateRelationalExpr = (
200
209
  node: Parser.RelationalExpressionContext,
@@ -210,11 +219,21 @@ const generateRelationalExpr = (
210
219
 
211
220
  // Issue #152: Extract operators in order from parse tree children
212
221
  const operators = orchestrator.getOperatorsFromChildren(node);
213
- return accumulateBinaryExprs(exprs, operators, "<", generateBitwiseOrExpr, {
214
- input,
215
- state,
216
- orchestrator,
217
- });
222
+
223
+ // Issue #1032: Clear expectedType for relational comparisons.
224
+ // The U suffix for MISRA 7.2 compliance applies to assignments, not comparisons.
225
+ // Comparing `i32 < 0` should NOT generate `signedIdx < 0U` because that
226
+ // changes semantics due to C's integer promotion rules.
227
+ // Use CodeGenState.withoutExpectedType() to clear the global state that
228
+ // generators read via getState(). The passed state is not used for
229
+ // expectedType lookup - generators read from CodeGenState directly.
230
+ return CodeGenState.withoutExpectedType(() =>
231
+ accumulateBinaryExprs(exprs, operators, "<", generateBitwiseOrExpr, {
232
+ input,
233
+ state,
234
+ orchestrator,
235
+ }),
236
+ );
218
237
  };
219
238
 
220
239
  /**
@@ -166,7 +166,8 @@ const _generateCFunctionArg = (
166
166
  }
167
167
 
168
168
  // Issue #948: Check if argument is an opaque scope variable (already a pointer)
169
- const isOpaqueScopeVar = CodeGenState.isOpaqueScopeVariable(argCode);
169
+ // Issue #996: ...including an element of an opaque-handle array (arr[i])
170
+ const isOpaqueScopeVar = CodeGenState.isOpaqueScopeVariableAccess(argCode);
170
171
 
171
172
  // Add & if argument needs address-of to match parameter type.
172
173
  // Issue #322: struct types passed to pointer params.
@@ -284,50 +285,54 @@ const generateFunctionCall = (
284
285
  // Get function signature once for all arguments
285
286
  const sig = input.functionSignatures.get(funcExpr);
286
287
 
287
- const args = argExprs
288
- .map((e, idx) => {
289
- // Get parameter type info from local signature or cross-file SymbolTable
290
- const resolved = CallExprUtils.resolveTargetParam(
291
- sig,
292
- idx,
293
- funcExpr,
294
- input.symbolTable,
295
- );
296
- const targetParam = resolved.param;
297
-
298
- // C/C++ function: use pass-by-value semantics
299
- if (!isCNextFunc) {
300
- return _generateCFunctionArg(e, targetParam, input, orchestrator);
301
- }
302
-
303
- // C-Next function: check if target parameter should be passed by value
304
- if (
305
- _shouldPassByValue(
306
- funcExpr,
288
+ // Issue #992: Clear inDeclarationInit for function call arguments — struct
289
+ // initializers inside function args need compound literals, not plain designated initializers.
290
+ const args = CodeGenState.withoutDeclarationInit(() =>
291
+ argExprs
292
+ .map((e, idx) => {
293
+ // Get parameter type info from local signature or cross-file SymbolTable
294
+ const resolved = CallExprUtils.resolveTargetParam(
295
+ sig,
307
296
  idx,
308
- targetParam,
309
- resolved.isCrossFile,
310
- orchestrator,
311
- )
312
- ) {
313
- // Issue #872: Set expectedType for MISRA 7.2 compliance, but suppress bare enum resolution
314
- const argCode = CodeGenState.withExpectedType(
315
- targetParam?.baseType,
316
- () => orchestrator.generateExpression(e),
317
- true, // suppressEnumResolution
318
- );
319
- return wrapWithCppEnumCast(
320
- argCode,
321
- e,
322
- targetParam?.baseType,
323
- orchestrator,
297
+ funcExpr,
298
+ input.symbolTable,
324
299
  );
325
- }
326
-
327
- // Target parameter is pass-by-reference: use & logic
328
- return orchestrator.generateFunctionArg(e, targetParam?.baseType);
329
- })
330
- .join(", ");
300
+ const targetParam = resolved.param;
301
+
302
+ // C/C++ function: use pass-by-value semantics
303
+ if (!isCNextFunc) {
304
+ return _generateCFunctionArg(e, targetParam, input, orchestrator);
305
+ }
306
+
307
+ // C-Next function: check if target parameter should be passed by value
308
+ if (
309
+ _shouldPassByValue(
310
+ funcExpr,
311
+ idx,
312
+ targetParam,
313
+ resolved.isCrossFile,
314
+ orchestrator,
315
+ )
316
+ ) {
317
+ // Issue #872: Set expectedType for MISRA 7.2 compliance, but suppress bare enum resolution
318
+ const argCode = CodeGenState.withExpectedType(
319
+ targetParam?.baseType,
320
+ () => orchestrator.generateExpression(e),
321
+ true, // suppressEnumResolution
322
+ );
323
+ return wrapWithCppEnumCast(
324
+ argCode,
325
+ e,
326
+ targetParam?.baseType,
327
+ orchestrator,
328
+ );
329
+ }
330
+
331
+ // Target parameter is pass-by-reference: use & logic
332
+ return orchestrator.generateFunctionArg(e, targetParam?.baseType);
333
+ })
334
+ .join(", "),
335
+ );
331
336
 
332
337
  return { code: `${funcExpr}(${args})`, effects };
333
338
  };
@@ -15,6 +15,7 @@ import TGeneratorEffect from "../TGeneratorEffect";
15
15
  import IGeneratorInput from "../IGeneratorInput";
16
16
  import IGeneratorState from "../IGeneratorState";
17
17
  import IOrchestrator from "../IOrchestrator";
18
+ import CodeGenState from "../../../../state/CodeGenState";
18
19
 
19
20
  /**
20
21
  * Generate C code for an expression (entry point).
@@ -74,9 +75,15 @@ const generateTernaryExpr = (
74
75
  orchestrator.validateTernaryConditionNoFunctionCall(condition);
75
76
 
76
77
  // Generate C output - parentheses already present from grammar
78
+ // Issue #992: Clear inDeclarationInit in ternary arms — struct initializers
79
+ // inside ternary branches need compound literals, not plain designated initializers.
77
80
  const condCode = orchestrator.generateOrExpr(condition);
78
- const trueCode = orchestrator.generateOrExpr(trueExpr);
79
- const falseCode = orchestrator.generateOrExpr(falseExpr);
81
+ const trueCode = CodeGenState.withoutDeclarationInit(() =>
82
+ orchestrator.generateOrExpr(trueExpr),
83
+ );
84
+ const falseCode = CodeGenState.withoutDeclarationInit(() =>
85
+ orchestrator.generateOrExpr(falseExpr),
86
+ );
80
87
 
81
88
  return { code: `(${condCode}) ? ${trueCode} : ${falseCode}`, effects };
82
89
  };
@@ -1477,4 +1477,48 @@ describe("CallExprGenerator", () => {
1477
1477
  expect(result.code).toBe("use_handle(my_handle)");
1478
1478
  });
1479
1479
  });
1480
+
1481
+ describe("inDeclarationInit clearing (Issue #992)", () => {
1482
+ it("clears inDeclarationInit during function argument generation", () => {
1483
+ CodeGenState.inDeclarationInit = true;
1484
+
1485
+ const argExprs = [createMockExpressionContext("myArg")];
1486
+ const argCtx = createMockArgListContext(argExprs);
1487
+ const input = createMockInput();
1488
+ const state = createMockState();
1489
+
1490
+ let flagDuringArg = true;
1491
+ const orchestrator = createMockOrchestrator({
1492
+ isCNextFunction: vi.fn(() => false),
1493
+ generateExpression: vi.fn((ctx: Parser.ExpressionContext) => {
1494
+ flagDuringArg = CodeGenState.inDeclarationInit;
1495
+ return ctx.getText();
1496
+ }),
1497
+ });
1498
+
1499
+ generateFunctionCall("myFunc", argCtx, input, state, orchestrator);
1500
+
1501
+ expect(flagDuringArg).toBe(false);
1502
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1503
+ });
1504
+
1505
+ it("restores inDeclarationInit after argument generation", () => {
1506
+ CodeGenState.inDeclarationInit = true;
1507
+
1508
+ const argExprs = [
1509
+ createMockExpressionContext("a"),
1510
+ createMockExpressionContext("b"),
1511
+ ];
1512
+ const argCtx = createMockArgListContext(argExprs);
1513
+ const input = createMockInput();
1514
+ const state = createMockState();
1515
+ const orchestrator = createMockOrchestrator({
1516
+ isCNextFunction: vi.fn(() => false),
1517
+ });
1518
+
1519
+ generateFunctionCall("myFunc", argCtx, input, state, orchestrator);
1520
+
1521
+ expect(CodeGenState.inDeclarationInit).toBe(true);
1522
+ });
1523
+ });
1480
1524
  });
@@ -1,9 +1,10 @@
1
- import { describe, it, expect, vi } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import expressionGenerators from "../ExpressionGenerator";
3
3
  import IGeneratorInput from "../../IGeneratorInput";
4
4
  import IGeneratorState from "../../IGeneratorState";
5
5
  import IOrchestrator from "../../IOrchestrator";
6
6
  import * as Parser from "../../../../../logic/parser/grammar/CNextParser";
7
+ import CodeGenState from "../../../../../state/CodeGenState";
7
8
 
8
9
  // ========================================================================
9
10
  // Test Helpers
@@ -455,6 +456,86 @@ describe("ExpressionGenerator", () => {
455
456
  });
456
457
  });
457
458
 
459
+ describe("inDeclarationInit clearing (Issue #992)", () => {
460
+ beforeEach(() => {
461
+ CodeGenState.reset();
462
+ });
463
+
464
+ it("clears inDeclarationInit in ternary true and false arms", () => {
465
+ CodeGenState.inDeclarationInit = true;
466
+
467
+ const condition = createMockOrExpr("x > 0");
468
+ const trueExpr = createMockOrExpr("a");
469
+ const falseExpr = createMockOrExpr("b");
470
+ const ctx = createMockTernaryContext([condition, trueExpr, falseExpr]);
471
+
472
+ const input = createMockInput();
473
+ const state = createMockState();
474
+
475
+ const flagDuringTrue: boolean[] = [];
476
+ const flagDuringFalse: boolean[] = [];
477
+
478
+ const orchestrator = createMockOrchestrator();
479
+ let callCount = 0;
480
+ (
481
+ orchestrator.generateOrExpr as ReturnType<typeof vi.fn>
482
+ ).mockImplementation((ctx: Parser.OrExpressionContext) => {
483
+ callCount++;
484
+ if (callCount === 2) {
485
+ flagDuringTrue.push(CodeGenState.inDeclarationInit);
486
+ } else if (callCount === 3) {
487
+ flagDuringFalse.push(CodeGenState.inDeclarationInit);
488
+ }
489
+ return ctx.getText();
490
+ });
491
+
492
+ expressionGenerators.generateTernaryExpr(
493
+ ctx,
494
+ input,
495
+ state,
496
+ orchestrator,
497
+ );
498
+
499
+ expect(flagDuringTrue).toEqual([false]);
500
+ expect(flagDuringFalse).toEqual([false]);
501
+ expect(CodeGenState.inDeclarationInit).toBe(true);
502
+ });
503
+
504
+ it("does not affect condition generation", () => {
505
+ CodeGenState.inDeclarationInit = true;
506
+
507
+ const condition = createMockOrExpr("x > 0");
508
+ const trueExpr = createMockOrExpr("a");
509
+ const falseExpr = createMockOrExpr("b");
510
+ const ctx = createMockTernaryContext([condition, trueExpr, falseExpr]);
511
+
512
+ const input = createMockInput();
513
+ const state = createMockState();
514
+
515
+ let flagDuringCondition = false;
516
+ const orchestrator = createMockOrchestrator();
517
+ let callCount = 0;
518
+ (
519
+ orchestrator.generateOrExpr as ReturnType<typeof vi.fn>
520
+ ).mockImplementation((ctx: Parser.OrExpressionContext) => {
521
+ callCount++;
522
+ if (callCount === 1) {
523
+ flagDuringCondition = CodeGenState.inDeclarationInit;
524
+ }
525
+ return ctx.getText();
526
+ });
527
+
528
+ expressionGenerators.generateTernaryExpr(
529
+ ctx,
530
+ input,
531
+ state,
532
+ orchestrator,
533
+ );
534
+
535
+ expect(flagDuringCondition).toBe(true);
536
+ });
537
+ });
538
+
458
539
  describe("effects", () => {
459
540
  it("returns empty effects array for non-ternary", () => {
460
541
  const orExpr = createMockOrExpr("value");
@@ -60,6 +60,13 @@ interface IFromASTDeps {
60
60
  * When the C typedef has `const T*`, this preserves const on the generated param.
61
61
  */
62
62
  forceConst?: boolean;
63
+
64
+ /**
65
+ * Issue #995: Check if a type is an opaque handle (incomplete struct typedef).
66
+ * Opaque handles should not get auto-const because they must be passed to
67
+ * C APIs that expect non-const pointers.
68
+ */
69
+ isOpaqueType?: (typeName: string) => boolean;
63
70
  }
64
71
 
65
72
  /**
@@ -142,6 +149,8 @@ class ParameterInputAdapter {
142
149
  const isKnownPrimitive = !!deps.typeMap[typeName];
143
150
  // Issue #958: C-header typedef struct types need pointer semantics
144
151
  const isTypedefStruct = deps.isTypedefStructType(typeName);
152
+ // Issue #995: Detect opaque handles — rule applied in ParameterSignatureBuilder
153
+ const isOpaque = deps.isOpaqueType?.(typeName) ?? false;
145
154
  // Issue #895: Don't add auto-const for callback-compatible functions
146
155
  // because it would change the signature and break typedef compatibility
147
156
  const isAutoConst =
@@ -171,6 +180,8 @@ class ParameterInputAdapter {
171
180
  deps.forcePassByReference || isTypedefStruct || undefined,
172
181
  // Issue #895: Preserve const from callback typedef signature
173
182
  forceConst: deps.forceConst,
183
+ // Issue #995: Pass through opaque handle detection — rule applied in builder
184
+ isOpaqueHandle: isOpaque || undefined,
174
185
  };
175
186
  }
176
187
 
@@ -236,6 +247,8 @@ class ParameterInputAdapter {
236
247
  isPassByReference: isCallbackPointer ? true : !deps.isPassByValue,
237
248
  forcePointerSyntax: isCallbackPointer || undefined,
238
249
  forceConst: param.isCallbackConst || undefined,
250
+ // Issue #995: Pass through opaque handle detection — rule applied in builder
251
+ isOpaqueHandle: param.isOpaqueHandle || undefined,
239
252
  };
240
253
  }
241
254
 
@@ -296,14 +309,15 @@ class ParameterInputAdapter {
296
309
  }
297
310
  }
298
311
 
299
- const isAutoConst = !deps.isModified && !isConst;
300
-
312
+ // ADR-006: Arrays are pass-by-reference and mutable by default.
313
+ // Never apply auto-const to arrays - only explicit const from source code.
314
+ // Auto-const would break compatibility with C APIs expecting mutable pointers.
301
315
  return {
302
316
  name,
303
317
  baseType: typeName,
304
318
  mappedType,
305
319
  isConst,
306
- isAutoConst,
320
+ isAutoConst: false,
307
321
  isArray: true,
308
322
  arrayDimensions: dims,
309
323
  isCallback: false,
@@ -47,6 +47,11 @@ class ParameterSignatureBuilder {
47
47
  return this._buildStringParam(param);
48
48
  }
49
49
 
50
+ // Issue #995: Opaque handles are always pass-by-reference with pointer syntax
51
+ if (param.isOpaqueHandle) {
52
+ return this._buildRefParam(param, refSuffix);
53
+ }
54
+
50
55
  // Known struct or known primitive: pass by reference
51
56
  if (param.isPassByReference) {
52
57
  return this._buildRefParam(param, refSuffix);
@@ -103,18 +108,21 @@ class ParameterSignatureBuilder {
103
108
  /**
104
109
  * Build pass-by-reference parameter signature.
105
110
  * C mode: const Point* p
106
- * C++ mode: const Point& p (unless forcePointerSyntax)
111
+ * C++ mode: const Point& p (unless forcePointerSyntax or isOpaqueHandle)
107
112
  *
108
113
  * Issue #895: When forcePointerSyntax is set, always use pointer syntax
109
114
  * because C callback typedefs expect pointers, not C++ references.
115
+ * Issue #995: Opaque handles must use pointer syntax because C APIs
116
+ * expect pointers to incomplete struct types (e.g., widget_t*).
110
117
  */
111
118
  private static _buildRefParam(
112
119
  param: IParameterInput,
113
120
  refSuffix: string,
114
121
  ): string {
115
122
  const constPrefix = this._getConstPrefix(param);
116
- // Issue #895: Override refSuffix for callback-compatible functions
117
- const actualSuffix = param.forcePointerSyntax ? "*" : refSuffix;
123
+ // Issue #895/#995: Override refSuffix for callback-compatible or opaque handle params
124
+ const actualSuffix =
125
+ param.forcePointerSyntax || param.isOpaqueHandle ? "*" : refSuffix;
118
126
  return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
119
127
  }
120
128
 
@@ -130,10 +138,15 @@ class ParameterSignatureBuilder {
130
138
  * Get const prefix combining explicit const, auto-const, and forced const.
131
139
  * Priority: forceConst > isConst > isAutoConst
132
140
  * Issue #895: forceConst preserves const from callback typedef signature.
141
+ * Issue #995: Opaque handles suppress auto-const (they must be passed to
142
+ * C APIs that expect non-const pointers).
133
143
  */
134
144
  private static _getConstPrefix(param: IParameterInput): string {
145
+ // Issue #995: Opaque handles never get auto-const — they're passed to C APIs
146
+ // expecting mutable pointers (e.g., LVGL's lv_obj_t)
147
+ const effectiveAutoConst = param.isOpaqueHandle ? false : param.isAutoConst;
135
148
  // Any source of const results in "const " prefix
136
- if (param.forceConst || param.isConst || param.isAutoConst) {
149
+ if (param.forceConst || param.isConst || effectiveAutoConst) {
137
150
  return "const ";
138
151
  }
139
152
  return "";