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

100% Statements 39/39
100% Branches 27/27
100% Functions 8/8
100% Lines 39/39

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 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297                                                                                                                                                                        1120x 1118x     2x       2x 2x   2x 2x                               1152x 1055x       97x               31x 31x 31x                           31x     30x     30x                     67x   67x                       1127x   198x     929x           929x 929x         929x 929x         4x       3x                 4x                 926x                             1196x           2x       47x                 1147x                             1147x     1147x           1147x 28x     1119x               1119x                  
/**
 * VariableDeclHelper - Renders variable declarations
 *
 * Issue #792: Extracted from CodeGenerator to reduce file size.
 *
 * Handles:
 * - Variable declarations with initializers
 * - Array declarations with dimension placement
 * - C++ constructor declarations
 *
 * ## It renders a plan; it does not read a tree (#1445 box 3)
 *
 * This module used to take a `VariableDeclarationContext` and ask it the same
 * questions repeatedly -- is there an initializer, is there an arrayType, what
 * are the trailing dimensions -- across four helpers behind FOUR callback
 * interfaces, each naming its own subset of grammar types. The questions are
 * answered once now, by `CodeGenerator.planVariableDecl`, and what crosses is
 * `TPlannedVariableDecl`.
 *
 * ## The planner writes state, and that is not an accident
 *
 * Building the plan records the local, marks it as a pointer when its type
 * was inferred as one, and resolves its emitted name. What the name is typed
 * as is not written here: it binds through the lexical frames 1.4 settled
 * (#1668, C8, which deleted the render-time registry this once filled). The
 * plan is built immediately before it is rendered, in the same call. What is left here is assembly: prefixes, dimension placement, the
 * MISRA Rule 10.3 cast, and the C++ assignment queue.
 */
 
import invariant from "../../../../utils/invariant";
import ArrayInitHelper from "./ArrayInitHelper";
import NarrowingCastHelper from "./NarrowingCastHelper";
import StringDeclHelper from "./StringDeclHelper";
import IPlannedArrayDeclaration from "../types/IPlannedArrayDeclaration";
import TPlannedVariableDecl from "../types/TPlannedVariableDecl";
import TPlannedVariableInitializer from "../types/TPlannedVariableInitializer";
import type TranspileState from "../../../TranspileState";
 
/**
 * Result from rendering the array half of a declaration.
 */
interface IArrayDeclResult {
  /** Whether array init was fully handled (early return) */
  handled: boolean;
  /** Generated code if handled */
  code: string;
  /** Updated declaration string */
  decl: string;
  /** Whether this is an array type */
  isArray: boolean;
}
 
/**
 * Renders variable declarations in C.
 */
class VariableDeclHelper {
  // ========================================================================
  // Tier 1: Pure Operations (CodeGenState only)
  // ========================================================================
 
  /**
   * Handle pending C++ class field assignments.
   * In function body, generates assignments after declaration.
   *
   * #1322: the rejection that stood at the end of this method is E0508 in pass
   * 2.1, and it is not a transcription of what was here. This method DRAINS a
   * queue another node filled, so the declaration it was reported against was
   * not necessarily the one that filled it: a scope member pushed and was never
   * drained (the initializer vanished at exit 0), and an unrelated global after
   * one reported `C++ class 'u32' with constructor`. `CppClassInitializerAnalyzer`
   * decides at the initializer itself.
   *
   * The queue can therefore no longer be non-empty here outside a function
   * body, which is what the assertion says.
   *
   * @param name - Variable name
   * @param decl - Current declaration string
   * @returns Final declaration with semicolon and any pending assignments
   */
  static finalizeCppClassAssignments(
    name: string,
    decl: string,
    state: TranspileState,
  ): string {
    if (state.pendingCppClassAssignments.length === 0) {
      return `${decl};`;
    }
 
    invariant(
      state.inFunctionBody,
      "E0508 rejects this in pass 2.1, before this runs",
    );
    const assignments = state.pendingCppClassAssignments
      .map((a) => `${name}.${a}`)
      .join("\n");
    state.pendingCppClassAssignments = [];
    return `${decl};\n${assignments}`;
  }
 
  // ========================================================================
  // Tier 2: Rendering one half of a declaration
  // ========================================================================
 
