All files / TRANSPILE/1-Analyze StructLiteralAnalyzer.ts

100% Statements 21/21
100% Branches 8/8
100% Functions 6/6
100% Lines 20/20

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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138                                                                                                                    484x   484x 484x       484x     110x           110x 1x           1x               109x 4x           4x             105x 105x 14x                               19x 19x         484x     484x 484x 484x          
/**
 * ADR-014 struct initializers: E0357, E0358, and E0866 where a whole array is
 * taken.
 *
 * #1322. Two throws in `CodeGenerator._resolveStructInitializerTypeName`, both
 * reported as `1:0`, and one of them fired on valid code (#1277).
 *
 * ## One question, asked once
 *
 * A struct literal has no type of its own; the position it stands in supplies
 * one (`Point p <- { x: 1 }`). Where nothing does, nothing can say what struct
 * this is -- E0357. A bare `{ x: 1, y: 2 };` as an expression statement is the
 * only shape in the language that reaches it.
 *
 * ## E0356 is retired, with the syntax it rejected
 *
 * A second rule stood here: a WRITTEN type (`Point { x: 1 }`) where the
 * position already declared one was redundant. It had no reachable complement
 * -- every position that consumes a value declares a type, so the written form
 * was an error in all of them, and the one place it parsed was a bare
 * expression statement, where it built a compound literal and discarded it.
 * The grammar alternative is removed on the language owner's decision, which
 * makes `Point { x: 1 }` a parse error and leaves this rule with one question
 * rather than two.
 *
 * E0357's help therefore does not offer "write the type" as a remedy: there is
 * no type to write. The only remedy is to move the initializer somewhere a
 * type is declared.
 *
 * ## Why #1277 was a codegen bug and not this rule's business
 *
 * The "cannot infer" throw fired on three shapes that are perfectly valid:
 * `return { x: 1, y: 2 };` at file scope, the same inside a scope method, and
 * a `for` header's declaration. In each, a type WAS available and codegen
 * simply did not thread it -- the return path set an expected type only when
 * the return type was an enum, the `for` header set none at all, and the scope
 * method never recorded its return type. That is fixed at the three emission
 * sites, so the rule here sees a position that supplies a type and stays
 * silent. Relocating the throw without fixing them would have preserved a
 * rejection of valid code behind a fresh error code.
 *
 * The scope-method half was found by an invariant this card installed: a bare
 * enum member returned from a scope method reached `Internal:` rather than a
 * diagnostic, because 2.1 said the position established an enum type and
 * codegen disagreed. Two generators had open-coded the same four-step function
 * context and one was missing a line.
 */
 
import { ParserRuleContext, ParseTreeWalker } from "antlr4ng";
 
import { CNextListener } from "../../PARSE/2-Parse/grammar/CNextListener";
import * as Parser from "../../PARSE/2-Parse/grammar/CNextParser";
import ParserUtils from "../../utils/ParserUtils";
import StructInitializerType from "./helpers/StructInitializerType";
import IStructLiteralError from "./types/IStructLiteralError";
import type IAnalysisContext from "./types/IAnalysisContext";
 
class StructLiteralListener extends CNextListener {
  private readonly found: IStructLiteralError[] = [];
 
  public constructor(private readonly context: IAnalysisContext) {
    super();
  }
 
  public errors(): IStructLiteralError[] {
    return this.found;
  }
 
  override enterStructInitializer = (
    ctx: Parser.StructInitializerContext,
  ): void => {
    // The STRUCTURAL question -- "does some enclosing position supply a type?"
    // -- and deliberately not "which type", which a C-header struct's field
    // cannot answer in this pass. See `hasEstablishingPosition`.
    if (!StructInitializerType.hasEstablishingPosition(ctx)) {
      this.report(
        ctx,
        "E0357",
        "Cannot infer struct type: nothing here says which struct this is",
        "Put the initializer where a type is declared -- a variable, an assignment target, a field, an argument, or a return (ADR-014).",
      );
      return;
    }
 
    // ADR-014: an array's whole initializer is ADR-035's list (E0866). A
    // declaration's is checked by that rule; a field, an assignment target
    // or a parameter that is an array is checked here (#1760 second review:
    // each typed the element, so the struct form passed for an array of
    // structs and was E0358's for an array of scalars).
    if (StructInitializerType.takesWholeArray(ctx, this.context)) {
      this.report(
        ctx,
        "E0866",
        `An array must be given a list, not '${ctx.getText()}'`,
        "Write the elements out in brackets, or set them one at a time (ADR-035).",
      );
      return;
    }
 
    // #1802: only a struct takes `{ field: value }`. A bitmap is its backing
    // integer wherever it is written, inside a struct or not (owner ruling
    // 2026-09-28), and a scalar's field list became a designated initializer
    // C rejects.
    const target = StructInitializerType.nonStructTarget(ctx, this.context);
    if (target === null) return;
    this.report(
      ctx,
      "E0358",
      `A struct initializer cannot be a value of '${target.typeName}', which is not a struct`,
      target.isBitmap
        ? `A bitmap's value is its backing integer, set field by field after: '${target.typeName} v <- 0; v.<field> <- true;' (ADR-034).`
        : `Give a value of type '${target.typeName}'; only a struct takes '{ field: value }' (ADR-014).`,
    );
  };
 
  private report(
    at: ParserRuleContext,
    code: string,
    message: string,
    helpText: string,
  ): void {
    const { line, column } = ParserUtils.getPosition(at);
    this.found.push({ code, line, column, message, helpText });
  }
}
 
class StructLiteralAnalyzer {
  public constructor(private readonly context: IAnalysisContext) {}
 
  public analyze(tree: Parser.ProgramContext): IStructLiteralError[] {
    const listener = new StructLiteralListener(this.context);
    ParseTreeWalker.DEFAULT.walk(listener, tree);
    return listener.errors();
  }
}
 
export default StructLiteralAnalyzer;