c-next 0.2.16 → 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.
- package/README.md +16 -0
- package/dist/index.js +1347 -414
- package/dist/index.js.map +3 -3
- package/grammar/CNext.g4 +4 -0
- package/package.json +5 -1
- package/src/transpiler/Transpiler.ts +90 -22
- package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
- package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
- package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
- package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
- package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
- package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
- package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
- package/src/transpiler/logic/symbols/SymbolTable.ts +17 -2
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +6 -4
- package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
- package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
- package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
- package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
- package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
- package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
- package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
- package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
- package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
- package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
- package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
- package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
- package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
- package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
- package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
- package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
- package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
- package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
- package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
- package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
- package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
- package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
- package/src/transpiler/state/CodeGenState.ts +71 -6
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
- package/src/utils/LiteralUtils.ts +23 -0
- package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
- 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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
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}${
|
|
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
|
-
|
|
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
|
-
//
|
|
485
|
-
|
|
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 =
|
|
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
|
|
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({
|