All files / PARSE/4-Resolve CallbackCompatibility.ts

95.23% Statements 20/21
94.44% Branches 17/18
100% Functions 2/2
100% Lines 19/19

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                                                                                              3242x 3242x 3240x 6893x 3233x             3242x 3242x 3240x 744x       744x 13x       3242x                     744x 589x             155x 155x 11x 11x 11x              
/**
 * Which functions are used as ADR-029 callbacks, across the whole program.
 *
 * A function assigned to a callback typedef must keep the typedef's parameter
 * shape, so it may not take #268 auto-const or ADR-006 pass-by-value. That makes
 * "is this function used as a callback?" a fact the generated signature depends
 * on — and the use can be in a different file from the declaration, so it is a
 * cross-file fact.
 *
 * It was accumulated instead. `FunctionCallAnalyzer` recorded it as a side
 * effect while analyzing each file during rendering, into a mutable static that
 * `CodeGenState.reset()` deliberately skipped so the entries would survive —
 * which means a file rendered early saw fewer callbacks than one rendered late,
 * and its signatures were decided on a partial answer. #1511 derived it over
 * every tree before anything renders, and #1452 removed the static.
 *
 * #1825: what remained was running that analyzer over every tree, diagnostics
 * discarded, for the map it filled as a side effect -- from the orchestration
 * layer, because `PARSE/` may not import `TRANSPILE/`. 1.3 now records where
 * each file names what may be a function (`CallbackUseCollector`), and the rule
 * that decides which of those uses is a callback is here, its one owner.
 *
 * #1544: a function is recognized wherever it is declared. Both recognition
 * rules once gated on the functions the USING file declares, so a callback
 * target in another file was missed and took ordinary parameter rules --
 * emitting a signature that no longer matched the typedef it was assigned to,
 * at transpile exit 0.
 */
 
import type SymbolTable from "../3-Declare/SymbolTable";
import type IFileSymbols from "../../types/IFileSymbols";
import type TCallbackUse from "../../types/TCallbackUse";
 
class CallbackCompatibility {
  /**
   * @param files every file's `IFileSymbols`, in declaration order
   * @param symbolTable the C and C++ header symbols, for the typedefs
   * @returns function name to the callback typedef it is used as
   */
  static derive(
    files: ReadonlyArray<IFileSymbols>,
    symbolTable: SymbolTable,
  ): ReadonlyMap<string, string> {
    // #1544: what the PROGRAM declares. The map decides a generated signature
    // and the wiring may sit in any file, so recognition needs the same
    // whole-program scope the fact has. Read from the symbols, which carry the
    // one encoding of a function's name.
    const programFunctions = new Set<string>();
    for (const file of files) {
      for (const symbol of file.symbols) {
        if (symbol.kind === "function") {
          programFunctions.add(symbol.fullyQualifiedCName);
        }
      }
    }
 
    // In declaration order and source order: a later use of a function wins
    // the typedef an earlier one recorded.
    const callbacks = new Map<string, string>();
    for (const file of files) {
      for (const use of file.callbackUses) {
        const typedef = CallbackCompatibility.callbackTypedefOf(
          use,
          symbolTable,
        );
        if (typedef !== null && programFunctions.has(use.functionName)) {
          callbacks.set(use.functionName, typedef);
        }
      }
    }
    return callbacks;
  }
 
  /**
   * The C function-pointer typedef a use assigns its function to, or null
   * when the use is not a callback position.
   */
  private static callbackTypedefOf(
    use: TCallbackUse,
    symbolTable: SymbolTable,
  ): string | null {
    if (use.kind === "initializer") {
      return symbolTable.isCFunctionPointerTypedef(use.typeName)
        ? use.typeName
        : null;
    }
 
    // Issue #895: an argument to a C function whose parameter at that
    // position is a function pointer typedef.
    const cFunc = symbolTable.getCSymbol(use.callee);
    if (cFunc?.kind !== "function" || !cFunc.parameters) return null;
    const param = cFunc.parameters[use.argIndex];
    Iif (!param) return null;
    return symbolTable.isCFunctionPointerTypedef(param.type)
      ? param.type
      : null;
  }
}
 
export default CallbackCompatibility;