reaches

tcmts.cli.effects.reaches · #7avj77bi · term · runs anywhere

What definitions' code can reach outside the codebase (the clock, randomness, the network, files, the environment, other processes), through everything they use: by hash. What a definition could reach, not what a run of it did: a function that reads the clock somewhere counts, called or not. Each one's own effects are worked out once and kept with it, and so is what each component reaches (kept with its first member). src has what isn't saved yet (a scratch file's definitions and watch expressions).

import type { CodebaseCore } from "#codebase/tcmts/cli/codebase";
import { ownEffectsKey, reached } from "#codebase/tcmts/cli/effects";
import { ownEffects } from "#codebase/tcmts/core/effects";
import type { Effect } from "#codebase/tcmts/core/effects";
import { definesFunction, writtenOutRefs } from "#codebase/tcmts/core/ir";
import type { DefRecord } from "#codebase/tcmts/core/types";
import type { DefSource } from "#codebase/tcmts/loader";

export function reaches(
  cb: CodebaseCore,
  hashes: Iterable<string>,
  src: DefSource = cb.source(),
): Map<string, Effect[]> {
  const key = ownEffectsKey();
  // (what's followed changes with `writtenOutRefs`: what's kept under the last one's key is worked out again)
  const through = `reaches ${key.slice("effects ".length)} ${String((writtenOutRefs as unknown as Record<symbol, string>)[Symbol.for("tcm.hash")] ?? "?").slice(0, 8)}`;
  const learned: [string, string, Effect[]][] = [];
  const own = (m: DefRecord): Effect[] => {
    const kept = cb.store.getMetadata(m.hash, key);
    if (kept !== undefined) return JSON.parse(kept) as Effect[];
    const e = ownEffects(m.ir);
    learned.push([m.hash, key, e]);
    return e;
  };
  const of = (comp: string): Effect[] => {
    const known = reached.get(comp);
    if (known) return known;
    const members = src.members(comp);
    const kept = members[0] && cb.store.getMetadata(members[0].hash, through);
    if (kept) {
      const r = JSON.parse(kept) as Effect[];
      reached.set(comp, r);
      return r;
    }
    // (components make no cycles: what one uses is in others)
    reached.set(comp, []);
    const found = new Set<Effect>();
    for (const m of members) {
      for (const e of own(m)) found.add(e);
      // a function whose source it only writes out (`${f}`) runs wherever that goes, not here
      const written = writtenOutRefs(m.ir);
      for (const d of m.deps) {
        if (d.pos !== "value" || !d.ref.startsWith("#")) continue;
        const dep = src.get(d.ref.slice(1));
        if (dep && written.has(dep.hash) && dep.kind === "term" && definesFunction(dep.ir))
          continue;
        if (dep && dep.componentHash !== comp) for (const e of of(dep.componentHash)) found.add(e);
      }
    }
    const r = [...found].sort();
    reached.set(comp, r);
    if (members[0]) learned.push([members[0].hash, through, r]);
    return r;
  };
  const out = new Map<string, Effect[]>();
  for (const h of hashes) {
    const rec = src.get(h);
    out.set(h, rec ? of(rec.componentHash) : []);
  }
  if (learned.length)
    cb.store.transaction(() => {
      for (const [h, k, e] of learned) cb.store.setMetadata(h, k, JSON.stringify(e));
    });
  return out;
}

uses

used by