All files / TRANSPILE/2-Plan DeclaredTypeInfo.ts

100% Statements 33/33
97.77% Branches 44/45
100% Functions 6/6
100% Lines 31/31

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                                                                                                            5943x   1795x 1795x 455x   1340x     1297x 1297x                       73x           2778x                               4273x           4273x 4273x 4136x   137x 4273x                                 4545x                 2637x 2637x   124x                       2513x 2513x 2513x                                                             455x 455x 455x     455x   455x 455x 455x 455x                                     2968x                
/**
 * #1668 (C7): a binding's declared type in the shape 2.2 and render read,
 * `TTypeInfo` -- the one projection from what the program declares to the
 * type facts the per-file registry used to hold.
 *
 * The registry was written by declaration walks and read by name, so a
 * function's locals shared one flat key space: an inner block's `x` and its
 * sibling's `x` were one entry, a `for` variable was never entered, and a
 * string global read as a C buffer in its own file and as a scalar in the
 * next. A binding is already the answer to "which declaration does this name
 * mean here", so this only says what that declaration's type looks like --
 * the same way for a local, a global, and a global read from another file.
 */
import CppNamespaceUtils from "../../utils/CppNamespaceUtils";
import DeclaredPointer from "../../utils/DeclaredPointer";
import DeclaredTypeFacts from "../../utils/DeclaredTypeFacts";
import ForeignTypeFacts from "../../utils/ForeignTypeFacts";
import TypeResolver from "../../utils/TypeResolver";
import TypeMapping from "../../utils/mapType";
import TYPE_WIDTH from "../../types/TYPE_WIDTH";
import ArrayDimensionText from "../../utils/ArrayDimensionText";
import type SymbolTable from "../../PARSE/3-Declare/SymbolTable";
import type ICodeGenSymbols from "../../types/ICodeGenSymbols";
import type TOverflowBehavior from "../../types/TOverflowBehavior";
import type TTypeInfo from "../../types/TTypeInfo";
import type TType from "../../types/TType";
import type TValueBinding from "../../types/TValueBinding";
import type IChainTyping from "../../types/IChainTyping";
import type IChainBase from "./types/IChainBase";
import type ITargetDescription from "../../types/ITargetDescription";
 
/** What a local declaration and a variable symbol both say */
interface IDeclared {
  readonly name: string;
  readonly type: TType;
  readonly arrayDimensions: ReadonlyArray<number | string>;
  readonly isConst: boolean;
  readonly isAtomic: boolean;
  readonly overflowBehavior: TOverflowBehavior;
  readonly initialValue: string | null;
  readonly initializerCallee: string | null;
}
 
class DeclaredTypeInfo {
  /**
   * @param target the run's target, whose data model types a header
   *   variable (a `double` is f32 on AVR)
   */
  static of(
    binding: TValueBinding | null,
    symbols: ICodeGenSymbols | null,
    symbolTable: SymbolTable,
    target: ITargetDescription | null,
  ): TTypeInfo | undefined {
    switch (binding?.kind) {
      case "local": {
        const declaration = binding.declaration;
        if (declaration.kind === "parameter") {
          return DeclaredTypeInfo.parameter(declaration, symbols, symbolTable);
        }
        return DeclaredTypeInfo.declared(declaration, symbols, symbolTable);
      }
      case "variable": {
        const symbol = binding.symbol;
        return DeclaredTypeInfo.declared(
          {
            ...symbol,
            arrayDimensions: symbol.arrayDimensions ?? [],
            initialValue: symbol.initialValue ?? null,
            initializerCallee: symbol.initializerCallee ?? null,
          },
          symbols,
          symbolTable,
        );
      }
      case "foreign":
        return ForeignTypeFacts.variableTypeInfo(
          symbolTable,
          binding.name,
          target,
        );
      default:
        return undefined;
    }
  }
 
  /**
   * What an assignment target writes, from the typer's chain of it: its root
   * as the spelling binds at the target (`this.`/`global.` included), and the
   * declared type of the variable written -- the root, or for `Scope.member`
   * the member.
   */
  static ofChain(
    chain: IChainTyping,
    symbols: ICodeGenSymbols | null,
    symbolTable: SymbolTable,
    target: ITargetDescription | null,
  ): IChainBase {
    const rootTypeInfo = DeclaredTypeInfo.of(
      chain.root,
      symbols,
      symbolTable,
      target,
    );
    const last = chain.steps.at(-1);
    if (DeclaredTypeInfo.nameSteps(chain) === 0) {
      return { root: chain.root, rootTypeInfo, typeInfo: rootTypeInfo, last };
    }
    const member = chain.steps[0]?.after?.binding ?? null;
    return {
      root: chain.root,
      rootTypeInfo,
      typeInfo: DeclaredTypeInfo.of(member, symbols, symbolTable, target),
      last,
    };
  }
 
