c-next 0.2.16 → 0.2.18

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 (118) hide show
  1. package/README.md +18 -2
  2. package/dist/index.js +8897 -6260
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +12 -0
  5. package/package.json +4 -2
  6. package/src/transpiler/Transpiler.ts +376 -48
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  9. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  10. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  11. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +65 -10
  12. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  13. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  14. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  15. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  16. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  17. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  18. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  19. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  20. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  21. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  22. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  23. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  24. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  25. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  26. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  27. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  28. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  29. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  30. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  31. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  32. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1318 -1178
  33. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  34. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  35. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  36. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  37. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  38. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  39. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  40. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  41. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  42. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  43. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  44. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  45. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  46. package/src/transpiler/logic/symbols/SymbolTable.ts +62 -2
  47. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +55 -4
  48. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  49. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  50. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  51. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  52. package/src/transpiler/output/codegen/CodeGenerator.ts +196 -98
  53. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  54. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  55. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
  56. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +163 -35
  57. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  58. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  59. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  60. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +97 -14
  61. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  62. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  63. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  64. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  65. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  66. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  67. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  68. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  69. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  70. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  71. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  72. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  73. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  74. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
  75. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
  76. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  77. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +76 -58
  78. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  79. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  80. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  81. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  82. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  83. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  84. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  85. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  86. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  87. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  88. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +240 -42
  89. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  90. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  91. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  92. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  93. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  94. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  95. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  96. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +322 -9
  97. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  98. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  99. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  100. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  101. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  102. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  103. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  104. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  105. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  106. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +140 -24
  107. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  108. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  109. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  110. package/src/transpiler/state/CodeGenState.ts +91 -22
  111. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  112. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  113. package/src/utils/LiteralUtils.ts +23 -0
  114. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  115. package/src/utils/cache/CacheManager.ts +13 -2
  116. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  117. package/src/utils/constants/TypeConstants.ts +13 -0
  118. package/src/utils/types/IParameterSymbol.ts +1 -0
