All files / TRANSPILE/1-Analyze ConstantDimensionAnalyzer.ts

100% Statements 25/25
100% Branches 8/8
100% Functions 7/7
100% Lines 22/22

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89                                                      485x     485x 485x       485x     50x     50x     2x 2x       52x 50x       50x 50x 1x           1x   49x 8x 8x 7x                     485x     485x 485x 485x          
/**
 * An array dimension that is not a constant expression: E0909, E0910
 * (#1175; ADR-023 "no VLAs", ADR-044 "Values fixed at compile time").
 *
 * Every dimension, wherever the grammar has one -- a declaration at any
 * scope, a struct field, a parameter, a `for` header -- because the check
 * listens on the dimension nodes themselves rather than on the declarations
 * that hold them. A rule enforced at some positions and silent at others is
 * how this card's defects survived: a parameter-sized local reached C as an
 * initialized variable-length array, at exit 0.
 *
 * A dimension C can evaluate and C-Next cannot (a header macro) is C's, and
 * passes. One that has no value is rejected here, so render never sees it.
 */
import { ParseTreeWalker } from "antlr4ng";
 
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import ConstExprLowering from "../../utils/ConstExprLowering";
import ConstantEvaluator from "../../utils/ConstantEvaluator";
import ConstantFold from "../../utils/ConstantFold";
import ParserUtils from "../../utils/ParserUtils";
import ConstantDiagnostics from "./helpers/ConstantDiagnostics";
import type IAnalysisContext from "./types/IAnalysisContext";
import type IConstantDimensionError from "./types/IConstantDimensionError";
 
class ConstantDimensionListener extends CNextListener {
  private readonly found: IConstantDimensionError[] = [];
 
  // eslint-disable-next-line @typescript-eslint/lines-between-class-members
  constructor(private readonly context: IAnalysisContext) {
    super();
  }
 
  public errors(): IConstantDimensionError[] {
    return this.found;
  }
 
  override enterArrayTypeDimension = (
    ctx: Parser.ArrayTypeDimensionContext,
  ): void => {
    this.check(ctx.expression());
  };
 
  override enterArrayDimension = (ctx: Parser.ArrayDimensionContext): void => {
    this.check(ctx.expression());
  };
 
  private check(expression: Parser.ExpressionContext | null): void {
    if (!expression) return; // an unsized `[]` is E0892's
    const result = ConstantEvaluator.evaluate(
      ConstExprLowering.lower(expression),
      ConstantFold.environment(this.context.program, this.context.sourceFile),
    );
    const at = ParserUtils.getPosition(expression);
    if (result.kind === "overflow") {
      this.found.push({
        code: "E0910",
        ...at,
        message: `Array dimension overflows ${result.typeName} at compile time: the arithmetic would clamp or wrap (ADR-044)`,
        helpText: ConstantDiagnostics.OVERFLOW_HELP,
      });
      return;
    }
    if (result.kind !== "notConstant") return;
    const why = ConstantDiagnostics.why(result);
    if (why === null) return;
    this.found.push({
      code: "E0909",
      ...at,
      message: `Array dimension must be known at compile time: ${why}`,
      helpText:
        "An array's size is built from literals, consts, sizeof and casts (ADR-023). C-Next has no variable-length arrays",
    });
  }
}
 
class ConstantDimensionAnalyzer {
  constructor(private readonly context: IAnalysisContext) {}
 
  public analyze(tree: Parser.ProgramContext): IConstantDimensionError[] {
    const listener = new ConstantDimensionListener(this.context);
    ParseTreeWalker.DEFAULT.walk(listener, tree);
    return listener.errors();
  }
}
 
export default ConstantDimensionAnalyzer;