  /**
   * How many of a chain's steps belong to the variable's NAME: a scope's
   * member (`Scope.member`) is one; `x`, `this.x` and `global.x` bind in the
   * root, so none. The steps after them are operations on the variable. The
   * one answer `ofChain` and every reader of "is this a name, and what
   * follows it" share (#1668: it was spelled twice, and a third copy missed
   * `Scope.arr[i]`).
   */
  static nameSteps(chain: IChainTyping): number {
    return chain.root?.kind === "scope" ? 1 : 0;
  }
 
  /** A local or a global: what its declaration says, as C will hold it */
  private static declared(
    d: IDeclared,
    symbols: ICodeGenSymbols | null,
    symbolTable: SymbolTable,
  ): TTypeInfo {
    const dimensions = ArrayDimensionText.numeric(d.arrayDimensions);
    if (d.type.kind === "string") {
      // ADR-045: a string is its C buffer, one wider than its capacity
      return {
        baseType: "char",
        bitWidth: 8,
        isArray: true,
        arrayDimensions: [...dimensions, d.type.capacity + 1],
        isConst: d.isConst,
        isString: true,
        stringCapacity: d.type.capacity,
        overflowBehavior: d.overflowBehavior,
        isAtomic: d.isAtomic,
      };
    }
    const baseType = DeclaredTypeInfo.baseTypeOf(d.type, symbolTable);
    const isArray = dimensions.length > 0;
    return {
      baseType,
      isArray,
      arrayDimensions: isArray ? dimensions : undefined,
      isConst: d.isConst,
      overflowBehavior: d.overflowBehavior,
      isAtomic: d.isAtomic,
      ...DeclaredTypeFacts.of(baseType, symbols, TYPE_WIDTH[baseType] || 0),
      isPointer: DeclaredPointer.of(
        {
          cType: TypeMapping.mapType(baseType),
          name: d.name,
          initializerCallee: d.initializerCallee,
          initialValue: d.initialValue,
        },
        symbolTable,
      ),
    };
  }
 
  /**
   * A parameter, as its function's context holds it: a scalar string is
   * `string`, an array of strings carries the buffer dimension, and a C
   * typedef struct is reached through a pointer (#958). Parameters have no
   * ADR-044 behavior of their own (#1681).
   */
  private static parameter(
    d: IDeclared,
    symbols: ICodeGenSymbols | null,
    symbolTable: SymbolTable,
  ): TTypeInfo {
    const dimensions = ArrayDimensionText.numeric(d.arrayDimensions);
    const isArray = dimensions.length > 0;
    const capacity = d.type.kind === "string" ? d.type.capacity : undefined;
    // The bare `string` keyword resolves as a struct named `string`
    const isString =
      d.type.kind === "string" ||
      (d.type.kind === "struct" && d.type.name === "string");
    let baseType = DeclaredTypeInfo.baseTypeOf(d.type, symbolTable);
    if (isString && !isArray) baseType = "string";
    if (capacity !== undefined && isArray) dimensions.push(capacity + 1);
    return {
      baseType,
      isArray,
      arrayDimensions: dimensions.length > 0 ? dimensions : undefined,
      isConst: d.isConst,
      // #1681: a compound assignment to a parameter clamps like any target
      overflowBehavior: d.overflowBehavior,
      ...DeclaredTypeFacts.of(baseType, symbols, TYPE_WIDTH[baseType] || 0),
      isString,
      stringCapacity: isString ? capacity : undefined,
      // #958, ADR-030: a handle is held through a pointer -- the one predicate
      ...(DeclaredPointer.isHandleType(baseType, symbolTable) && {
        isPointer: true,
      }),
    };
  }
 
  /** A declared type's name as C spells it, `::` for a C++ namespace */
  private static baseTypeOf(type: TType, symbolTable: SymbolTable): string {
    return CppNamespaceUtils.convertToCppNamespace(
      TypeResolver.getTypeName(type),
      symbolTable,
    );
  }
}
 
export default DeclaredTypeInfo;