@@ -147,6 +147,7 @@ function createMockOrchestrator(overrides?: {
147
147
  ) => TTypeInfo | null;
148
148
  validateCrossScopeVisibility?: (scope: string, member: string) => void;
149
149
  generateBitMask?: (width: string, is64?: boolean) => string;
150
+ tryEvaluateConstant?: (ctx: unknown) => number | undefined;
150
151
  hasFloatBitShadow?: (name: string) => boolean;
151
152
  registerFloatBitShadow?: (name: string) => void;
152
153
  addPendingTempDeclaration?: (decl: string) => void;
@@ -172,7 +173,7 @@ function createMockOrchestrator(overrides?: {
172
173
  isCNextFunction: vi.fn(),
173
174
  isStructType: vi.fn(),
174
175
  getTypeName: vi.fn(),
175
- tryEvaluateConstant: vi.fn(),
176
+ tryEvaluateConstant: overrides?.tryEvaluateConstant ?? vi.fn(),
176
177
  getZeroInitializer: vi.fn(),
177
178
  getExpressionEnumType: vi.fn(),
178
179
  isIntegerExpression: vi.fn(),
@@ -1265,6 +1266,98 @@ describe("PostfixExpressionGenerator", () => {
1265
1266
  const result = generatePostfixExpression(ctx, input, state, orchestrator);
1266
1267
  expect(result.code).toBe("((val) & 0xFF)");
1267
1268
  });
1269
+
1270
+ // Issue #1094: a const/macro width must be resolved to its numeric value (with
1271
+ // a "U" suffix to match the literal path) before mask generation, instead of
1272
+ // being passed through as the identifier — otherwise the mask falls back to a
1273
+ // runtime ((1U << W) - 1) that is UB at full width.
1274
+ it("resolves a const width to a precomputed mask (#1094)", () => {
1275
+ const typeRegistry = new Map<string, TTypeInfo>([
1276
+ [
1277
+ "val",
1278
+ { baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
1279
+ ],
1280
+ ]);
1281
+ const ctx = createMockPostfixExpressionContext("val", [
1282
+ createMockPostfixOp({
1283
+ expressions: [
1284
+ createMockExpression("0"),
1285
+ createMockExpression("WIDTH"),
1286
+ ],
1287
+ }),
1288
+ ]);
1289
+ const input = createMockInput({ typeRegistry });
1290
+ const state = createMockState();
1291
+ const generateBitMask = vi.fn(() => "0xFFFFFFFFU");
1292
+ const orchestrator = createMockOrchestrator({
1293
+ generatePrimaryExpr: () => "val",
1294
+ generateExpression: (ctx) => ctx.getText(),
1295
+ generateBitMask,
1296
+ tryEvaluateConstant: () => 32,
1297
+ });
1298
+
1299
+ const result = generatePostfixExpression(ctx, input, state, orchestrator);
1300
+ // Width resolved to "32U" (not the identifier "WIDTH"); u32 operand → not 64-bit.
1301
+ expect(generateBitMask).toHaveBeenCalledWith("32U", false);
1302
+ expect(result.code).toBe("((val) & 0xFFFFFFFFU)");
1303
+ });
1304
+
1305
+ it("passes the raw width when it is not a const (#1094)", () => {
1306
+ const typeRegistry = new Map<string, TTypeInfo>([
1307
+ [
1308
+ "val",
1309
+ { baseType: "u32", bitWidth: 32, isArray: false, isConst: false },
1310
+ ],
1311
+ ]);
1312
+ const ctx = createMockPostfixExpressionContext("val", [
1313
+ createMockPostfixOp({
1314
+ expressions: [createMockExpression("0"), createMockExpression("n")],
1315
+ }),
1316
+ ]);
1317
+ const input = createMockInput({ typeRegistry });
1318
+ const state = createMockState();
1319
+ const generateBitMask = vi.fn(() => "((1U << n) - 1)");
1320
+ const orchestrator = createMockOrchestrator({
1321
+ generatePrimaryExpr: () => "val",
1322
+ generateExpression: (ctx) => ctx.getText(),
1323
+ generateBitMask,
1324
+ tryEvaluateConstant: () => undefined,
1325
+ });
1326
+
1327
+ generatePostfixExpression(ctx, input, state, orchestrator);
1328
+ // Non-const width: the generated expression string flows through unchanged.
1329
+ expect(generateBitMask).toHaveBeenCalledWith("n", false);
1330
+ });
1331
+
1332
+ it("marks a u64 operand as 64-bit for the mask base (#1094)", () => {
1333
+ const typeRegistry = new Map<string, TTypeInfo>([
1334
+ [
1335
+ "val",
1336
+ { baseType: "u64", bitWidth: 64, isArray: false, isConst: false },
1337
+ ],
1338
+ ]);
1339
+ const ctx = createMockPostfixExpressionContext("val", [
1340
+ createMockPostfixOp({
1341
+ expressions: [
1342
+ createMockExpression("0"),
1343
+ createMockExpression("WIDTH"),
1344
+ ],
1345
+ }),
1346
+ ]);
1347
+ const input = createMockInput({ typeRegistry });
1348
+ const state = createMockState();
1349
+ const generateBitMask = vi.fn(() => "((1ULL << 40U) - 1)");
1350
+ const orchestrator = createMockOrchestrator({
1351
+ generatePrimaryExpr: () => "val",
1352
+ generateExpression: (ctx) => ctx.getText(),
1353
+ generateBitMask,
1354
+ tryEvaluateConstant: () => 40,
1355
+ });
1356
+
1357
+ generatePostfixExpression(ctx, input, state, orchestrator);
1358
+ // u64 operand → is64Bit true, so the mask uses a 64-bit base (1ULL).
1359
+ expect(generateBitMask).toHaveBeenCalledWith("40U", true);
1360
+ });
1268
1361
  });
1269
1362
 
1270
1363
  describe("float bit indexing", () => {
@@ -1371,6 +1464,41 @@ describe("PostfixExpressionGenerator", () => {
1371
1464
  // Uses union member .u for bit access
1372
1465
  expect(result.code).toBe("(__bits_f.u & 0xFF)");
1373
1466
  });
1467
+
1468
+ // Issue #1094: the float branch must resolve a const width too, and tell the
1469
+ // mask generator the f64 union is 64-bit.
1470
+ it("resolves a const width for f64 bit indexing (#1094)", () => {
1471
+ const typeRegistry = new Map<string, TTypeInfo>([
1472
+ [
1473
+ "d",
1474
+ { baseType: "f64", bitWidth: 64, isArray: false, isConst: false },
1475
+ ],
1476
+ ]);
1477
+ const ctx = createMockPostfixExpressionContext("d", [
1478
+ createMockPostfixOp({
1479
+ expressions: [
1480
+ createMockExpression("0"),
1481
+ createMockExpression("WIDTH"),
1482
+ ],
1483
+ }),
1484
+ ]);
1485
+ const input = createMockInput({ typeRegistry });
1486
+ const state = createMockState({ inFunctionBody: true });
1487
+ const generateBitMask = vi.fn(() => "0xFFFFFFFFFFFFFFFFULL");
1488
+ const orchestrator = createMockOrchestrator({
1489
+ generatePrimaryExpr: () => "d",
1490
+ generateExpression: (ctx) => ctx.getText(),
1491
+ generateBitMask,
1492
+ tryEvaluateConstant: () => 64,
1493
+ hasFloatBitShadow: () => true,
1494
+ isFloatShadowCurrent: () => true,
1495
+ });
1496
+
1497
+ const result = generatePostfixExpression(ctx, input, state, orchestrator);
1498
+ // Const width "64U" passed; f64 union → 64-bit mask base.
1499
+ expect(generateBitMask).toHaveBeenCalledWith("64U", true);
1500
+ expect(result.code).toBe("(__bits_d.u & 0xFFFFFFFFFFFFFFFFULL)");
1501
+ });
1374
1502
  });
1375
1503
 
1376
1504
  describe("function call", () => {
@@ -14,6 +14,7 @@ import {
14
14
  WhileStatementContext,
15
15
  DoWhileStatementContext,
16
16
  ForStatementContext,
17
+ ForeverStatementContext,
17
18
  ForVarDeclContext,
18
19
  ForAssignmentContext,
19
20
  ExpressionContext,
@@ -167,6 +168,9 @@ const generateWhile = (
167
168
  // Issue #884: Validate condition is a boolean expression (E0701)
168
169
  orchestrator.validateConditionIsBoolean(node.expression(), "while");
169
170
 
171
+ // ADR-113 / #1075: reject always-true literal condition (E0707)
172
+ orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
173
+
170
174
  const condition = orchestrator.generateExpression(node.expression());
171
175
 
172
176
  // Issue #250: Flush any temp vars from condition BEFORE generating body
@@ -201,6 +205,9 @@ const generateDoWhile = (
201
205
  // Issue #884: Validate condition is a boolean expression (E0701)
202
206
  orchestrator.validateConditionIsBoolean(node.expression(), "do-while");
203
207
 
208
+ // ADR-113 / #1075: reject always-true literal condition (E0707)
209
+ orchestrator.validateLoopConditionNotAlwaysTrue(node.expression());
210
+
204
211
  const body = orchestrator.generateBlock(node.block());
205
212
  const condition = orchestrator.generateExpression(node.expression());
206
213
 
@@ -286,6 +293,15 @@ const generateFor = (
286
293
  ): IGeneratorOutput => {
287
294
  const effects: TGeneratorEffect[] = [];
288
295
 
296
+ // ADR-113 / #1075 E0707: a for-loop with no controlling expression (`for (;;)`)
297
+ // is a disguised infinite loop. C-Next has one source form for that — `forever`.
298
+ if (!node.expression()) {
299
+ throw new Error(
300
+ "Error E0707: for-loop has no controlling expression (infinite loop)\n" +
301
+ " help: write 'forever { ... }' for an intentional infinite loop",
302
+ );
303
+ }
304
+
289
305
  let init = "";
290
306
  const forInit = node.forInit();
291
307
  if (forInit) {
@@ -313,15 +329,20 @@ const generateFor = (
313
329
  // Issue #250: Flush temps from init before generating condition
314
330
  const initTemps = orchestrator.flushPendingTempDeclarations();
315
331
 
316
- let condition = "";
317
- if (node.expression()) {
318
- // Issue #254: Validate no function calls in condition (E0702)
319
- orchestrator.validateConditionNoFunctionCall(node.expression()!, "for");
332
+ // The empty-header case (`for (;;)`) already threw E0707 above, so the
333
+ // controlling expression is guaranteed present here.
334
+ const conditionExpr = node.expression()!;
320
335
 
321
- // Issue #884: Validate condition is a boolean expression (E0701)
322
- orchestrator.validateConditionIsBoolean(node.expression()!, "for");
323
- condition = orchestrator.generateExpression(node.expression()!);
324
- }
336
+ // Issue #254: Validate no function calls in condition (E0702)
337
+ orchestrator.validateConditionNoFunctionCall(conditionExpr, "for");
338
+
339
+ // Issue #884: Validate condition is a boolean expression (E0701)
340
+ orchestrator.validateConditionIsBoolean(conditionExpr, "for");
341
+
342
+ // ADR-113 / #1075: reject always-true literal condition (E0707)
343
+ orchestrator.validateLoopConditionNotAlwaysTrue(conditionExpr);
344
+
345
+ const condition = orchestrator.generateExpression(conditionExpr);
325
346
 
326
347
  // Issue #250: Flush temps from condition before generating update
327
348
  const conditionTemps = orchestrator.flushPendingTempDeclarations();
@@ -357,6 +378,40 @@ const generateFor = (
357
378
  return { code: result, effects };
358
379
  };
359
380
 
381
+ /**
382
+ * Generate C code for a forever statement (ADR-113).
383
+ *
384
+ * Lowers to the MISRA C:2012 Rule 14.3-compliant infinite-loop idiom `for (;;)`
385
+ * (the carve-out that rule explicitly permits — no controlling expression to be
386
+ * flagged as invariant). A `forever` loop may appear only in a void function
387
+ * (E0705): a value-returning function can never honor its return type if it
388
+ * loops forever.
389
+ */
390
+ const generateForever = (
391
+ node: ForeverStatementContext,
392
+ _input: IGeneratorInput,
393
+ _state: IGeneratorState,
394
+ orchestrator: IOrchestrator,
395
+ ): IGeneratorOutput => {
396
+ const effects: TGeneratorEffect[] = [];
397
+
398
+ // ADR-113 E0705: forever is void-only.
399
+ const returnType = orchestrator.getCurrentFunctionReturnType();
400
+ if (returnType && returnType !== "void") {
401
+ throw new Error(
402
+ "Error E0705: forever loop in non-void function\n" +
403
+ " help: a forever loop never returns a value; make the function return void, " +
404
+ "or use a while loop with an exit condition",
405
+ );
406
+ }
407
+
408
+ const body = orchestrator.generateBlock(node.block());
409
+ const comment =
410
+ "/* MISRA C:2012 Rule 14.3: infinite loop is intentional (C-Next `forever`) */";
411
+
412
+ return { code: `${comment}\nfor (;;) ${body}`, effects };
413
+ };
414
+
360
415
  // Export all control flow generators
361
416
  const controlFlowGenerators = {
362
417
  generateReturn,
@@ -364,6 +419,7 @@ const controlFlowGenerators = {
364
419
  generateWhile,
365
420
  generateDoWhile,
366
421
  generateFor,
422
+ generateForever,
367
423
  generateForVarDecl,
368
424
  generateForAssignment,
369
425
  };
@@ -365,6 +365,7 @@ function createMockOrchestrator(options?: {
365
365
  flushPendingTempDeclarations: vi.fn(() => options?.tempDeclarations ?? ""),
366
366
  validateConditionNoFunctionCall: vi.fn(),
367
367
  validateConditionIsBoolean: vi.fn(),
368
+ validateLoopConditionNotAlwaysTrue: vi.fn(),
368
369
  countStringLengthAccesses: vi.fn(() => new Map()),
369
370
  countBlockLengthAccesses: vi.fn(),
370
371
  setupLengthCache: vi.fn(() => options?.lengthCacheDecls ?? ""),
@@ -898,15 +899,15 @@ describe("ControlFlowGenerator", () => {
898
899
  // ========================================================================
899
900
 
900
901
  describe("generateFor", () => {
901
- it("generates empty for loop (infinite loop)", () => {
902
- const ctx = createMockForStatement();
902
+ it("rejects an empty for(;;) header as a disguised infinite loop (ADR-113 / #1075, E0707)", () => {
903
+ const ctx = createMockForStatement(); // no controlling expression
903
904
  const input = createMockInput();
904
905
  const state = createMockState();
905
906
  const orchestrator = createMockOrchestrator({ statementCode: "{ }" });
906
907
 
907
- const result = generateFor(ctx, input, state, orchestrator);
908
-
909
- expect(result.code).toBe("for (; ; ) { }");
908
+ expect(() => generateFor(ctx, input, state, orchestrator)).toThrow(
909
+ /E0707: for-loop has no controlling expression/,
910
+ );
910
911
  });
911
912
 
912
913
  it("generates for loop with all parts", () => {
@@ -947,6 +948,7 @@ describe("ControlFlowGenerator", () => {
947
948
  operator: createMockAssignmentOperator("<-"),
948
949
  }),
949
950
  }),
951
+ expr: createMockExpression({ text: "i < 10" }),
950
952
  });
951
953
  const input = createMockInput();
952
954
  const state = createMockState();
@@ -1007,6 +1009,7 @@ describe("ControlFlowGenerator", () => {
1007
1009
  init: createMockForInit({
1008
1010
  varDecl: createMockForVarDecl(),
1009
1011
  }),
1012
+ expr: createMockExpression({ text: "i < 10" }),
1010
1013
  });
1011
1014
  const input = createMockInput();
1012
1015
  const state = createMockState();
@@ -71,7 +71,10 @@ class AssignmentExpectedTypeResolver {
71
71
  // Case 2b: Simple array element access (arr[i] <- value)
72
72
  // Issue #872: Resolve element type for MISRA 7.2 U suffix
73
73
  if (identifiers.length === 1 && hasSubscript) {
74
- return AssignmentExpectedTypeResolver.resolveForArrayElement(baseId);
74
+ return AssignmentExpectedTypeResolver.resolveForArrayElement(
75
+ baseId,
76
+ postfixOps,
77
+ );
75
78
  }
76
79
 
77
80
  // Case 2c: Member chain with array access (struct.arr[i] <- value)
@@ -87,6 +90,17 @@ class AssignmentExpectedTypeResolver {
87
90
  return { expectedType: null, assignmentContext: null };
88
91
  }
89
92
 
93
+ /**
94
+ * True if any postfix subscript is the 2-expression `[offset, length]` form —
95
+ * an array slice or scalar bit-range write (vs a 1-expression element/bit
96
+ * access). The grammar models both as `'[' expression ',' expression ']'`.
97
+ */
98
+ private static hasRangeSubscript(
99
+ postfixOps: Parser.PostfixTargetOpContext[],
100
+ ): boolean {
101
+ return postfixOps.some((op) => op.expression().length === 2);
102
+ }
103
+
90
104
  /**
91
105
  * Resolve expected type for a simple identifier target.
92
106
  */
@@ -124,13 +138,27 @@ class AssignmentExpectedTypeResolver {
124
138
  /**
125
139
  * Resolve expected type for array element access.
126
140
  * Issue #872: arr[i] <- value needs baseType for MISRA 7.2 U suffix.
141
+ *
142
+ * An array SLICE (2-expression subscript `arr[off, len]`) is the exception:
143
+ * its source serializes at the SOURCE's own width, so leaking the element type
144
+ * as expectedType truncates an element-width-sensitive source such as a
145
+ * bit-extraction (Issue #1085: `buf[0,4] <- b[0,32]` wrote only the low byte).
146
+ * This applies only to arrays — a 2-expression subscript on a scalar is a
147
+ * bit-range write, whose value is genuinely the field's type (unchanged).
127
148
  */
128
- private static resolveForArrayElement(id: string): IExpectedTypeResult {
149
+ private static resolveForArrayElement(
150
+ id: string,
151
+ postfixOps: Parser.PostfixTargetOpContext[],
152
+ ): IExpectedTypeResult {
129
153
  const typeInfo = CodeGenState.getVariableTypeInfo(id);
130
154
  if (!typeInfo?.isArray) {
131
155
  return { expectedType: null, assignmentContext: null };
132
156
  }
133
157
 
158
+ if (AssignmentExpectedTypeResolver.hasRangeSubscript(postfixOps)) {
159
+ return { expectedType: null, assignmentContext: null };
160
+ }
161
+
134
162
  // Element type is the baseType (e.g., u8[10] -> "u8")
135
163
  return { expectedType: typeInfo.baseType, assignmentContext: null };
136
164
  }
@@ -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 "";