All files / TRANSPILE/3-Render/codegen/helpers ArrayInitHelper.ts

100% Statements 25/25
100% Branches 13/13
100% Functions 7/7
100% Lines 25/25

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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208                                                                                                                                                      39x   39x     39x 2x     37x       39x           39x                   39x 39x 39x                                         10x         10x 10x                           10x         10x                             27x         27x               27x                     33x 26x     7x   7x 1x     6x 6x          
/**
 * ArrayInitHelper - Handles array initialization with size inference and fill-all syntax
 *
 * Issue #644: Extracted from CodeGenerator to reduce file size.
 *
 * Handles:
 * - Array initializers with size inference: u8[] data <- [1, 2, 3]
 * - Fill-all syntax: u8 data[10] <- [0*]
 * - Array size validation
 *
 * Migrated to use CodeGenState instead of constructor DI.
 */
 
import invariant from "../../../../utils/invariant";
import type TranspileState from "../../../TranspileState";
 
/**
 * Result from processing array initialization.
 */
interface IArrayInitResult {
  /** Whether this was an array initializer (vs regular expression) */
  isArrayInit: boolean;
  /** The dimension suffix to add to declaration (e.g., "[3]") */
  dimensionSuffix: string;
  /** The final initializer value */
  initValue: string;
}
 
/**
 * Callbacks required for array initialization.
 * These need CodeGenerator context and cannot be replaced with static state.
 *
 * #1445 box 3: these are THUNKS, and the nodes they used to take are closed
 * over by the caller. This module never read anything off those three
 * contexts -- each was received and handed straight back to the callback the
 * caller supplied -- so naming `ExpressionContext`, `TypeContext` and
 * `ArrayDimensionContext` here bought a dependency on the grammar purely to
 * pass values through.
 *
 * Thunks rather than pre-generated strings, deliberately. `getTypeName` must
 * run BEFORE `generateExpression`, and `generateExpression` must run INSIDE
 * the `callbacks.state.withExpectedType` window that this helper opens -- that
 * window is the whole point of `_generateArrayInitValue`. Passing strings
 * would evaluate them at the call site, outside it.
 */
interface IArrayInitCallbacks {
  /** Generate the initializer expression's code */
  generateExpression: () => string;
  /** Get the declared type's C name */
  getTypeName: () => string;
  /** Generate the declaration's array dimension suffix */
  generateArrayDimensions: () => string;
}
 
/**
 * Handles array initialization with size inference and fill-all syntax.
 */
class ArrayInitHelper {
  /**
   * Process array initialization expression.
   * Returns null if not an array initializer pattern.
   *
   * @param name - Variable name
   * @param hasEmptyArrayDim - Whether any dimension is empty (for inference)
   * @param declaredSize - First dimension size if explicit, null otherwise
   * @param callbacks - Callbacks to CodeGenerator methods
   */
  static processArrayInit(
    name: string,
    hasEmptyArrayDim: boolean,
    declaredSize: number | null,
    callbacks: IArrayInitCallbacks,
    state: TranspileState,
  ): IArrayInitResult | null {
    // Reset and generate initializer
    state.resetArrayInitTracking();
 
    const initValue = ArrayInitHelper._generateArrayInitValue(callbacks, state);
 
    // Check if it was an array initializer
    if (!state.wasArrayInit()) {
      return null;
    }
 
    const dimensionSuffix = hasEmptyArrayDim
      ? ArrayInitHelper._processSizeInference(name, declaredSize, state)
      : ArrayInitHelper._processExplicitSize(declaredSize, callbacks, state);
 
    const finalInitValue = ArrayInitHelper._expandFillAllSyntax(
      initValue,
      declaredSize,
      state,
    );
 
    return { isArrayInit: true, dimensionSuffix, initValue: finalInitValue };
  }
 
  /**
   * Generate the array initializer value with proper expected type
   */
  private static _generateArrayInitValue(
    callbacks: IArrayInitCallbacks,
    state: TranspileState,
  ): string {
    const typeName = callbacks.getTypeName();
    return state.withExpectedType(typeName, () =>
      callbacks.generateExpression(),
    );
  }
 
  /**
   * An inferred size (`u8[] data <- [1, 2, 3]`) is the declaration's: 1.3
   * counted the list and 1.4 settled it, and the `.h` states that number.
   *
   * #1664 box 3: this used to count the elements it had just rendered -- a
   * second derivation of one fact, which agreed with the header only while
   * nothing else shaped the suffix. For `u8[][3] n` it emitted `n[2]` against
   * the header's `n[2][3]` (#1822, now E0892 in pass 2.1). The rendered count
   * survives as the check that the two still agree.
   */
  private static _processSizeInference(
    name: string,
    declaredSize: number | null,
    state: TranspileState,
  ): string {
    // #1322: E0876 rejects the fill-all form on an inferred size in pass 2.1
    // (ADR-035); an inferred size is counted from a list.
    invariant(
      state.lastArrayFillValue === undefined,
      `an inferred array size comes from a list -- E0876 rejects the fill-all ` +
        `form [${state.lastArrayFillValue}*] on '${name}' in pass 2.1, before this runs`,
    );
    ArrayInitHelper.assertInferredSize(name, declaredSize, state);
    return `[${declaredSize}]`;
  }
 
  /**
   * The rendered list has exactly the elements 1.3 counted for an omitted
   * size. Every declaration renderer that emits an inferred size asks this,
   * right after rendering the list (#1824 review: the scope-member renderer
   * had no check at all).
   */
  static assertInferredSize(
    name: string,
    declaredSize: number | null,
    state: TranspileState,
  ): asserts declaredSize is number {
    invariant(
      declaredSize !== null,
      `an inferred size is its declaration's count, and '${name}' has none ` +
        `here -- a declaration fact that never reached render`,
    );
    invariant(
      state.lastArrayInitCount === declaredSize,
      `an inferred size is its declaration's -- 1.3 counted [${declaredSize}] ` +
        `for '${name}' but ${state.lastArrayInitCount} element(s) rendered`,
    );
  }
 
  /**
   * Process explicit array size with validation
   */
  private static _processExplicitSize(
    declaredSize: number | null,
    callbacks: IArrayInitCallbacks,
    state: TranspileState,
  ): string {
    const dimensionSuffix = callbacks.generateArrayDimensions();
 
    // #1322: the element count is E0866's in pass 2.1 (ADR-035); an
    // initializer shorter than the declaration would be emitted as C's
    // partial initialization, which MISRA 9.3 forbids, so it is asserted.
    invariant(
      declaredSize === null ||
        state.lastArrayFillValue !== undefined ||
        state.lastArrayInitCount === declaredSize,
      `an array initializer has the declared number of elements -- E0866 rejects ` +
        `${state.lastArrayInitCount} for [${declaredSize}] in pass 2.1, before this runs`,
    );
 
    return dimensionSuffix;
  }
 
  /**
   * Expand fill-all syntax (e.g., [0*] with size 5 -> {0, 0, 0, 0, 0})
   */
  private static _expandFillAllSyntax(
    initValue: string,
    declaredSize: number | null,
    state: TranspileState,
  ): string {
    if (state.lastArrayFillValue === undefined || declaredSize === null) {
      return initValue;
    }
 
    const fillVal = state.lastArrayFillValue;
    // C handles {0} correctly, no need to expand
    if (fillVal === "0") {
      return initValue;
    }
 
    const elements = new Array<string>(declaredSize).fill(fillVal);
    return `{${elements.join(", ")}}`;
  }
}
 
export default ArrayInitHelper;