matchOne

tcmts.core.shape.matchOne · #fqavtgz6 · term · runs anywhere

Match one query shape against one definition shape: in order, with arguments reordered, or with the result awaited.

import { permutations, unify } from "#codebase/tcmts/core/shape";
import type { Shape, ShapeMatch, Subst } from "#codebase/tcmts/core/shape";

export function matchOne(q: Shape, d: Shape): ShapeMatch | undefined {
  let best: ShapeMatch | undefined;
  const attempt = (qs: Shape, ds: Shape, extra: number, note?: string) => {
    const s: Subst = { q: new Map(), d: new Map(), cost: extra, notes: new Set(note ? [note] : []) };
    if (unify(qs, ds, s) && (!best || s.cost < best.cost)) best = { cost: s.cost, notes: [...s.notes] };
  };
  if (q.k !== "fn" || d.k !== "fn") {
    attempt(q, d, 0);
    return best;
  }
  // an async function answers a query for its result type, a step away
  const rets: [Shape, number, string?][] = [[d.ret, 0]];
  if (d.ret.k === "named" && d.ret.name === "Promise" && d.ret.args.length === 1 && !(q.ret.k === "named" && q.ret.name === "Promise")) {
    rets.push([d.ret.args[0]!, 1, "async"]);
  }
  const orders = q.params.length <= 5 ? permutations(q.params.map((_, i) => i)) : [q.params.map((_, i) => i)];
  for (const [ret, cost, note] of rets) {
    for (const order of orders) {
      const reordered = order.some((j, i) => j !== i);
      const qp: Shape = { ...q, params: order.map((j) => q.params[j]!) };
      const dp: Shape = { ...d, ret };
      attempt(qp, dp, cost + (reordered ? 1 : 0), reordered ? "arguments reordered" : note);
      if (reordered && note && best) best.notes = [...new Set([...best.notes, note])];
    }
  }
  return best;
}

uses

used by