  /**
   * Render the array half: dimensions, or a complete ADR-035 initializer.
   */
  static renderArrayDeclaration(
    plan: IPlannedArrayDeclaration,
    sourceName: string,
    decl: string,
    state: TranspileState,
  ): IArrayDeclResult {
    if (!plan.isArray) {
      return { handled: false, code: "", decl, isArray: false };
    }
 
    // ADR-035: Handle array initializers with size inference
    if (plan.init) {
      // MISRA C:2012 Rule 9.3: an array declaration initializer is a declaration
      // initializer for its ELEMENTS too, so it takes withDeclarationInit exactly
      // as the scalar path below does. Without it a struct element formatted as a
      // compound literal -- `Point pair[2] = {(Point){ .x = 1 }, ...}` -- which
      // cppcheck reads as a partially initialized array, while the scalar
      // `Point single = { .x = 1 }` on the next line was already plain. One
      // declaration-initializer decision, previously made in two places.
      const init = plan.init;
      const arrayInitResult = state.withDeclarationInit(() =>
        ArrayInitHelper.processArrayInit(
          sourceName,
          plan.hasEmptyDimension,
          plan.declaredSize,
          {
            // Lazy, not pre-generated: each must run inside the
            // `withExpectedType` window the helper opens.
            generateExpression: init.renderExpression,
            getTypeName: init.renderTypeName,
            generateArrayDimensions: init.renderDimensions,
          },
          state,
        ),
      );
      if (arrayInitResult) {
        // When size inference happens and the empty dim is in arrayType,
        // dimensionSuffix already contains the inferred size - don't duplicate
        const fullDimSuffix = plan.hasEmptyArrayTypeDimension
          ? arrayInitResult.dimensionSuffix
          : plan.arrayTypeDimensions + arrayInitResult.dimensionSuffix;
        return {
          handled: true,
          code: `${decl}${fullDimSuffix} = ${arrayInitResult.initValue};`,
          decl,
          isArray: true,
        };
      }
    }
 
    // Generate dimensions: arrayType dimension first, then arrayDimension dimensions
    const newDecl =
      decl + plan.arrayTypeDimensions + plan.renderCStyleDimensions();
 
    return { handled: false, code: "", decl: newDecl, isArray: true };
  }
 
  /**
   * Render a variable's initializer, with the MISRA Rule 10.3 cast.
   */
  static renderVariableInitializer(
    plan: TPlannedVariableInitializer,
    decl: string,
    isArray: boolean,
    state: TranspileState,
  ): string {
    if (plan.kind === "zero") {
      // ADR-015: Zero initialization for uninitialized variables
      return `${decl} = ${plan.render(isArray)}`;
    }
 
    const typeName = plan.renderTypeName();
 
    // #1322: ADR-024's initializer rules are E0868/E0869 in pass 2.1.
 
    // Issue #872: Set expectedType for MISRA 7.2 U suffix compliance
    // MISRA 10.3: Also check for cross-type-category conversions (int <-> float)
    return state.withExpectedType(typeName, () => {
      let exprCode = state.withDeclarationInit(plan.renderExpression);
 
      // MISRA 10.3: Check for cross-type-category conversions (int <-> float).
      // Asked AFTER the render, and inside the window, because the question is
      // what the expression turned out to be.
      const exprType = plan.resolveExpressionType();
      if (
        exprType &&
        NarrowingCastHelper.isCrossTypeCategoryConversion(exprType, typeName)
      ) {
        // Int to float: add explicit cast
        if (
          NarrowingCastHelper.isIntegerCategory(exprType) &&
          NarrowingCastHelper.isFloatCategory(typeName)
        ) {
          exprCode = NarrowingCastHelper.wrapIntToFloat(
            exprCode,
            typeName,
            state,
          );
        }
        // Float to int is E0891 in pass 2.1 (#1800): the author writes the
        // cast, which saturates. This emitted a raw C conversion instead,
        // undefined for NaN and past the target's range.
        invariant(
          !(
            NarrowingCastHelper.isFloatCategory(exprType) &&
            NarrowingCastHelper.isIntegerCategory(typeName)
          ),
          "an implicit float-to-integer conversion is rejected in pass 2.1 (E0891)",
        );
      }
 
      return `${decl} = ${exprCode}`;
    });
  }
 
  // ========================================================================
  // Tier 3: Orchestrator
  // ========================================================================
 
  /**
   * Render the declaration a plan describes.
   */
  static renderVariableDecl(
    plan: TPlannedVariableDecl,
    state: TranspileState,
  ): string {
    switch (plan.kind) {
      // Issue #375: C++ constructor syntax.
      //
      // ADR-057: emitted under the name registration decided on, not the
      // source one -- the planner did the registering.
      case "constructor":
        return `${plan.type} ${plan.emittedName}(${plan.args.join(", ")});`;
 
      // ADR-045: string types have their own three forms.
      case "string":
        return StringDeclHelper.generateStringDecl(
          plan.string,
          plan.emittedName,
          plan.modifiers,
          plan.isConst,
          state,
        );
 
      case "plain":
        return VariableDeclHelper.renderPlainDecl(plan, state);
    }
  }
 
  /**
   * Render everything that is neither a constructor nor a string.
   */
  private static renderPlainDecl(
    plan: Extract<TPlannedVariableDecl, { kind: "plain" }>,
    state: TranspileState,
  ): string {
    // ADR-057: the DECLARED identifier is the emitted one -- a local shadowing a
    // file-scope name carries a distinct C name so `global.x` still reaches
    // past it. References in the source say the bare name, and bind to this
    // declaration through the lexical frames; only the text moves.
    const base = `${plan.modifierPrefix}${plan.type} ${plan.emittedName}`;
 
    // Array declarations can complete the whole declaration themselves.
    const arrayResult = VariableDeclHelper.renderArrayDeclaration(
      plan.array,
      plan.sourceName,
      base,
      state,
    );
    if (arrayResult.handled) {
      return arrayResult.code;
    }
 
    const decl = VariableDeclHelper.renderVariableInitializer(
      plan.initializer,
      arrayResult.decl,
      arrayResult.isArray,
      state,
    );
 
    // Handle pending C++ class field assignments
    return VariableDeclHelper.finalizeCppClassAssignments(
      plan.sourceName,
      decl,
      state,
    );
  }
}
 
export default VariableDeclHelper;