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
@@ -332,6 +332,60 @@ class TypeRegistrationEngine {
332
332
  return true;
333
333
  }
334
334
 
335
+ /**
336
+ * Issue #1029: Register type info for string arrays (string<N>[M]).
337
+ *
338
+ * String arrays are parsed as arrayType with stringType inside:
339
+ * arrayType -> stringType arrayTypeDimension+
340
+ *
341
+ * For `string<32>[4] items`:
342
+ * - stringType gives capacity 32
343
+ * - arrayTypeDimension gives [4]
344
+ * - Result: char items[4][33] with dimensions [4, 33]
345
+ */
346
+ private static _registerStringArrayType(
347
+ registryName: string,
348
+ arrayTypeCtx: Parser.ArrayTypeContext,
349
+ arrayDim: Parser.ArrayDimensionContext[] | null,
350
+ isConst: boolean,
351
+ overflowBehavior: TOverflowBehavior,
352
+ isAtomic: boolean,
353
+ callbacks: ITypeRegistrationCallbacks,
354
+ ): void {
355
+ const stringCtx = arrayTypeCtx.stringType()!;
356
+ const intLiteral = stringCtx.INTEGER_LITERAL();
357
+ if (!intLiteral) {
358
+ return; // No capacity specified - can't register
359
+ }
360
+
361
+ const capacity = Number.parseInt(intLiteral.getText(), 10);
362
+ callbacks.requireInclude("string");
363
+ const stringDim = capacity + 1;
364
+
365
+ // Collect dimensions: arrayTypeDimension from arrayType, then string capacity
366
+ // Build all dimensions at once to avoid multiple push() calls (SonarCloud S7778)
367
+ const arrayTypeDims = arrayTypeCtx
368
+ .arrayTypeDimension()
369
+ .map((dim) => dim.expression())
370
+ .filter((expr): expr is Parser.ExpressionContext => expr !== null)
371
+ .map((expr) => Number.parseInt(expr.getText(), 10))
372
+ .filter((size) => !Number.isNaN(size));
373
+ const additionalDims = ArrayDimensionParser.parseSimpleDimensions(arrayDim);
374
+ const dimensions = [...arrayTypeDims, ...additionalDims, stringDim];
375
+
376
+ CodeGenState.setVariableTypeInfo(registryName, {
377
+ baseType: "char",
378
+ bitWidth: 8,
379
+ isArray: true,
380
+ arrayDimensions: dimensions,
381
+ isConst,
382
+ isString: true,
383
+ stringCapacity: capacity,
384
+ overflowBehavior,
385
+ isAtomic,
386
+ });
387
+ }
388
+
335
389
  // ============================================================================
336
390
  // Array and standard type registration
337
391
  // ============================================================================
