c-next 0.2.17 → 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 (81) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +7645 -5941
  3. package/dist/index.js.map +4 -4
  4. package/grammar/CNext.g4 +8 -0
  5. package/package.json +1 -3
  6. package/src/transpiler/Transpiler.ts +286 -26
  7. package/src/transpiler/__tests__/compileCommandsDiscovery.integration.test.ts +94 -0
  8. package/src/transpiler/__tests__/externalSymbolRecovery.integration.test.ts +215 -0
  9. package/src/transpiler/logic/__tests__/detectAssemblySyntax.test.ts +116 -0
  10. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +8 -0
  11. package/src/transpiler/logic/analysis/MixedTypeCategoryAnalyzer.ts +408 -0
  12. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +16 -97
  13. package/src/transpiler/logic/analysis/ReturnPathAnalyzer.ts +171 -0
  14. package/src/transpiler/logic/analysis/__tests__/MixedTypeCategoryAnalyzer.test.ts +346 -0
  15. package/src/transpiler/logic/analysis/__tests__/ReturnPathAnalyzer.test.ts +232 -0
  16. package/src/transpiler/logic/analysis/runAnalyzers.ts +23 -2
  17. package/src/transpiler/logic/analysis/types/IMixedTypeCategoryError.ts +17 -0
  18. package/src/transpiler/logic/analysis/types/IReturnPathError.ts +12 -0
  19. package/src/transpiler/logic/detectAssemblySyntax.ts +80 -0
  20. package/src/transpiler/logic/parser/grammar/CNext.interp +4 -1
  21. package/src/transpiler/logic/parser/grammar/CNext.tokens +169 -167
  22. package/src/transpiler/logic/parser/grammar/CNextLexer.interp +4 -1
  23. package/src/transpiler/logic/parser/grammar/CNextLexer.tokens +169 -167
  24. package/src/transpiler/logic/parser/grammar/CNextLexer.ts +545 -541
  25. package/src/transpiler/logic/parser/grammar/CNextListener.ts +11 -0
  26. package/src/transpiler/logic/parser/grammar/CNextParser.ts +1257 -1185
  27. package/src/transpiler/logic/parser/grammar/CNextVisitor.ts +7 -0
  28. package/src/transpiler/logic/preprocessor/CompileCommandsReader.ts +332 -0
  29. package/src/transpiler/logic/preprocessor/ExternalDeclarationOracle.ts +202 -0
  30. package/src/transpiler/logic/preprocessor/Preprocessor.ts +24 -6
  31. package/src/transpiler/logic/preprocessor/ToolchainDetector.ts +42 -1
  32. package/src/transpiler/logic/preprocessor/__tests__/CompileCommandsReader.test.ts +216 -0
  33. package/src/transpiler/logic/preprocessor/__tests__/ExternalDeclarationOracle.test.ts +153 -0
  34. package/src/transpiler/logic/preprocessor/__tests__/Preprocessor.test.ts +43 -18
  35. package/src/transpiler/logic/preprocessor/__tests__/ToolchainDetector.crossCompiler.test.ts +75 -0
  36. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/dependent.h +7 -0
  37. package/src/transpiler/logic/preprocessor/__tests__/fixtures/oracle/predecessor.h +5 -0
  38. package/src/transpiler/logic/preprocessor/types/ICompileCommandsResult.ts +14 -0
  39. package/src/transpiler/logic/preprocessor/types/IPreprocessOptions.ts +17 -0
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +45 -0
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +49 -0
  42. package/src/transpiler/output/MisraSuppressionUtils.ts +52 -0
  43. package/src/transpiler/output/__tests__/MisraSuppressionUtils.test.ts +67 -0
  44. package/src/transpiler/output/codegen/CodeGenerator.ts +45 -4
  45. package/src/transpiler/output/codegen/TypeResolver.ts +148 -0
  46. package/src/transpiler/output/codegen/TypeValidator.ts +179 -81
  47. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +13 -1
  48. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +93 -0
  49. package/src/transpiler/output/codegen/__tests__/TypeValidator.alwaysTrueLoop.test.ts +91 -0
  50. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +86 -14
  51. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +13 -0
  52. package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +1 -1
  53. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +20 -12
  54. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +424 -22
  55. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +31 -18
  56. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +7 -8
  57. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +6 -8
  58. package/src/transpiler/output/codegen/assignment/handlers/RegisterUtils.ts +12 -10
  59. package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +3 -7
  60. package/src/transpiler/output/codegen/assignment/handlers/SpecialHandlers.ts +7 -9
  61. package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +12 -10
  62. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +479 -11
  63. package/src/transpiler/output/codegen/generators/IOrchestrator.ts +3 -0
  64. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +28 -15
  65. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +26 -13
  66. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +129 -1
  67. package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +64 -8
  68. package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +8 -5
  69. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +30 -2
  70. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +16 -13
  71. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +19 -0
  72. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +57 -11
  73. package/src/transpiler/output/codegen/subscript/SubscriptClassifier.ts +30 -11
  74. package/src/transpiler/output/codegen/subscript/TSubscriptKind.ts +1 -1
  75. package/src/transpiler/output/codegen/subscript/__tests__/SubscriptClassifier.test.ts +38 -6
  76. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +65 -24
  77. package/src/transpiler/state/CodeGenState.ts +20 -16
  78. package/src/transpiler/types/ICachedFileEntry.ts +7 -0
  79. package/src/utils/cache/CacheManager.ts +13 -2
  80. package/src/utils/cache/__tests__/CacheManager.test.ts +18 -1
  81. package/src/utils/constants/TypeConstants.ts +13 -0
