All files / utils DeclaredPointer.ts

100% Statements 15/15
100% Branches 19/19
100% Functions 4/4
100% Lines 12/12

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                                                                        1377x                           3592x               2521x 2520x 2513x   1927x 57x   57x         3x       1924x 1924x     1x              
/**
 * #1668: whether a declaration is a C pointer though C-Next spelled its type
 * without one -- the one decision the emitted declaration and the
 * declaration's type info both read, so the two cannot disagree.
 *
 * A declaration whose C type is already a pointer (ADR-046's `cstring` is
 * `char*`) is one. Otherwise three arms decide, in order (they were
 * `CodeGenWalker._inferVariableType`'s):
 *
 * 1. #958: a C header's typedef struct is always handled through a pointer.
 * 2. #895 Bug B: an initializer that calls a C function returning `T*`, for a
 *    declared `T`, makes the variable a `T*`.
 * 3. ADR-046: a `c_`-prefixed variable initialized from one of the C library
 *    functions that return a struct pointer.
 */
import type SymbolTable from "../PARSE/3-Declare/SymbolTable";
import STRUCT_POINTER_C_FUNCTIONS from "./constants/STRUCT_POINTER_C_FUNCTIONS";
 
/** What the three arms read of a declaration */
interface IPointerFacts {
  /** The declared type as C spells it (`uint8_t`, `char*`, `widget_t`) */
  readonly cType: string;
  readonly name: string;
  /** What the initializer calls, as 1.3 recorded it */
  readonly initializerCallee: string | null;
  /** The initializer's source text, or null when there is none */
  readonly initialValue: string | null;
}
 
class DeclaredPointer {
  /**
   * The C type a declaration is written with: its type, and a `*` when it
   * is a pointer its type does not already spell. The one consequence the
   * `.c` definition and the `.h` declaration both follow.
   */
  static spell(cType: string, isPointer: boolean): string {
    return isPointer && !cType.endsWith("*") ? `${cType}*` : cType;
  }
 
  /**
   * ADR-030 / #958: whether a type is a C handle -- an incomplete (opaque) or
   * typedef struct type from a C header, which C-Next only ever holds through
   * a pointer. Arm 1 below, and `TranspileState.isHeldThroughPointer`, which
   * every other declaration site asks: one predicate, so a declaration's
   * pointer-ness and the sites that ask about its type cannot differ.
   */
  static isHandleType(
    cType: string,
    foreign: Pick<SymbolTable, "isOpaqueType">,
  ): boolean {
    return foreign.isOpaqueType(cType);
  }
 
  /** Whether the declaration is a pointer, by the three arms above */
  static of(
    facts: IPointerFacts,
    foreign: Pick<SymbolTable, "isOpaqueType" | "getCSymbol">,
  ): boolean {
    if (facts.cType.endsWith("*")) return true;
    if (DeclaredPointer.isHandleType(facts.cType, foreign)) return true;
    if (facts.initialValue === null) return false;
 
    if (facts.initializerCallee !== null) {
      const callee = foreign.getCSymbol(facts.initializerCallee);
      // `widget_t *` or `widget_t*` for a declared `widget_t`
      if (
        callee?.kind === "function" &&
        callee.type.endsWith("*") &&
        callee.type.slice(0, -1).trim() === facts.cType
      ) {
        return true;
      }
    }
 
    const initialValue = facts.initialValue;
    return (
      facts.name.startsWith("c_") &&
      [...STRUCT_POINTER_C_FUNCTIONS].some((fn) =>
        initialValue.includes(`${fn}(`),
      )
    );
  }
}
 
export default DeclaredPointer;