@@ -345,45 +399,40 @@ class TypeRegistrationEngine {
345
399
  isAtomic: boolean,
346
400
  callbacks: ITypeRegistrationCallbacks,
347
401
  ): void {
348
- let baseType = "";
349
- let bitWidth = 0;
402
+ // Issue #1029: Handle string arrays (string<N>[M]) - must check before primitiveType/userType
403
+ if (arrayTypeCtx.stringType()) {
404
+ TypeRegistrationEngine._registerStringArrayType(
405
+ registryName,
406
+ arrayTypeCtx,
407
+ arrayDim,
408
+ isConst,
409
+ overflowBehavior,
410
+ isAtomic,
411
+ callbacks,
412
+ );
413
+ return;
414
+ }
350
415
 
351
- if (arrayTypeCtx.primitiveType()) {
352
- baseType = arrayTypeCtx.primitiveType()!.getText();
353
- bitWidth = TYPE_WIDTH[baseType] || 0;
354
- } else if (arrayTypeCtx.userType()) {
355
- baseType = arrayTypeCtx.userType()!.getText();
356
-
357
- const combinedArrayDim = arrayDim ?? [];
358
- if (
359
- TypeRegistrationEngine._tryRegisterEnumOrBitmapType(
360
- registryName,
361
- baseType,
362
- isConst,
363
- combinedArrayDim,
364
- overflowBehavior,
365
- isAtomic,
366
- callbacks,
367
- )
368
- ) {
369
- const existingInfo = CodeGenState.getVariableTypeInfo(registryName);
370
- if (existingInfo) {
371
- const arrayTypeDim =
372
- TypeRegistrationEngine.parseArrayTypeDimension(arrayTypeCtx);
373
- const allDims = arrayTypeDim
374
- ? [arrayTypeDim, ...(existingInfo.arrayDimensions ?? [])]
375
- : existingInfo.arrayDimensions;
376
- CodeGenState.setVariableTypeInfo(registryName, {
377
- ...existingInfo,
378
- isArray: true,
379
- arrayDimensions: allDims,
380
- });
381
- }
416
+ // Try to register enum/bitmap user type arrays separately
417
+ if (arrayTypeCtx.userType()) {
418
+ const registered = TypeRegistrationEngine._tryRegisterUserTypeArray(
419
+ registryName,
420
+ arrayTypeCtx,
421
+ arrayDim,
422
+ isConst,
423
+ overflowBehavior,
424
+ isAtomic,
425
+ callbacks,
426
+ );
427
+ if (registered) {
382
428
  return;
383
429
  }
384
430
  }
385
431
 
386
- if (!baseType) {
432
+ // Extract base type and bit width from array type
433
+ const typeInfo =
434
+ TypeRegistrationEngine._extractArrayBaseTypeInfo(arrayTypeCtx);
435
+ if (!typeInfo.baseType) {
387
436
  return;
388
437
  }
389
438
 
@@ -394,8 +443,8 @@ class TypeRegistrationEngine {
394
443
  );
395
444
 
396
445
  CodeGenState.setVariableTypeInfo(registryName, {
397
- baseType,
398
- bitWidth,
446
+ baseType: typeInfo.baseType,
447
+ bitWidth: typeInfo.bitWidth,
399
448
  isArray: true,
400
449
  arrayDimensions: arrayDimensions.length > 0 ? arrayDimensions : undefined,
401
450
  isConst,
@@ -404,6 +453,91 @@ class TypeRegistrationEngine {
404
453
  });
405
454
  }
406
455
 
456
+ /**
457
+ * Extract base type and bit width from an array type context.
458
+ * Handles primitive, qualified, scoped, and user types.
459
+ */
460
+ private static _extractArrayBaseTypeInfo(
461
+ arrayTypeCtx: Parser.ArrayTypeContext,
462
+ ): { baseType: string; bitWidth: number } {
463
+ if (arrayTypeCtx.primitiveType()) {
464
+ const baseType = arrayTypeCtx.primitiveType()!.getText();
465
+ return { baseType, bitWidth: TYPE_WIDTH[baseType] || 0 };
466
+ }
467
+
468
+ if (arrayTypeCtx.qualifiedType()) {
469
+ const parts = arrayTypeCtx.qualifiedType()!.IDENTIFIER();
470
+ return { baseType: parts.map((p) => p.getText()).join("_"), bitWidth: 0 };
471
+ }
472
+
473
+ if (arrayTypeCtx.scopedType()) {
474
+ const typeName = arrayTypeCtx.scopedType()!.IDENTIFIER().getText();
475
+ const baseType = CodeGenState.currentScope
476
+ ? `${CodeGenState.currentScope}_${typeName}`
477
+ : typeName;
478
+ return { baseType, bitWidth: 0 };
479
+ }
480
+
481
+ if (arrayTypeCtx.globalType()) {
482
+ const typeName = arrayTypeCtx.globalType()!.IDENTIFIER().getText();
483
+ return { baseType: typeName, bitWidth: 0 };
484
+ }
485
+
486
+ if (arrayTypeCtx.userType()) {
487
+ return { baseType: arrayTypeCtx.userType()!.getText(), bitWidth: 0 };
488
+ }
489
+
490
+ return { baseType: "", bitWidth: 0 };
491
+ }
492
+
493
+ /**
494
+ * Try to register a user type array as enum or bitmap.
495
+ * Returns true if registration was handled, false if it should fall through.
496
+ */
497
+ private static _tryRegisterUserTypeArray(
498
+ registryName: string,
499
+ arrayTypeCtx: Parser.ArrayTypeContext,
500
+ arrayDim: Parser.ArrayDimensionContext[] | null,
501
+ isConst: boolean,
502
+ overflowBehavior: TOverflowBehavior,
503
+ isAtomic: boolean,
504
+ callbacks: ITypeRegistrationCallbacks,
505
+ ): boolean {
506
+ const baseType = arrayTypeCtx.userType()!.getText();
507
+ const combinedArrayDim = arrayDim ?? [];
508
+
509
+ const registered = TypeRegistrationEngine._tryRegisterEnumOrBitmapType(
510
+ registryName,
511
+ baseType,
512
+ isConst,
513
+ combinedArrayDim,
514
+ overflowBehavior,
515
+ isAtomic,
516
+ callbacks,
517
+ );
518
+
519
+ if (!registered) {
520
+ return false;
521
+ }
522
+
523
+ // Add arrayType dimensions to existing info
524
+ const existingInfo = CodeGenState.getVariableTypeInfo(registryName);
525
+ if (existingInfo) {
526
+ const arrayTypeDim =
527
+ TypeRegistrationEngine.parseArrayTypeDimension(arrayTypeCtx);
528
+ const allDims = arrayTypeDim
529
+ ? [arrayTypeDim, ...(existingInfo.arrayDimensions ?? [])]
530
+ : existingInfo.arrayDimensions;
531
+ CodeGenState.setVariableTypeInfo(registryName, {
532
+ ...existingInfo,
533
+ isArray: true,
534
+ arrayDimensions: allDims,
535
+ });
536
+ }
537
+
538
+ return true;
539
+ }
540
+
407
541
  private static _collectArrayDimensions(
408
542
  arrayTypeCtx: Parser.ArrayTypeContext,
409
543
  arrayDim: Parser.ArrayDimensionContext[] | null,
@@ -268,49 +268,27 @@ class VariableDeclHelper {
268
268
  return; // Not an array declaration
269
269
  }
270
270
 
271
- // If type already has arrayType, additional dimensions are allowed (multi-dim)
272
- if (typeCtx.arrayType()) {
273
- return; // Valid C-Next style: u16[4] arr[2] -> uint16_t arr[4][2]
274
- }
275
-
276
- // Allow empty first dimension for size inference: u8 arr[] <- [1, 2, 3]
277
- // The grammar doesn't support u8[] arr syntax, so this is the only way
278
- if (arrayDims.length === 1 && !arrayDims[0].expression()) {
279
- return; // Size inference pattern allowed
280
- }
281
-
282
- // Allow C-style for multi-dimensional arrays: u8 matrix[4][4]
283
- // The arrayType grammar only supports single dimension, so multi-dim needs C-style
284
- if (arrayDims.length > 1) {
285
- return; // Multi-dimensional arrays need C-style
286
- }
287
-
288
- // Allow C-style for types that don't support arrayType syntax:
289
- // - Qualified types (Scope.Type, Namespace::Type)
290
- // - Scoped types (this.Type)
291
- // - Global types (global.Type)
292
- // - String types (string<N>)
293
- // - Bitmap types (code generator doesn't yet handle arrayType for bitmaps)
294
- if (
295
- typeCtx.qualifiedType() ||
296
- typeCtx.scopedType() ||
297
- typeCtx.globalType() ||
298
- typeCtx.stringType()
299
- ) {
300
- return; // Grammar limitation - these can't use arrayType
301
- }
302
-
303
- // C-style array declaration detected - reject with helpful error
271
+ // Issues #1014-#1017: ALL trailing brackets after the variable name are rejected.
272
+ // The only valid form is dimensions in type position: u8[4][8] matrix, string<32>[5] names
273
+ // No mixed forms (u8[4] matrix[8]), no C-style (u8 matrix[4][8]), no trailing inference (u8 arr[])
304
274
  const baseType = VariableDeclHelper.extractBaseTypeName(typeCtx);
305
- const dimensions = arrayDims
275
+ const existingDims = typeCtx.arrayType()
276
+ ? typeCtx
277
+ .arrayType()!
278
+ .arrayTypeDimension()
279
+ .map((d) => `[${d.expression()?.getText() ?? ""}]`)
280
+ .join("")
281
+ : "";
282
+ const trailingDims = arrayDims
306
283
  .map((dim) => `[${dim.expression()?.getText() ?? ""}]`)
307
284
  .join("");
285
+ const allDims = existingDims + trailingDims;
308
286
  const line = ctx.start?.line ?? 0;
309
287
  const col = ctx.start?.column ?? 0;
310
288
 
311
289
  throw new Error(
312
290
  `${line}:${col} C-style array declaration is not allowed. ` +
313
- `Use '${baseType}${dimensions} ${name}' instead of '${baseType} ${name}${dimensions}'`,
291
+ `Use '${baseType}${allDims} ${name}' instead of '${baseType}${existingDims} ${name}${trailingDims}'`,
314
292
  );
315
293
  }
316
294
 
@@ -456,7 +434,22 @@ class VariableDeclHelper {
456
434
  callbacks,
457
435
  );
458
436
 
459
- const hasEmptyArrayDim = arrayDims.some((dim) => !dim.expression());
437
+ // Check for empty dimensions in both trailing brackets and arrayType
438
+ const hasEmptyArrayDim =
439
+ arrayDims.some((dim) => !dim.expression()) ||
440
+ (typeCtx
441
+ .arrayType()
442
+ ?.arrayTypeDimension()
443
+ .some((dim) => !dim.expression()) ??
444
+ false);
445
+
446
+ // Check if the empty dimension is specifically in arrayType (vs trailing arrayDims)
447
+ const hasEmptyArrayTypeDim =
448
+ typeCtx
449
+ .arrayType()
450
+ ?.arrayTypeDimension()
451
+ .some((dim) => !dim.expression()) ?? false;
452
+
460
453
  const declaredSize =
461
454
  VariableDeclHelper.parseArrayTypeDimension(typeCtx) ??
462
455
  VariableDeclHelper.parseFirstArrayDimension(arrayDims);
@@ -481,8 +474,11 @@ class VariableDeclHelper {
481
474
  if (arrayInitResult) {
482
475
  // Track as local array for type resolution
483
476
  CodeGenState.localArrays.add(name);
484
- // Include arrayType dimension before arrayDimension dimensions
485
- const fullDimSuffix = arrayTypeDimStr + arrayInitResult.dimensionSuffix;
477
+ // When size inference happens and the empty dim is in arrayType,
478
+ // dimensionSuffix already contains the inferred size - don't duplicate
479
+ const fullDimSuffix = hasEmptyArrayTypeDim
480
+ ? arrayInitResult.dimensionSuffix
481
+ : arrayTypeDimStr + arrayInitResult.dimensionSuffix;
486
482
  return {
487
483
  handled: true,
488
484
  code: `${decl}${fullDimSuffix} = ${arrayInitResult.initValue};`,
@@ -537,7 +533,9 @@ class VariableDeclHelper {
537
533
  // Issue #872: Set expectedType for MISRA 7.2 U suffix compliance
538
534
  // MISRA 10.3: Also check for cross-type-category conversions (int <-> float)
539
535
  return CodeGenState.withExpectedType(typeName, () => {
540
- let exprCode = callbacks.generateExpression(ctx.expression()!);
536
+ let exprCode = CodeGenState.withDeclarationInit(() =>
537
+ callbacks.generateExpression(ctx.expression()!),
538
+ );
541
539
 
542
540
  // MISRA 10.3: Check for cross-type-category conversions (int <-> float)
543
541
  const exprType = callbacks.getExpressionType(ctx.expression()!);
@@ -334,6 +334,55 @@ describe("ParameterInputAdapter", () => {
334
334
  expect(result.forcePointerSyntax).toBeUndefined();
335
335
  expect(result.forceConst).toBeUndefined();
336
336
  });
337
+
338
+ // Issue #995: Opaque handle support in fromSymbol
339
+ it("passes through isOpaqueHandle when set (Issue #995)", () => {
340
+ // The adapter passes through isOpaqueHandle; ParameterSignatureBuilder
341
+ // applies the rule (suppress auto-const, force pointer syntax)
342
+ const param: IParameterSymbol = {
343
+ name: "widget",
344
+ type: "widget_t",
345
+ isConst: false,
346
+ isArray: false,
347
+ isOpaqueHandle: true,
348
+ };
349
+
350
+ const result = ParameterInputAdapter.fromSymbol(param, defaultDeps);
351
+
352
+ expect(result.isOpaqueHandle).toBe(true);
353
+ });
354
+
355
+ it("passes through isAutoConst even when isOpaqueHandle is set (builder applies rule)", () => {
356
+ // The adapter passes through both flags; the builder suppresses auto-const
357
+ // for opaque handles — this is NOT the adapter's responsibility
358
+ const param: IParameterSymbol = {
359
+ name: "widget",
360
+ type: "widget_t",
361
+ isConst: false,
362
+ isArray: false,
363
+ isOpaqueHandle: true,
364
+ isAutoConst: true,
365
+ };
366
+
367
+ const result = ParameterInputAdapter.fromSymbol(param, defaultDeps);
368
+
369
+ // Adapter passes through isAutoConst as-is; builder will suppress it
370
+ expect(result.isAutoConst).toBe(true);
371
+ expect(result.isOpaqueHandle).toBe(true);
372
+ });
373
+
374
+ it("does not force pointer when isOpaqueHandle is not set", () => {
375
+ const param: IParameterSymbol = {
376
+ name: "point",
377
+ type: "Point",
378
+ isConst: false,
379
+ isArray: false,
380
+ };
381
+
382
+ const result = ParameterInputAdapter.fromSymbol(param, defaultDeps);
383
+
384
+ expect(result.forcePointerSyntax).toBeUndefined();
385
+ });
337
386
  });
338
387
 
339
388
  describe("fromAST", () => {
@@ -404,6 +453,18 @@ describe("ParameterInputAdapter", () => {
404
453
  expect(result.arrayDimensions).toEqual(["4", "4"]);
405
454
  });
406
455
 
456
+ // Issue #986: ADR-006 says arrays are mutable by default.
457
+ // Arrays should never get auto-const, even when unmodified.
458
+ it("does not set auto-const for unmodified array parameter", () => {
459
+ const ctx = getParameterContext("void foo(u8[8] data) {}");
460
+ const deps = createDefaultASTDeps({ isModified: false });
461
+
462
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
463
+
464
+ expect(result.isArray).toBe(true);
465
+ expect(result.isAutoConst).toBe(false); // Arrays never get auto-const
466
+ });
467
+
407
468
  it("converts non-array string parameter", () => {
408
469
  const ctx = getParameterContext("void foo(string<32> name) {}");
409
470
  const deps = createDefaultASTDeps();
@@ -508,5 +569,61 @@ describe("ParameterInputAdapter", () => {
508
569
 
509
570
  expect(result.forceConst).toBeUndefined();
510
571
  });
572
+
573
+ // Issue #995: Opaque handles pass through isOpaqueHandle; builder applies rule
574
+ it("passes through isOpaqueHandle for opaque type parameter", () => {
575
+ const ctx = getParameterContext("void foo(widget_t w) {}");
576
+ const deps = {
577
+ ...createDefaultASTDeps({ isModified: false }),
578
+ isOpaqueType: (typeName: string) => typeName === "widget_t",
579
+ };
580
+
581
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
582
+
583
+ // Adapter passes through detection; builder applies rule
584
+ expect(result.isOpaqueHandle).toBe(true);
585
+ // isAutoConst computed normally; builder will suppress it for opaque handles
586
+ expect(result.isAutoConst).toBe(true);
587
+ });
588
+
589
+ // Issue #995: Opaque handles don't set forcePointerSyntax — builder handles it
590
+ it("does not set forcePointerSyntax for opaque type (builder handles it)", () => {
591
+ const ctx = getParameterContext("void foo(widget_t w) {}");
592
+ const deps = {
593
+ ...createDefaultASTDeps({ isModified: false }),
594
+ isOpaqueType: (typeName: string) => typeName === "widget_t",
595
+ isTypedefStructType: () => false, // Not a typedef struct
596
+ };
597
+
598
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
599
+
600
+ // forcePointerSyntax not set by adapter for opaque handles
601
+ // (builder uses isOpaqueHandle instead)
602
+ expect(result.forcePointerSyntax).toBeUndefined();
603
+ expect(result.isOpaqueHandle).toBe(true);
604
+ });
605
+
606
+ // Issue #995: Verify non-opaque types still get auto-const
607
+ it("sets auto-const for non-opaque type when isOpaqueType returns false", () => {
608
+ const ctx = getParameterContext("void foo(Point p) {}");
609
+ const deps = {
610
+ ...createDefaultASTDeps({ isModified: false }),
611
+ isOpaqueType: () => false,
612
+ };
613
+
614
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
615
+
616
+ expect(result.isAutoConst).toBe(true);
617
+ });
618
+
619
+ // Issue #995: When isOpaqueType is not provided, auto-const still works
620
+ it("sets auto-const when isOpaqueType is not provided", () => {
621
+ const ctx = getParameterContext("void foo(Point p) {}");
622
+ const deps = createDefaultASTDeps({ isModified: false });
623
+
624
+ const result = ParameterInputAdapter.fromAST(ctx, deps);
625
+
626
+ expect(result.isAutoConst).toBe(true);
627
+ });
511
628
  });
512
629
  });
@@ -44,15 +44,20 @@ describe("ParameterSignatureBuilder", () => {
44
44
  });
45
45
  });
46
46
 
47
+ // Note: These tests verify the builder respects isAutoConst when explicitly set.
48
+ // Per ADR-006 and issue #986, ParameterInputAdapter now sets isAutoConst=false
49
+ // for arrays (arrays are mutable by default). These tests still pass because
50
+ // they test the builder's behavior when isAutoConst IS requested (e.g., for
51
+ // explicit const arrays or legacy code paths).
47
52
  describe("array parameters", () => {
48
- it("generates single dimension array", () => {
53
+ it("generates const array when isAutoConst is true", () => {
49
54
  const input = createInput({
50
55
  name: "arr",
51
56
  baseType: "u32",
52
57
  mappedType: "uint32_t",
53
58
  isArray: true,
54
59
  arrayDimensions: ["10"],
55
- isAutoConst: true,
60
+ isAutoConst: true, // Explicit request for const
56
61
  });
57
62
 
58
63
  const result = ParameterSignatureBuilder.build(input, "*");
@@ -313,6 +318,93 @@ describe("ParameterSignatureBuilder", () => {
313
318
  });
314
319
  });
315
320
 
321
+ describe("opaque handle parameters (Issue #995)", () => {
322
+ it("suppresses auto-const for opaque handles", () => {
323
+ // Opaque handles should not get const because they must be passed
324
+ // to C APIs expecting non-const pointers (like LVGL's lv_obj_t)
325
+ const input = createInput({
326
+ name: "widget",
327
+ baseType: "widget_t",
328
+ mappedType: "widget_t",
329
+ isPassByReference: true,
330
+ isAutoConst: true, // Would normally add const
331
+ isOpaqueHandle: true, // But opaque overrides
332
+ });
333
+
334
+ const result = ParameterSignatureBuilder.build(input, "*");
335
+
336
+ // auto-const should be suppressed
337
+ expect(result).toBe("widget_t* widget");
338
+ });
339
+
340
+ it("uses pointer syntax for opaque handles in C++ mode", () => {
341
+ // Opaque handles must use pointer syntax even in C++ mode
342
+ // because C APIs expect pointers to incomplete struct types
343
+ const input = createInput({
344
+ name: "w",
345
+ baseType: "widget_t",
346
+ mappedType: "widget_t",
347
+ isPassByReference: true,
348
+ isOpaqueHandle: true,
349
+ });
350
+
351
+ const result = ParameterSignatureBuilder.build(input, "&");
352
+
353
+ // Should use pointer (not reference) in C++ mode
354
+ expect(result).toBe("widget_t* w");
355
+ });
356
+
357
+ it("routes opaque handles through pass-by-reference path", () => {
358
+ // Even if isPassByReference is false, opaque handles should
359
+ // still generate pointer syntax
360
+ const input = createInput({
361
+ name: "ctx",
362
+ baseType: "context_t",
363
+ mappedType: "context_t",
364
+ isPassByReference: false, // Not explicitly set
365
+ isPassByValue: false, // Not explicitly set
366
+ isOpaqueHandle: true,
367
+ });
368
+
369
+ const result = ParameterSignatureBuilder.build(input, "*");
370
+
371
+ // Should still get pointer syntax from opaque handle routing
372
+ expect(result).toBe("context_t* ctx");
373
+ });
374
+
375
+ it("preserves explicit const on opaque handles", () => {
376
+ // If the user explicitly marks it const, that should be preserved
377
+ const input = createInput({
378
+ name: "widget",
379
+ baseType: "widget_t",
380
+ mappedType: "widget_t",
381
+ isConst: true, // Explicit const
382
+ isOpaqueHandle: true,
383
+ });
384
+
385
+ const result = ParameterSignatureBuilder.build(input, "*");
386
+
387
+ expect(result).toBe("const widget_t* widget");
388
+ });
389
+
390
+ it("isOpaqueHandle overrides forcePointerSyntax check", () => {
391
+ // isOpaqueHandle should force pointer even without forcePointerSyntax
392
+ const input = createInput({
393
+ name: "widget",
394
+ baseType: "widget_t",
395
+ mappedType: "widget_t",
396
+ isPassByReference: true,
397
+ forcePointerSyntax: false, // Not set
398
+ isOpaqueHandle: true, // But opaque overrides
399
+ });
400
+
401
+ const result = ParameterSignatureBuilder.build(input, "&");
402
+
403
+ // Should use pointer (not reference) due to isOpaqueHandle
404
+ expect(result).toBe("widget_t* widget");
405
+ });
406
+ });
407
+
316
408
  describe("callback-compatible parameters (Issue #895)", () => {
317
409
  it("forceConst adds const from typedef signature", () => {
318
410
  const input = createInput({