@@ -16,6 +16,22 @@ vi.mock("../../../TypeValidator", () => ({
16
16
  },
17
17
  }));
18
18
 
19
+ // Slice codegen resolves the source value's type (Issue #1081 review) — mock it
20
+ // so slice tests can control the source type independently of a real parse tree.
21
+ const { mockGetExpressionType, mockGetIntegerExpressionType } = vi.hoisted(
22
+ () => ({
23
+ mockGetExpressionType: vi.fn(),
24
+ mockGetIntegerExpressionType: vi.fn(),
25
+ }),
26
+ );
27
+
28
+ vi.mock("../../../TypeResolver", () => ({
29
+ default: {
30
+ getExpressionType: mockGetExpressionType,
31
+ getIntegerExpressionType: mockGetIntegerExpressionType,
32
+ },
33
+ }));
34
+
19
35
  import arrayHandlers from "../ArrayHandlers";
20
36
  import AssignmentKind from "../../AssignmentKind";
21
37
  import IAssignmentContext from "../../IAssignmentContext";
@@ -42,6 +58,9 @@ function createMockContext(
42
58
  cOp: "=",
43
59
  generatedValue: "value",
44
60
  targetCtx: {} as never,
61
+ // Truthy so slice codegen resolves the source type via the mocked
62
+ // TypeResolver.getExpressionType (Issue #1081 review).
63
+ valueCtx: {} as never,
45
64
  hasThis: false,
46
65
  hasGlobal: false,
47
66
  hasMemberAccess: false,
@@ -63,6 +82,9 @@ describe("ArrayHandlers", () => {
63
82
  CodeGenState.reset();
64
83
  HandlerTestUtils.setupMockGenerator();
65
84
  HandlerTestUtils.setupMockSymbols();
85
+ // Default: a source whose direct type is null is also unresolved as a
86
+ // composite. Tests that exercise composite resolution override this.
87
+ mockGetIntegerExpressionType.mockReturnValue(null);
66
88
  });
67
89
 
68
90
  describe("handler registration", () => {
@@ -241,32 +263,231 @@ describe("ArrayHandlers", () => {
241
263
  const getHandler = () =>
242
264
  arrayHandlers.find(([kind]) => kind === AssignmentKind.ARRAY_SLICE)?.[1];
243
265
 
244
- it("generates memcpy for valid slice assignment", () => {
266
+ // Every unrolled slice copy is prefixed with a comment naming the MISRA
267
+ // rule it satisfies and why the codegen looks the way it does (Issue #1081).
268
+ // Mirror production sliceUnrollComment: the rule is cited ONLY when an
269
+ // equivalent memcpy would pass incompatible pointer types.
270
+ const sliceComment = (destCType: string, srcCType: string) =>
271
+ "/* MISRA C:2012 Rule 21.15: slice copy unrolled to per-element writes " +
272
+ `(memcpy would pass incompatible pointer types: ${destCType}* vs ${srcCType}*). */`;
273
+
274
+ // Default the source type to a wide unsigned; tests override per case.
275
+ beforeEach(() => mockGetExpressionType.mockReturnValue("u64"));
276
+
277
+ // Issue #1081: slice assignment lowers to per-element little-endian writes
278
+ // (no memcpy), keeping MISRA C:2012 Rule 21.15 (compatible memcpy pointers)
279
+ // satisfied because no incompatible pointer punning is emitted.
280
+ it("generates unrolled byte writes for a u8 slice (no memcpy/string.h)", () => {
281
+ mockGetExpressionType.mockReturnValue("u32");
245
282
  HandlerTestUtils.setupMockTypeRegistry([
246
- ["buffer", { arrayDimensions: [100], baseType: "u8" }],
283
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
247
284
  ]);
248
285
  HandlerTestUtils.setupMockGenerator({
249
286
  tryEvaluateConstant: vi
250
287
  .fn()
251
288
  .mockReturnValueOnce(0)
252
- .mockReturnValueOnce(10),
289
+ .mockReturnValueOnce(4),
253
290
  });
254
291
  const ctx = createMockContext({
255
292
  identifiers: ["buffer"],
256
293
  subscripts: [
257
294
  { mockValue: "0", start: { line: 1 } } as never,
258
- { mockValue: "10", start: { line: 1 } } as never,
295
+ { mockValue: "4", start: { line: 1 } } as never,
259
296
  ],
260
297
  generatedValue: "source",
261
298
  });
262
299
 
263
300
  const result = getHandler()!(ctx);
264
301
 
265
- expect(result).toBe("memcpy(&buffer[0], &source, 10);");
266
- expect(CodeGenState.needsString).toBe(true);
302
+ // The source is materialized into a single unsigned temp so it is read
303
+ // exactly once (Issue #1085 review, Finding 2).
304
+ expect(result).toBe(
305
+ `${sliceComment("uint8_t", "uint32_t")}\n` +
306
+ "const uint32_t _tmp0 = (uint32_t)(source);\n" +
307
+ "buffer[0] = (uint8_t)(_tmp0);\n" +
308
+ "buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
309
+ "buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
310
+ "buffer[3] = (uint8_t)(_tmp0 >> 24U);",
311
+ );
312
+ // No memcpy means <string.h> is not required.
313
+ expect(CodeGenState.needsString).toBe(false);
314
+ });
315
+
316
+ it("writes at element granularity for a u16 slice (offset = element index)", () => {
317
+ mockGetExpressionType.mockReturnValue("u64");
318
+ HandlerTestUtils.setupMockTypeRegistry([
319
+ ["arr16", { arrayDimensions: [16], baseType: "u16", bitWidth: 16 }],
320
+ ]);
321
+ HandlerTestUtils.setupMockGenerator({
322
+ tryEvaluateConstant: vi
323
+ .fn()
324
+ .mockReturnValueOnce(0)
325
+ .mockReturnValueOnce(8),
326
+ });
327
+ const ctx = createMockContext({
328
+ identifiers: ["arr16"],
329
+ subscripts: [
330
+ { mockValue: "0", start: { line: 1 } } as never,
331
+ { mockValue: "8", start: { line: 1 } } as never,
332
+ ],
333
+ generatedValue: "value",
334
+ });
335
+
336
+ const result = getHandler()!(ctx);
337
+
338
+ expect(result).toBe(
339
+ `${sliceComment("uint16_t", "uint64_t")}\n` +
340
+ "const uint64_t _tmp0 = (uint64_t)(value);\n" +
341
+ "arr16[0] = (uint16_t)(_tmp0);\n" +
342
+ "arr16[1] = (uint16_t)(_tmp0 >> 16U);\n" +
343
+ "arr16[2] = (uint16_t)(_tmp0 >> 32U);\n" +
344
+ "arr16[3] = (uint16_t)(_tmp0 >> 48U);",
345
+ );
267
346
  });
268
347
 
269
- it("generates memcpy for string slice", () => {
348
+ it("omits the rule citation when source and element types match (no 21.15)", () => {
349
+ mockGetExpressionType.mockReturnValue("u32");
350
+ HandlerTestUtils.setupMockTypeRegistry([
351
+ ["arr32", { arrayDimensions: [16], baseType: "u32", bitWidth: 32 }],
352
+ ]);
353
+ HandlerTestUtils.setupMockGenerator({
354
+ tryEvaluateConstant: vi
355
+ .fn()
356
+ .mockReturnValueOnce(0)
357
+ .mockReturnValueOnce(4),
358
+ });
359
+ const ctx = createMockContext({
360
+ identifiers: ["arr32"],
361
+ subscripts: [
362
+ { mockValue: "0", start: { line: 1 } } as never,
363
+ { mockValue: "4", start: { line: 1 } } as never,
364
+ ],
365
+ generatedValue: "value",
366
+ });
367
+
368
+ const result = getHandler()!(ctx);
369
+
370
+ // u32[] <- u32: an equivalent memcpy would be compliant, so no comment.
371
+ expect(result).toBe("arr32[0] = (uint32_t)(value);");
372
+ });
373
+
374
+ it("accepts an in-bounds wide-element slice at a non-zero offset (Finding 1)", () => {
375
+ mockGetExpressionType.mockReturnValue("u64");
376
+ HandlerTestUtils.setupMockTypeRegistry([
377
+ ["arr64", { arrayDimensions: [8], baseType: "u64", bitWidth: 64 }],
378
+ ]);
379
+ HandlerTestUtils.setupMockGenerator({
380
+ tryEvaluateConstant: vi
381
+ .fn()
382
+ .mockReturnValueOnce(4)
383
+ .mockReturnValueOnce(8),
384
+ });
385
+ const ctx = createMockContext({
386
+ identifiers: ["arr64"],
387
+ subscripts: [
388
+ { mockValue: "4", start: { line: 1 } } as never,
389
+ { mockValue: "8", start: { line: 1 } } as never,
390
+ ],
391
+ generatedValue: "value",
392
+ });
393
+
394
+ const result = getHandler()!(ctx);
395
+
396
+ // Element span is offset(4) + 1 element = 5 <= capacity 8. The old check
397
+ // compared byte length against element capacity (4 + 8 = 12 > 8) and
398
+ // wrongly rejected this in-bounds slice. Single element → no temp;
399
+ // same-type u64[] <- u64 → no rule citation.
400
+ expect(result).toBe("arr64[4] = (uint64_t)(value);");
401
+ });
402
+
403
+ it("reports the element span (not bytes) in the out-of-bounds message", () => {
404
+ mockGetExpressionType.mockReturnValue("u64");
405
+ HandlerTestUtils.setupMockTypeRegistry([
406
+ ["arr16", { arrayDimensions: [4], baseType: "u16", bitWidth: 16 }],
407
+ ]);
408
+ HandlerTestUtils.setupMockGenerator({
409
+ tryEvaluateConstant: vi
410
+ .fn()
411
+ .mockReturnValueOnce(2)
412
+ .mockReturnValueOnce(8),
413
+ });
414
+ const ctx = createMockContext({
415
+ identifiers: ["arr16"],
416
+ subscripts: [
417
+ { mockValue: "2", start: { line: 7 } } as never,
418
+ { mockValue: "8", start: { line: 7 } } as never,
419
+ ],
420
+ generatedValue: "value",
421
+ });
422
+
423
+ // offset 2 + (8 bytes / 2) = 6 elements > capacity 4.
424
+ expect(() => getHandler()!(ctx)).toThrow(
425
+ "offset(2) + 4 element(s) = 6 exceeds buffer capacity(4)",
426
+ );
427
+ });
428
+
429
+ it("casts a signed source to unsigned once in the temp (MISRA 10.1)", () => {
430
+ mockGetExpressionType.mockReturnValue("i32");
431
+ HandlerTestUtils.setupMockTypeRegistry([
432
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
433
+ ]);
434
+ HandlerTestUtils.setupMockGenerator({
435
+ tryEvaluateConstant: vi
436
+ .fn()
437
+ .mockReturnValueOnce(0)
438
+ .mockReturnValueOnce(4),
439
+ });
440
+ const ctx = createMockContext({
441
+ identifiers: ["buffer"],
442
+ subscripts: [
443
+ { mockValue: "0", start: { line: 1 } } as never,
444
+ { mockValue: "4", start: { line: 1 } } as never,
445
+ ],
446
+ generatedValue: "value",
447
+ });
448
+
449
+ const result = getHandler()!(ctx);
450
+
451
+ // The signed source is cast to its same-width unsigned type once, in the
452
+ // temp declaration, so every shift on the temp is MISRA Rule 10.1-clean.
453
+ expect(result).toBe(
454
+ `${sliceComment("uint8_t", "int32_t")}\n` +
455
+ "const uint32_t _tmp0 = (uint32_t)(value);\n" +
456
+ "buffer[0] = (uint8_t)(_tmp0);\n" +
457
+ "buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
458
+ "buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
459
+ "buffer[3] = (uint8_t)(_tmp0 >> 24U);",
460
+ );
461
+ });
462
+
463
+ it("uses the signed-element double-cast for a signed destination (MISRA 10.8)", () => {
464
+ mockGetExpressionType.mockReturnValue("i32");
465
+ HandlerTestUtils.setupMockTypeRegistry([
466
+ ["arrI", { arrayDimensions: [16], baseType: "i32", bitWidth: 32 }],
467
+ ]);
468
+ HandlerTestUtils.setupMockGenerator({
469
+ tryEvaluateConstant: vi
470
+ .fn()
471
+ .mockReturnValueOnce(0)
472
+ .mockReturnValueOnce(4),
473
+ });
474
+ const ctx = createMockContext({
475
+ identifiers: ["arrI"],
476
+ subscripts: [
477
+ { mockValue: "0", start: { line: 1 } } as never,
478
+ { mockValue: "4", start: { line: 1 } } as never,
479
+ ],
480
+ generatedValue: "value",
481
+ });
482
+
483
+ const result = getHandler()!(ctx);
484
+
485
+ // i32[] <- i32: same type, so no rule citation; dest cast still applies.
486
+ expect(result).toBe("arrI[0] = (int32_t)(uint32_t)(value);");
487
+ });
488
+
489
+ it("generates double-cast char writes for a string slice (MISRA 10.8)", () => {
490
+ mockGetExpressionType.mockReturnValue("u16");
270
491
  HandlerTestUtils.setupMockTypeRegistry([
271
492
  [
272
493
  "str",
@@ -282,20 +503,267 @@ describe("ArrayHandlers", () => {
282
503
  tryEvaluateConstant: vi
283
504
  .fn()
284
505
  .mockReturnValueOnce(5)
285
- .mockReturnValueOnce(10),
506
+ .mockReturnValueOnce(2),
286
507
  });
287
508
  const ctx = createMockContext({
288
509
  identifiers: ["str"],
289
510
  subscripts: [
290
511
  { mockValue: "5", start: { line: 1 } } as never,
291
- { mockValue: "10", start: { line: 1 } } as never,
512
+ { mockValue: "2", start: { line: 1 } } as never,
292
513
  ],
293
514
  generatedValue: "data",
294
515
  });
295
516
 
296
517
  const result = getHandler()!(ctx);
297
518
 
298
- expect(result).toBe("memcpy(&str[5], &data, 10);");
519
+ expect(result).toBe(
520
+ `${sliceComment("char", "uint16_t")}\n` +
521
+ "const uint16_t _tmp0 = (uint16_t)(data);\n" +
522
+ "str[5] = (char)(uint8_t)(_tmp0);\n" +
523
+ "str[6] = (char)(uint8_t)(_tmp0 >> 8U);",
524
+ );
525
+ });
526
+
527
+ it("sizes the temp to the slice length for an unresolved source type", () => {
528
+ mockGetExpressionType.mockReturnValue(null); // e.g. a computed expression
529
+ HandlerTestUtils.setupMockTypeRegistry([
530
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
531
+ ]);
532
+ HandlerTestUtils.setupMockGenerator({
533
+ tryEvaluateConstant: vi
534
+ .fn()
535
+ .mockReturnValueOnce(0)
536
+ .mockReturnValueOnce(4),
537
+ });
538
+ const ctx = createMockContext({
539
+ identifiers: ["buffer"],
540
+ subscripts: [
541
+ { mockValue: "0", start: { line: 1 } } as never,
542
+ { mockValue: "4", start: { line: 1 } } as never,
543
+ ],
544
+ generatedValue: "expr",
545
+ });
546
+
547
+ const result = getHandler()!(ctx);
548
+
549
+ // Unknown source: NO Rule 21.15 citation — we cannot prove the source type
550
+ // is incompatible with the destination element, so asserting "incompatible
551
+ // pointer types" would be false (Issue #1085 review, Finding #1). The temp
552
+ // is still sized to the 4-byte slice length (uint32_t), not the widest
553
+ // type — so the cast does not widen a composite expression like `a + b`
554
+ // (MISRA Rule 10.8), while every shift on the temp stays unsigned (10.1)
555
+ // and in range (Finding 3).
556
+ expect(result).toBe(
557
+ "const uint32_t _tmp0 = (uint32_t)(expr);\n" +
558
+ "buffer[0] = (uint8_t)(_tmp0);\n" +
559
+ "buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
560
+ "buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
561
+ "buffer[3] = (uint8_t)(_tmp0 >> 24U);",
562
+ );
563
+ });
564
+
565
+ it("two-steps a composite signed source so no composite is cast (Rule 10.8)", () => {
566
+ // Direct type unresolved (composite), but resolvable as a signed i32 via
567
+ // Rule 10.4's same-category guarantee. The source must be bound to its
568
+ // own signed type first, then reinterpreted — the unsigned cast lands on
569
+ // the simple temp, never on the `a + b` composite.
570
+ mockGetExpressionType.mockReturnValue(null);
571
+ mockGetIntegerExpressionType.mockReturnValue("i32");
572
+ HandlerTestUtils.setupMockTypeRegistry([
573
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
574
+ ]);
575
+ HandlerTestUtils.setupMockGenerator({
576
+ tryEvaluateConstant: vi
577
+ .fn()
578
+ .mockReturnValueOnce(0)
579
+ .mockReturnValueOnce(4),
580
+ });
581
+ const ctx = createMockContext({
582
+ identifiers: ["buffer"],
583
+ subscripts: [
584
+ { mockValue: "0", start: { line: 1 } } as never,
585
+ { mockValue: "4", start: { line: 1 } } as never,
586
+ ],
587
+ generatedValue: "a + b",
588
+ });
589
+
590
+ const result = getHandler()!(ctx);
591
+
592
+ expect(result).toBe(
593
+ "/* MISRA C:2012 Rule 21.15: slice copy unrolled to per-element writes " +
594
+ "(memcpy would pass incompatible pointer types: uint8_t* vs int32_t*). */\n" +
595
+ "const int32_t _tmp0 = a + b;\n" +
596
+ "const uint32_t _tmp1 = (uint32_t)_tmp0;\n" +
597
+ "buffer[0] = (uint8_t)(_tmp1);\n" +
598
+ "buffer[1] = (uint8_t)(_tmp1 >> 8U);\n" +
599
+ "buffer[2] = (uint8_t)(_tmp1 >> 16U);\n" +
600
+ "buffer[3] = (uint8_t)(_tmp1 >> 24U);",
601
+ );
602
+ });
603
+
604
+ it("throws when slice length is not a multiple of the element size", () => {
605
+ mockGetExpressionType.mockReturnValue("u64");
606
+ HandlerTestUtils.setupMockTypeRegistry([
607
+ ["arr16", { arrayDimensions: [16], baseType: "u16", bitWidth: 16 }],
608
+ ]);
609
+ HandlerTestUtils.setupMockGenerator({
610
+ tryEvaluateConstant: vi
611
+ .fn()
612
+ .mockReturnValueOnce(0)
613
+ .mockReturnValueOnce(3),
614
+ });
615
+ const ctx = createMockContext({
616
+ identifiers: ["arr16"],
617
+ subscripts: [
618
+ { mockValue: "0", start: { line: 7 } } as never,
619
+ { mockValue: "3", start: { line: 7 } } as never,
620
+ ],
621
+ generatedValue: "value",
622
+ });
623
+
624
+ expect(() => getHandler()!(ctx)).toThrow(
625
+ "must be a multiple of the element size",
626
+ );
627
+ });
628
+
629
+ it("throws when slice length exceeds the source value width", () => {
630
+ mockGetExpressionType.mockReturnValue("u32"); // 4-byte source
631
+ HandlerTestUtils.setupMockTypeRegistry([
632
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
633
+ ]);
634
+ HandlerTestUtils.setupMockGenerator({
635
+ tryEvaluateConstant: vi
636
+ .fn()
637
+ .mockReturnValueOnce(0)
638
+ .mockReturnValueOnce(8), // copying 8 bytes from a 4-byte value
639
+ });
640
+ const ctx = createMockContext({
641
+ identifiers: ["buffer"],
642
+ subscripts: [
643
+ { mockValue: "0", start: { line: 8 } } as never,
644
+ { mockValue: "8", start: { line: 8 } } as never,
645
+ ],
646
+ generatedValue: "value",
647
+ });
648
+
649
+ expect(() => getHandler()!(ctx)).toThrow(
650
+ "exceeds the source value width",
651
+ );
652
+ });
653
+
654
+ it("throws on a non-integer (float) slice source", () => {
655
+ mockGetExpressionType.mockReturnValue("f32");
656
+ HandlerTestUtils.setupMockTypeRegistry([
657
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
658
+ ]);
659
+ HandlerTestUtils.setupMockGenerator({
660
+ tryEvaluateConstant: vi
661
+ .fn()
662
+ .mockReturnValueOnce(0)
663
+ .mockReturnValueOnce(4),
664
+ });
665
+ const ctx = createMockContext({
666
+ identifiers: ["buffer"],
667
+ subscripts: [
668
+ { mockValue: "0", start: { line: 9 } } as never,
669
+ { mockValue: "4", start: { line: 9 } } as never,
670
+ ],
671
+ generatedValue: "fval",
672
+ });
673
+
674
+ expect(() => getHandler()!(ctx)).toThrow(
675
+ "source must be an integer value",
676
+ );
677
+ });
678
+
679
+ it("throws on slice assignment into a float array", () => {
680
+ HandlerTestUtils.setupMockTypeRegistry([
681
+ ["arrF", { arrayDimensions: [16], baseType: "f32", bitWidth: 32 }],
682
+ ]);
683
+ HandlerTestUtils.setupMockGenerator({
684
+ tryEvaluateConstant: vi
685
+ .fn()
686
+ .mockReturnValueOnce(0)
687
+ .mockReturnValueOnce(4),
688
+ });
689
+ const ctx = createMockContext({
690
+ identifiers: ["arrF"],
691
+ subscripts: [
692
+ { mockValue: "0", start: { line: 9 } } as never,
693
+ { mockValue: "4", start: { line: 9 } } as never,
694
+ ],
695
+ generatedValue: "value",
696
+ });
697
+
698
+ expect(() => getHandler()!(ctx)).toThrow(
699
+ "Slice assignment is not supported for element type",
700
+ );
701
+ });
702
+
703
+ it("rejects a negative literal that does not fit the slice (mirror of the positive overflow check)", () => {
704
+ // A negative literal types as null via getExpressionType (unary minus), but
705
+ // is still a compile-time constant. It must be fit-checked like a positive
706
+ // one: buf[0,1] <- -300 cannot fit a 1-byte slice (-300 < -128) and must be
707
+ // rejected, not silently truncated to (uint8_t)(-300) (Issue #1085 review).
708
+ mockGetExpressionType.mockReturnValue(null);
709
+ HandlerTestUtils.setupMockTypeRegistry([
710
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
711
+ ]);
712
+ HandlerTestUtils.setupMockGenerator({
713
+ tryEvaluateConstant: vi
714
+ .fn()
715
+ .mockReturnValueOnce(0) // offset
716
+ .mockReturnValueOnce(1) // length (1-byte slice)
717
+ .mockReturnValue(-300), // source value (folded; re-read by resolveLiteralSliceSource)
718
+ });
719
+ const ctx = createMockContext({
720
+ identifiers: ["buffer"],
721
+ subscripts: [
722
+ { mockValue: "0", start: { line: 7 } } as never,
723
+ { mockValue: "1", start: { line: 7 } } as never,
724
+ ],
725
+ generatedValue: "-300",
726
+ });
727
+
728
+ expect(() => getHandler()!(ctx)).toThrow(
729
+ "Slice assignment literal value (-300) does not fit in the 1-byte slice",
730
+ );
731
+ });
732
+
733
+ it("serializes an in-range negative literal as two's-complement little-endian bytes", () => {
734
+ // -1 fits a 4-byte slice (-2^31 <= -1 < 2^32), so it is accepted and written
735
+ // as 0xFFFFFFFF little-endian. The literal types to the slice width (u32),
736
+ // so an equivalent memcpy would be incompatible -> Rule 21.15 is cited.
737
+ mockGetExpressionType.mockReturnValue(null);
738
+ HandlerTestUtils.setupMockTypeRegistry([
739
+ ["buffer", { arrayDimensions: [100], baseType: "u8", bitWidth: 8 }],
740
+ ]);
741
+ HandlerTestUtils.setupMockGenerator({
742
+ tryEvaluateConstant: vi
743
+ .fn()
744
+ .mockReturnValueOnce(0) // offset
745
+ .mockReturnValueOnce(4) // length
746
+ .mockReturnValue(-1), // source value
747
+ });
748
+ const ctx = createMockContext({
749
+ identifiers: ["buffer"],
750
+ subscripts: [
751
+ { mockValue: "0", start: { line: 1 } } as never,
752
+ { mockValue: "4", start: { line: 1 } } as never,
753
+ ],
754
+ generatedValue: "-1",
755
+ });
756
+
757
+ const result = getHandler()!(ctx);
758
+
759
+ expect(result).toBe(
760
+ `${sliceComment("uint8_t", "uint32_t")}\n` +
761
+ "const uint32_t _tmp0 = (uint32_t)(-1);\n" +
762
+ "buffer[0] = (uint8_t)(_tmp0);\n" +
763
+ "buffer[1] = (uint8_t)(_tmp0 >> 8U);\n" +
764
+ "buffer[2] = (uint8_t)(_tmp0 >> 16U);\n" +
765
+ "buffer[3] = (uint8_t)(_tmp0 >> 24U);",
766
+ );
299
767
  });
300
768
 
301
769
  it("throws on compound assignment", () => {
@@ -382,7 +850,7 @@ describe("ArrayHandlers", () => {
382
850
 
383
851
  it("throws on out of bounds access", () => {
384
852
  HandlerTestUtils.setupMockTypeRegistry([
385
- ["buffer", { arrayDimensions: [50], baseType: "u8" }],
853
+ ["buffer", { arrayDimensions: [50], baseType: "u8", bitWidth: 8 }],
386
854
  ]);
387
855
  HandlerTestUtils.setupMockGenerator({
388
856
  tryEvaluateConstant: vi
@@ -214,6 +214,9 @@ interface IOrchestrator {
214
214
  conditionType: string,
215
215
  ): void;
216
216
 
217
+ /** Reject an always-true literal loop condition (ADR-113 / #1075, E0707) */
218
+ validateLoopConditionNotAlwaysTrue(ctx: Parser.ExpressionContext): void;
219
+
217
220
  /** Validate no function calls in condition (Issue #254, E0702) */
218
221
  validateConditionNoFunctionCall(
219
222
  ctx: Parser.ExpressionContext,
@@ -104,6 +104,29 @@ const _parameterExpectsAddressOf = (
104
104
  return paramBaseType === argType;
105
105
  };
106
106
 
107
+ /**
108
+ * Resolve an argument expression's C-Next type for the address-of decision.
109
+ * Prefers the expression type, then the codegen variable registry, then the C
110
+ * symbol table — the last covers extern C globals (e.g. an lvgl font
111
+ * `lv_font_montserrat_32` of type `lv_font_t`, Issue #985) whose type only
112
+ * header symbol collection knows. Returns null when the type can't be resolved
113
+ * or the argument is already an address-of expression.
114
+ */
115
+ const _resolveArgType = (
116
+ e: ExpressionContext,
117
+ argCode: string,
118
+ typeInfoBaseType: string | undefined,
119
+ orchestrator: IOrchestrator,
120
+ ): string | null => {
121
+ const exprType = orchestrator.getExpressionType(e);
122
+ if (exprType) return exprType;
123
+ if (argCode.startsWith("&")) return null;
124
+ if (typeInfoBaseType) return typeInfoBaseType;
125
+ const cSymbol = CodeGenState.symbolTable?.getCSymbol(argCode);
126
+ if (cSymbol?.kind === "variable" && !cSymbol.isArray) return cSymbol.type;
127
+ return null;
128
+ };
129
+
107
130
  /**
108
131
  * Generate argument code for a C/C++ function call.
109
132
  * Handles automatic address-of (&) for struct arguments passed to pointer params.
@@ -148,22 +171,12 @@ const _generateCFunctionArg = (
148
171
  return wrapWithCppEnumCast(argCode, e, targetParam?.baseType, orchestrator);
149
172
  }
150
173
 
151
- // Try getExpressionType first
152
- let argType = orchestrator.getExpressionType(e);
153
-
154
- // Issue #322: If getExpressionType returns null (e.g., for this.member),
155
- // fall back to looking up the generated code in the type registry
156
- let isPointerVariable = false;
174
+ // Resolve the argument's type (expression type → variable registry → C symbol
175
+ // table for extern globals) to decide whether it needs address-of.
157
176
  const typeInfo = CodeGenState.getVariableTypeInfo(argCode);
158
- if (!argType && !argCode.startsWith("&")) {
159
- if (typeInfo) {
160
- argType = typeInfo.baseType;
161
- }
162
- }
163
- // Issue #895 Bug B: Check if variable was inferred as a pointer
164
- if (typeInfo?.isPointer) {
165
- isPointerVariable = true;
166
- }
177
+ const argType = _resolveArgType(e, argCode, typeInfo?.baseType, orchestrator);
178
+ // Issue #895 Bug B: a variable already inferred as a pointer must not get `&`.
179
+ const isPointerVariable = typeInfo?.isPointer ?? false;
167
180
 
168
181
  // Issue #948: Check if argument is an opaque scope variable (already a pointer)
169
182
  // Issue #996: ...including an element of an opaque-handle array (arr[i])