All files / utils DeclaredTypeFacts.ts

100% Statements 11/11
94.44% Branches 17/18
100% Functions 5/5
100% Lines 9/9

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                                                                                                        2968x 2968x   2968x                                             3310x         3081x         117x                                                                       1396x 909x 905x          
/**
 * DeclaredTypeFacts -- what a declared type NAME tells you about a variable.
 *
 * ## Why this exists (#1651)
 *
 * Four sites independently answered "is this name an enum or a bitmap, and how
 * wide is it": the declaration path, the bitmap-array path, the parameter path
 * and the cross-file symbol converter. All four read the same two sets, so the
 * DETECTION was already shared -- and CLAUDE.md is explicit that this is not
 * enough, because each then derived the CONSEQUENCES for itself.
 *
 * They diverged twice before anyone looked, in the same direction both times:
 * the cross-file converter is the one that forgets a field.
 *
 * - #1303 -- it dropped `overflowBehavior`, so an imported `u8` wrapped where
 *   the declared ADR-044 behavior said saturate.
 * - #1651 -- it dropped `isBitmap`/`bitmapTypeName`, so `shared.Active <- 1`
 *   one include hop from the declaration classified as a struct member write
 *   and emitted `shared.Active = 1`: a member access on a scalar, which gcc
 *   rejects outright. The same statement same-file lowered to mask-and-shift.
 *
 * It had also diverged a third time without being noticed, on the width: the
 * converter computed `TYPE_WIDTH[name]`, which has no entry for a bitmap type,
 * so every cross-file bitmap registered `bitWidth: 0` against the real 8/16/
 * 32/64 the other three record. Reads survive that because
 * `getNumericBitWidth` re-derives the width from the name when the registered
 * one is 0 -- a compensation in a fourth module for a fact the registry was
 * supposed to carry. Byte-count consumers have no such fallback.
 *
 * So the quintuple is derived ONCE, here, and the callers keep only what
 * genuinely differs between them: the width of a name that is neither an enum
 * nor a bitmap, which is a string capacity in one caller and a primitive width
 * in the others.
 */
import type IDeclaredTypeFacts from "./types/IDeclaredTypeFacts";
import type IDeclaredTypeSets from "./types/IDeclaredTypeSets";
import type IStructFieldLookup from "./types/IStructFieldLookup";
 
class DeclaredTypeFacts {
  /**
   * Classify a base type name against the declared enum and bitmap sets.
   *
   * `fallbackBitWidth` is used only when the name is neither -- callers answer
   * that differently and correctly: a string variable is 8 (its element), a
   * parameter is its primitive width, an enum registers 0 and is widened to
   * ADR-017's 32 bits at the point of use rather than here.
   */
  static of(
    baseType: string,
    sets: IDeclaredTypeSets | null,
    fallbackBitWidth: number,
  ): IDeclaredTypeFacts {
    const isEnum = DeclaredTypeFacts.isEnum(sets, baseType);
    const isBitmap = DeclaredTypeFacts.isBitmap(sets, baseType);
 
    return {
      isEnum,
      enumTypeName: isEnum ? baseType : undefined,
      isBitmap,
      bitmapTypeName: isBitmap ? baseType : undefined,
      bitWidth: isBitmap
        ? (sets?.bitmapBitWidth.get(baseType) ?? 0)
        : fallbackBitWidth,
    };
  }
 
  /**
   * Is this name a declared enum / bitmap / scope?
   *
   * #1456: one-line set lookups, but they had exactly one home --
   * `CodeGenState.isKnownEnum` and friends -- so 2.1 Analyze had to read
   * render state to ask. Both callers share these now: `CodeGenState`
   * delegates, and an analyzer passes the view its `IAnalysisContext` carries.
   *
   * The `?? false` is the existing reading of an absent symbol view: "nothing
   * is known yet", never "not an enum".
   */
  static isEnum(sets: IDeclaredTypeSets | null, name: string): boolean {
    return sets?.knownEnums.has(name) ?? false;
  }
 
  /** @see isEnum */
  static isBitmap(sets: IDeclaredTypeSets | null, name: string): boolean {
    return sets?.knownBitmaps.has(name) ?? false;
  }
 
  /** @see isEnum */
  static isScope(sets: IDeclaredTypeSets | null, name: string): boolean {
    return sets?.knownScopes.has(name) ?? false;
  }
 
  /**
   * Is this type name a struct? Bitmaps count -- they are struct-like and take
   * the same pass-by-reference `->` treatment (#551).
   *
   * ## Why this is here and not at three call sites (#1656)
   *
   * It was at three, spelled the same way each time and reached by 19 callers:
   * `CodeGenState.isKnownStruct`, `SymbolLookupHelper.isKnownStruct` (through
   * `IOrchestrator`), and `ExpressionTypeResolver.isStructType` in 2-Plan,
   * which alone had ten sites through `IOrchestrator.isStructType`. All three
   * ran the identical three checks in the identical order; the 2-Plan copy
   * differed from the `state/` copy only in `CodeGenState.` versus `this.`.
   *
   * They had not diverged in RESULT, which is why nothing caught them -- they
   * had diverged in FAILURE MODE. See `IStructFieldLookup` for that, and for
   * why the lookup is required rather than optional here.
   *
   * The per-file sets answer first because they are the file's own view; the
   * run-wide table answers for a struct declared in an included header, which
   * the per-file sets do not carry. Both are needed, which is why this takes
   * two arguments rather than pretending one source suffices (#1312).
   *
   * `!== undefined` is deliberate. `getStructFields` returns a `Map`, and an
   * empty `Map` is truthy, so all three call sites read a zero-field struct as
   * known by accident rather than by decision. No such struct is reachable
   * today -- `SymbolTable.addStructField` always sets a field on creation --
   * so this states the existing behavior rather than changing it.
   */
  static isStruct(
    sets: IDeclaredTypeSets | null,
    fields: IStructFieldLookup,
    typeName: string,
  ): boolean {
    if (sets?.knownStructs.has(typeName)) return true;
    if (sets?.knownBitmaps.has(typeName)) return true;
    return fields.getStructFields(typeName) !== undefined;
  }
}
 
export default DeclaredTypeFacts;