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
@@ -657,48 +657,7 @@ class TypeValidator {
657
657
  // ========================================================================
658
658
 
659
659
  static validateTernaryCondition(ctx: Parser.OrExpressionContext): void {
660
- const text = ctx.getText();
661
-
662
- if (ctx.andExpression().length > 1) {
663
- return;
664
- }
665
-
666
- const andExpr = ctx.andExpression(0);
667
- if (!andExpr) {
668
- throw new Error(
669
- `Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
670
- );
671
- }
672
-
673
- if (andExpr.equalityExpression().length > 1) {
674
- return;
675
- }
676
-
677
- const equalityExpr = andExpr.equalityExpression(0);
678
- if (!equalityExpr) {
679
- throw new Error(
680
- `Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
681
- );
682
- }
683
-
684
- if (equalityExpr.relationalExpression().length > 1) {
685
- return;
686
- }
687
-
688
- const relationalExpr = equalityExpr.relationalExpression(0);
689
- if (!relationalExpr) {
690
- throw new Error(
691
- `Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
692
- );
693
- }
694
-
695
- if (relationalExpr.bitwiseOrExpression().length > 1) {
696
- return;
697
- }
698
-
699
- throw new Error(
700
- `Error: Ternary condition must be a boolean expression (comparison or logical operation), not '${text}'`,
701
- );
660
+ TypeValidator._validateConditionOrExpression(ctx, "ternary");
702
661
  }
703
662
 
704
663
  static validateNoNestedTernary(
@@ -730,75 +689,214 @@ class TypeValidator {
730
689
  );
731
690
  }
732
691
 
733
- const orExpr = orExprs[0];
734
- const text = orExpr.getText();
735
-
736
- if (orExpr.andExpression().length > 1) {
737
- return;
738
- }
692
+ TypeValidator._validateConditionOrExpression(orExprs[0], conditionType);
693
+ }
739
694
 
740
- const andExpr = orExpr.andExpression(0);
741
- if (!andExpr) {
742
- throw new Error(
743
- `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`,
744
- );
695
+ /**
696
+ * MISRA C:2012 Rule 14.4 (Issue #1042): a controlling expression must be an
697
+ * explicit comparison. Every leaf operand — after decomposing `||` and `&&` —
698
+ * must itself be an equality (`=`, `!=`) or relational (`<`, `>`, `<=`, `>=`)
699
+ * comparison. A bare value, a bare boolean (local, parameter, or `this.`/
700
+ * `global.` member), a literal, or a negation (`!x`) is rejected; use an
701
+ * explicit form such as `x = true`. This is the single decision point shared
702
+ * by `if`/`while`/`for`/`do-while` and ternary conditions.
703
+ */
704
+ private static _validateConditionOrExpression(
705
+ orExpr: Parser.OrExpressionContext,
706
+ conditionType: string,
707
+ ): void {
708
+ const andExprs = orExpr.andExpression();
709
+ if (andExprs.length === 0) {
710
+ TypeValidator._throwConditionNotBoolean(orExpr, conditionType);
745
711
  }
746
712
 
747
- if (andExpr.equalityExpression().length > 1) {
748
- return;
749
- }
713
+ for (const andExpr of andExprs) {
714
+ const equalityExprs = andExpr.equalityExpression();
715
+ if (equalityExprs.length === 0) {
716
+ TypeValidator._throwConditionNotBoolean(orExpr, conditionType);
717
+ }
750
718
 
751
- const equalityExpr = andExpr.equalityExpression(0);
752
- if (!equalityExpr) {
753
- throw new Error(
754
- `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`,
755
- );
719
+ for (const equalityExpr of equalityExprs) {
720
+ TypeValidator._validateConditionIsComparison(
721
+ equalityExpr,
722
+ orExpr,
723
+ conditionType,
724
+ );
725
+ }
756
726
  }
727
+ }
757
728
 
729
+ private static _validateConditionIsComparison(
730
+ equalityExpr: Parser.EqualityExpressionContext,
731
+ orExpr: Parser.OrExpressionContext,
732
+ conditionType: string,
733
+ ): void {
734
+ // An equality operator (`=`, `!=`) makes this operand a comparison.
758
735
  if (equalityExpr.relationalExpression().length > 1) {
759
736
  return;
760
737
  }
761
738
 
762
739
  const relationalExpr = equalityExpr.relationalExpression(0);
763
740
  if (!relationalExpr) {
764
- throw new Error(
765
- `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)`,
766
- );
741
+ TypeValidator._throwConditionNotBoolean(orExpr, conditionType);
742
+ return;
767
743
  }
768
744
 
745
+ // A relational operator (`<`, `>`, `<=`, `>=`) makes this operand a comparison.
769
746
  if (relationalExpr.bitwiseOrExpression().length > 1) {
770
747
  return;
771
748
  }
772
749
 
773
- const bitwiseOrExpr = relationalExpr.bitwiseOrExpression(0);
774
- if (bitwiseOrExpr && TypeValidator._isBooleanExpression(bitwiseOrExpr)) {
775
- return;
776
- }
750
+ // No comparison operator: a bare value, bare boolean, literal, or negation.
751
+ TypeValidator._throwConditionNotBoolean(equalityExpr, conditionType);
752
+ }
777
753
 
754
+ private static _throwConditionNotBoolean(
755
+ node: Parser.OrExpressionContext | Parser.EqualityExpressionContext,
756
+ conditionType: string,
757
+ ): void {
758
+ const text = node.getText();
778
759
  throw new Error(
779
- `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)\n help: use explicit comparison: ${text} > 0 or ${text} != 0`,
760
+ `Error E0701: ${conditionType} condition must be a boolean expression (comparison or logical operation), not '${text}' (MISRA C:2012 Rule 14.4)\n help: ${TypeValidator._conditionHelp(text)}`,
780
761
  );
781
762
  }
782
763
 
783
- private static _isBooleanExpression(
784
- ctx: Parser.BitwiseOrExpressionContext,
785
- ): boolean {
786
- const text = ctx.getText();
764
+ // ========================================================================
765
+ // Disguised Infinite Loop Validation (ADR-113 / #1075, E0707)
766
+ // ========================================================================
787
767
 
788
- if (text === "true" || text === "false") {
789
- return true;
768
+ /**
769
+ * ADR-113 / #1075 (E0707): reject a loop whose controlling expression is an
770
+ * always-TRUE comparison of literal operands (`while (1 = 1)`, `5 > 3`,
771
+ * `true = true`). C-Next has one source form for an intentional infinite loop —
772
+ * `forever`. This is the v0.2.18 *literal* slice only: named constants and
773
+ * non-literal operands need symbol resolution (out of scope), and always-FALSE
774
+ * conditions are a separate MISRA 14.3 case — both tracked in #1076.
775
+ *
776
+ * Runs after the E0701 boolean check, so the condition is already a comparison.
777
+ */
778
+ static validateLoopConditionNotAlwaysTrue(
779
+ ctx: Parser.ExpressionContext,
780
+ ): void {
781
+ const comparison = TypeValidator._asSingleLiteralComparison(ctx);
782
+ if (comparison && TypeValidator._comparisonIsAlwaysTrue(comparison)) {
783
+ throw new Error(
784
+ `Error E0707: loop condition '${ctx.getText()}' is always true\n` +
785
+ " help: write 'forever { ... }' for an intentional infinite loop",
786
+ );
790
787
  }
788
+ }
791
789
 
792
- if (text.startsWith("!")) {
793
- return true;
790
+ /**
791
+ * If `ctx` is a single comparison of two literal operands (no `||`/`&&`, no
792
+ * ternary, no chaining), return its operator and the two literal values.
793
+ * Otherwise null — anything involving identifiers, floats, or sub-expressions
794
+ * is left to the full MISRA 14.3 effort (#1076).
795
+ */
796
+ private static _asSingleLiteralComparison(
797
+ ctx: Parser.ExpressionContext,
798
+ ): { operator: string; left: number; right: number } | null {
799
+ const orExprs = ctx.ternaryExpression().orExpression();
800
+ if (orExprs.length !== 1) return null;
801
+ const andExprs = orExprs[0].andExpression();
802
+ if (andExprs.length !== 1) return null;
803
+ const equalityExprs = andExprs[0].equalityExpression();
804
+ if (equalityExprs.length !== 1) return null;
805
+ const equalityExpr = equalityExprs[0];
806
+
807
+ const relationalExprs = equalityExpr.relationalExpression();
808
+ if (relationalExprs.length === 2) {
809
+ // Equality comparison: relExpr ('=' | '!=') relExpr
810
+ return TypeValidator._buildLiteralComparison(
811
+ equalityExpr.getChild(1)?.getText(),
812
+ relationalExprs[0].getText(),
813
+ relationalExprs[1].getText(),
814
+ );
815
+ }
816
+ if (relationalExprs.length === 1) {
817
+ const bitwiseOrExprs = relationalExprs[0].bitwiseOrExpression();
818
+ if (bitwiseOrExprs.length === 2) {
819
+ // Relational comparison: orExpr ('<' | '>' | '<=' | '>=') orExpr
820
+ return TypeValidator._buildLiteralComparison(
821
+ relationalExprs[0].getChild(1)?.getText(),
822
+ bitwiseOrExprs[0].getText(),
823
+ bitwiseOrExprs[1].getText(),
824
+ );
825
+ }
794
826
  }
827
+ return null;
828
+ }
795
829
 
796
- const typeInfo = CodeGenState.getVariableTypeInfo(text);
797
- if (typeInfo?.baseType === "bool") {
798
- return true;
830
+ private static _buildLiteralComparison(
831
+ operator: string | undefined,
832
+ leftText: string,
833
+ rightText: string,
834
+ ): { operator: string; left: number; right: number } | null {
835
+ const left = TypeValidator._literalValue(leftText);
836
+ const right = TypeValidator._literalValue(rightText);
837
+ if (operator === undefined || left === null || right === null) return null;
838
+ return { operator, left, right };
839
+ }
840
+
841
+ /**
842
+ * Strict literal-to-number for the E0707 literal slice: integer literals
843
+ * (decimal/hex/binary, optional type suffix) and `true`/`false`. Floats,
844
+ * strings, chars, identifiers, and any compound text return null so they are
845
+ * not treated as compile-time-known.
846
+ */
847
+ private static _literalValue(text: string): number | null {
848
+ if (text === "true") return 1;
849
+ if (text === "false") return 0;
850
+ if (text.includes(".")) return null;
851
+ // A leading-zero integer (`0777`) is emitted verbatim and read by C as an
852
+ // OCTAL constant, so a decimal parse would diverge from the generated code's
853
+ // value. Skip it (defer to #1076) rather than risk a wrong verdict. `0x`/`0b`
854
+ // and a bare `0` are unambiguous and still handled.
855
+ if (/^0\d/.test(text)) return null;
856
+ if (/^(0[xX][\da-fA-F]+|0[bB][01]+|\d+)([uUiI]\d+)?$/.test(text)) {
857
+ return LiteralEvaluator.parseLiteral(text);
799
858
  }
859
+ return null;
860
+ }
800
861
 
801
- return false;
862
+ private static _comparisonIsAlwaysTrue(comparison: {
863
+ operator: string;
864
+ left: number;
865
+ right: number;
866
+ }): boolean {
867
+ switch (comparison.operator) {
868
+ case "=":
869
+ return comparison.left === comparison.right;
870
+ case "!=":
871
+ return comparison.left !== comparison.right;
872
+ case "<":
873
+ return comparison.left < comparison.right;
874
+ case ">":
875
+ return comparison.left > comparison.right;
876
+ case "<=":
877
+ return comparison.left <= comparison.right;
878
+ case ">=":
879
+ return comparison.left >= comparison.right;
880
+ default:
881
+ return false;
882
+ }
883
+ }
884
+
885
+ /**
886
+ * Builds a context-aware "help" suggestion for a rejected condition. For a
887
+ * boolean operand the correct explicit form is `flag = true` (or `flag = false`
888
+ * for a negated `!flag`), not a numeric `> 0` comparison. Non-boolean operands
889
+ * keep the generic `> 0 or != 0` guidance. A member access (`this.flag`) that
890
+ * does not resolve to a known type falls back to the generic form.
891
+ */
892
+ private static _conditionHelp(text: string): string {
893
+ const isNegated = text.startsWith("!");
894
+ const base = isNegated ? text.slice(1) : text;
895
+ const typeInfo = CodeGenState.getVariableTypeInfo(base);
896
+ if (typeInfo?.baseType === "bool") {
897
+ return `use explicit comparison: ${base} = ${isNegated ? "false" : "true"}`;
898
+ }
899
+ return `use explicit comparison: ${text} > 0 or ${text} != 0`;
802
900
  }
803
901
 
804
902
  // ========================================================================
@@ -8454,6 +8454,17 @@ describe("CodeGenerator", () => {
8454
8454
  const generator = new CodeGenerator();
8455
8455
  const tSymbols = CNextResolver.resolve(tree, "test.cnx");
8456
8456
  const symbols = TSymbolInfoAdapter.convert(tSymbols);
8457
+ // Issue #1100: SymbolTable must be wired up (as the real Transpiler
8458
+ // pipeline always does, see setupGenerator() above) so
8459
+ // getMemberTypeInfo() can resolve struct field array-ness. Without
8460
+ // this, getStructFieldInfo() always returns null and the struct
8461
+ // field's array-ness is never determined via the correct path — this
8462
+ // test was previously passing only because a since-removed blanket
8463
+ // "parameter -> array access" rule (Issue #579) coincidentally
8464
+ // produced the right output for the wrong reason.
8465
+ const symbolTable = new SymbolTable();
8466
+ symbolTable.addTSymbols(tSymbols);
8467
+ CodeGenState.symbolTable = symbolTable;
8457
8468
 
8458
8469
  const code = generator.generate(tree, tokenStream, {
8459
8470
  symbolInfo: symbols,
@@ -14922,7 +14933,8 @@ describe("CodeGenerator", () => {
14922
14933
  it("should reject break - not part of C-Next spec", () => {
14923
14934
  const source = `
14924
14935
  void test() {
14925
- while (true) {
14936
+ u32 i <- 0;
14937
+ while (i < 10) {
14926
14938
  break;
14927
14939
  }
14928
14940
  }
@@ -3,10 +3,20 @@
3
3
  * Tests type classification, conversion validation, and literal validation
4
4
  */
5
5
  import { describe, it, expect, beforeEach } from "vitest";
6
+ import { CharStream, CommonTokenStream } from "antlr4ng";
6
7
  import TypeResolver from "../TypeResolver";
7
8
  import SymbolTable from "../../../logic/symbols/SymbolTable";
8
9
  import CodeGenState from "../../../state/CodeGenState";
9
10
  import TTypeInfo from "../types/TTypeInfo";
11
+ import { CNextLexer } from "../../../logic/parser/grammar/CNextLexer";
12
+ import { CNextParser } from "../../../logic/parser/grammar/CNextParser";
13
+
14
+ /** Parse a standalone C-Next expression into an ExpressionContext. */
15
+ function parseExpression(source: string) {
16
+ const lexer = new CNextLexer(CharStream.fromString(source));
17
+ const parser = new CNextParser(new CommonTokenStream(lexer));
18
+ return parser.expression();
19
+ }
10
20
 
11
21
  describe("TypeResolver", () => {
12
22
  let symbolTable: SymbolTable;
@@ -598,6 +608,89 @@ describe("TypeResolver", () => {
598
608
  });
599
609
  });
600
610
 
611
+ describe("getIntegerExpressionType", () => {
612
+ function setInt(name: string, baseType: string, bitWidth: number): void {
613
+ setTypeInfo(name, { baseType, bitWidth, isArray: false, isConst: false });
614
+ }
615
+
616
+ it("resolves a signed composite to its operand category and width", () => {
617
+ setInt("a", "i32", 32);
618
+ setInt("b", "i32", 32);
619
+ expect(
620
+ TypeResolver.getIntegerExpressionType(parseExpression("a + b")),
621
+ ).toBe("i32");
622
+ });
623
+
624
+ it("resolves an unsigned composite to its operand category and width", () => {
625
+ setInt("a", "u32", 32);
626
+ setInt("b", "u32", 32);
627
+ expect(
628
+ TypeResolver.getIntegerExpressionType(parseExpression("a | b")),
629
+ ).toBe("u32");
630
+ });
631
+
632
+ it("uses the widest operand width across a same-category composite", () => {
633
+ setInt("small", "u8", 8);
634
+ setInt("big", "u32", 32);
635
+ expect(
636
+ TypeResolver.getIntegerExpressionType(parseExpression("small + big")),
637
+ ).toBe("u32");
638
+ });
639
+
640
+ it("still resolves a simple variable via getExpressionType", () => {
641
+ setInt("x", "i16", 16);
642
+ expect(TypeResolver.getIntegerExpressionType(parseExpression("x"))).toBe(
643
+ "i16",
644
+ );
645
+ });
646
+
647
+ it("returns null when no integer variable leaf can be resolved", () => {
648
+ expect(
649
+ TypeResolver.getIntegerExpressionType(parseExpression("a + b")),
650
+ ).toBeNull();
651
+ });
652
+
653
+ it("ignores integer literals (contextually typed, not fixed-category)", () => {
654
+ setInt("a", "u32", 32);
655
+ expect(
656
+ TypeResolver.getIntegerExpressionType(parseExpression("a + 5")),
657
+ ).toBe("u32");
658
+ });
659
+
660
+ it("narrows a bit-extraction operand to the extracted width, not the variable's full width", () => {
661
+ setInt("a", "u32", 32);
662
+ setInt("b", "u64", 64);
663
+ // b[0, 32] is u32, so a + b[0, 32] is u32 — NOT u64. Typing it u64 would
664
+ // make slice codegen cast the composite to a wider type (MISRA 10.8).
665
+ expect(
666
+ TypeResolver.getIntegerExpressionType(parseExpression("a + b[0, 32]")),
667
+ ).toBe("u32");
668
+ });
669
+
670
+ it("types an array-element operand by its element type, ignoring the index variable's width", () => {
671
+ setTypeInfo("arr", {
672
+ baseType: "u8",
673
+ bitWidth: 8,
674
+ isArray: true,
675
+ isConst: false,
676
+ });
677
+ setInt("idx", "u64", 64);
678
+ setInt("a", "u8", 8);
679
+ // arr[idx] is u8 (the element); a wide index must not inflate the width.
680
+ expect(
681
+ TypeResolver.getIntegerExpressionType(parseExpression("a + arr[idx]")),
682
+ ).toBe("u8");
683
+ });
684
+
685
+ it("types a bit-extraction operand by the extracted width when it is the widest operand", () => {
686
+ setInt("b", "u64", 64);
687
+ setInt("c", "u8", 8);
688
+ expect(
689
+ TypeResolver.getIntegerExpressionType(parseExpression("b[0, 32] + c")),
690
+ ).toBe("u32");
691
+ });
692
+ });
693
+
601
694
  // ========================================================================
602
695
  // Postfix Expression Type Detection
603
696
  // ========================================================================
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Unit tests for TypeValidator.validateLoopConditionNotAlwaysTrue
3
+ * ADR-113 / Issue #1075: reject always-true literal loop conditions (E0707).
4
+ *
5
+ * Only the v0.2.18 literal slice — integer/bool literal comparisons decided
6
+ * without symbol resolution. Named constants, non-literal operands, and
7
+ * always-false conditions are out of scope (#1076).
8
+ */
9
+ import { describe, it, expect } from "vitest";
10
+ import { CharStream, CommonTokenStream } from "antlr4ng";
11
+ import { CNextLexer } from "../../../logic/parser/grammar/CNextLexer";
12
+ import { CNextParser } from "../../../logic/parser/grammar/CNextParser";
13
+ import TypeValidator from "../TypeValidator";
14
+
15
+ function parseCondition(text: string) {
16
+ const charStream = CharStream.fromString(text);
17
+ const lexer = new CNextLexer(charStream);
18
+ const tokenStream = new CommonTokenStream(lexer);
19
+ const parser = new CNextParser(tokenStream);
20
+ return parser.expression();
21
+ }
22
+
23
+ function isFlaggedAlwaysTrue(text: string): boolean {
24
+ try {
25
+ TypeValidator.validateLoopConditionNotAlwaysTrue(parseCondition(text));
26
+ return false;
27
+ } catch (error) {
28
+ return /E0707/.test((error as Error).message);
29
+ }
30
+ }
31
+
32
+ describe("TypeValidator.validateLoopConditionNotAlwaysTrue (E0707)", () => {
33
+ describe("flags always-true literal comparisons", () => {
34
+ it.each([
35
+ "1 = 1",
36
+ "true = true",
37
+ "false = false",
38
+ "1 != 2",
39
+ "5 > 3",
40
+ "3 < 5",
41
+ "3 >= 3",
42
+ "5 <= 5",
43
+ "0x10 = 16",
44
+ "0XFF = 255", // uppercase hex prefix
45
+ "0b10 = 2",
46
+ "5u8 = 5", // type-suffixed literal
47
+ ])("flags %s", (condition) => {
48
+ expect(isFlaggedAlwaysTrue(condition)).toBe(true);
49
+ });
50
+
51
+ it("includes the condition text and steers to forever", () => {
52
+ let message = "";
53
+ try {
54
+ TypeValidator.validateLoopConditionNotAlwaysTrue(
55
+ parseCondition("1 = 1"),
56
+ );
57
+ } catch (error) {
58
+ message = (error as Error).message;
59
+ }
60
+ expect(message).toContain(
61
+ "Error E0707: loop condition '1=1' is always true",
62
+ );
63
+ expect(message).toContain("forever { ... }");
64
+ });
65
+ });
66
+
67
+ describe("does NOT flag always-false comparisons (out of scope, #1076)", () => {
68
+ it.each(["1 = 2", "true = false", "1 != 1", "5 < 3", "3 > 5", "5 <= 3"])(
69
+ "allows %s",
70
+ (condition) => {
71
+ expect(isFlaggedAlwaysTrue(condition)).toBe(false);
72
+ },
73
+ );
74
+ });
75
+
76
+ describe("does NOT flag non-literal or compound conditions (needs folding, #1076)", () => {
77
+ it.each([
78
+ "x = 1", // identifier operand
79
+ "MAX > 0", // named constant
80
+ "a = b", // two identifiers
81
+ "5.0 > 3.0", // float operands
82
+ "1 + 0 = 1", // compound left operand
83
+ "1 = 1 || 0 > 5", // logical-or combination
84
+ "1 = 1 && 2 = 2", // logical-and combination
85
+ "0777 = 777", // leading-zero literal: C reads it as octal (511), so a
86
+ // decimal parse (777) would diverge — skipped, not wrongly flagged
87
+ ])("allows %s", (condition) => {
88
+ expect(isFlaggedAlwaysTrue(condition)).toBe(false);
89
+ });
90
+ });
91
+ });