@suss/resolution 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ var ASKING_RELATIONS = [
17
17
  "wantedEnvObject",
18
18
  "wantedStreamObject",
19
19
  "wantedExitSink",
20
+ "wantedDispatch",
20
21
  "wantedOrigin",
21
22
  "wantedCallOrigin",
22
23
  "wantedExportsOf",
@@ -534,6 +535,29 @@ function classMemberOf(name) {
534
535
  return name.startsWith(CLASS_MEMBER_PREFIX) ? name.slice(CLASS_MEMBER_PREFIX.length) : null;
535
536
  }
536
537
 
538
+ // src/valuePositions.ts
539
+ var VALUE_ENTRY = "value";
540
+ var SPREAD_ENTRY = "spread";
541
+ function addEntryRows(add, container, entries) {
542
+ entries.forEach((entry, position) => {
543
+ add("writtenEntryAt", [container, String(position), entry.key, entry.form]);
544
+ });
545
+ }
546
+ function addTemplateRows(add, template, pieces) {
547
+ let position = 0;
548
+ for (const piece of pieces) {
549
+ if ("hole" in piece) {
550
+ add("holeAt", [template, String(position), piece.hole]);
551
+ position += 1;
552
+ continue;
553
+ }
554
+ if (piece.text !== "") {
555
+ add("partAt", [template, String(position), piece.text]);
556
+ position += 1;
557
+ }
558
+ }
559
+ }
560
+
537
561
  // src/contract.ts
538
562
  var distinct = (values) => new Set(values).size;
539
563
  function leadsToOneOf(facts, key, targets) {
@@ -551,6 +575,30 @@ function leadsToOneOf(facts, key, targets) {
551
575
  return false;
552
576
  }
553
577
  var RELATIONS_THAT_CAN_CROSS = ["imports", "binds", "reExports"];
578
+ function someValueLeadsTo(facts, key) {
579
+ const target = /* @__PURE__ */ new Set([key]);
580
+ return ["returnsValue", "holdsUnderKey"].some(
581
+ (relation) => facts(relation).some((row) => leadsToOneOf(facts, row[1] ?? "", target))
582
+ );
583
+ }
584
+ function holdsValueThenSpread(facts, name) {
585
+ const entries = facts("writtenEntryAt");
586
+ const held = facts("holdsProperty");
587
+ return [...new Set(entries.map((row) => row[0]))].some((container) => {
588
+ const own = entries.filter((row) => row[0] === container);
589
+ const value = own.find((row) => row[1] === "0");
590
+ const spread2 = own.find((row) => row[1] === "1");
591
+ return own.length === 2 && value?.[3] === VALUE_ENTRY && spread2?.[3] === SPREAD_ENTRY && held.some(
592
+ (row) => row[0] === container && row[1] === name && row[2] === value[2]
593
+ ) && !held.some((row) => row[0] === container && row[2] === spread2[2]);
594
+ });
595
+ }
596
+ function valueThenSpread(facts, name) {
597
+ if (facts("writtenEntryAt").length === 0) {
598
+ return "no list or object states its entries, so nothing says the order they are written in";
599
+ }
600
+ return holdsValueThenSpread(facts, name) ? null : `no container's entries are the value under ${name} at 0, keyed as holdsProperty keys it, and then a spread at 1 keyed on the collection it unpacks`;
601
+ }
554
602
  var FACT_CONTRACT_CASES = [
555
603
  {
556
604
  name: "two functions, one parameter name",
@@ -749,6 +797,122 @@ var FACT_CONTRACT_CASES = [
749
797
  return stores.some((row) => leadsToOneOf(facts, row[0] ?? "", sites)) ? null : "the write is keyed so that nothing leads from it to the construction, so the rules cannot find the object it lands on";
750
798
  }
751
799
  },
800
+ {
801
+ name: "a table of functions called by a key worked out at run time",
802
+ requires: "a module-level table written out with a function under the name created and another under deleted, and a function that calls the table's entry under its parameter, `TABLE[kind](payload)` or the language's own spelling",
803
+ // The rules take the entries off the object the read's container
804
+ // leads to, so the container has to be keyed the way `binds` keys it,
805
+ // and each entry under the name the source writes, without quotes.
806
+ check: (facts) => {
807
+ const entries = facts("holdsProperty");
808
+ const tables = new Set(
809
+ entries.filter((row) => row[1] === "created").map((row) => row[0] ?? "").filter(
810
+ (table) => entries.some((row) => row[0] === table && row[1] === "deleted")
811
+ )
812
+ );
813
+ if (tables.size === 0) {
814
+ return "no object contains a function under created and another under deleted, written without quotes";
815
+ }
816
+ const reads = facts("choosesEntry");
817
+ if (reads.length === 0) {
818
+ return "the read at a key worked out at run time is not written down as choosesEntry, so no call through it finds the table's functions";
819
+ }
820
+ return reads.some((row) => leadsToOneOf(facts, row[1] ?? "", tables)) ? null : "the read's container is keyed so that nothing leads from it to the table";
821
+ }
822
+ },
823
+ {
824
+ name: "a function written into a table under a computed key",
825
+ requires: "a module-level empty table, and a function that writes its second parameter into it under its first, `REGISTRY[name] = handler` or the language's own spelling",
826
+ // A registry fills its table from a function its callers pass handlers
827
+ // to, so the write has to say the parameter went in, keyed so that
828
+ // `binds` leads from where it went to the table.
829
+ check: (facts) => {
830
+ const params = new Set(facts("paramOf").map((row) => row[2] ?? ""));
831
+ const writes = facts("holdsUnderKey").filter(
832
+ (row) => leadsToOneOf(facts, row[1] ?? "", params)
833
+ );
834
+ if (writes.length === 0) {
835
+ return "the write of a parameter into a table under a computed key is not written down as holdsUnderKey";
836
+ }
837
+ const objects = new Set(facts("objectValue").map((row) => row[0] ?? ""));
838
+ return writes.some((row) => leadsToOneOf(facts, row[0] ?? "", objects)) ? null : "the write is keyed so that nothing leads from it to the table";
839
+ }
840
+ },
841
+ {
842
+ name: "a list written with a spread",
843
+ requires: "a module-level name assigned a written-out list of a name and then a spread of another name, `[first, ...rest]` or the language's own spelling",
844
+ check: (facts) => valueThenSpread(facts, "0")
845
+ },
846
+ {
847
+ name: "an object written with a spread",
848
+ requires: "a module-level name assigned an object or a dictionary written out with a name under the key a, written as a string where the language writes keys that way, and then a spread of another name",
849
+ check: (facts) => valueThenSpread(facts, "a")
850
+ },
851
+ {
852
+ name: "a template string with a hole",
853
+ requires: "a module-level name assigned the result of a call, and a template string that puts that name between two pieces of text, `/users/${id}/posts` or the language's own spelling",
854
+ check: (facts) => {
855
+ const parts = facts("partAt");
856
+ const [hole, ...moreHoles] = facts("holeAt");
857
+ if (parts.length !== 2 || hole === void 0 || moreHoles.length > 0) {
858
+ return `expected two parts and one hole, found ${parts.length} and ${moreHoles.length + (hole === void 0 ? 0 : 1)}`;
859
+ }
860
+ const positions = [...parts, hole].filter((row) => row[0] === hole[0]).map((row) => row[1]).sort();
861
+ if (positions.join() !== "0,1,2" || hole[1] !== "1") {
862
+ return "the parts and the hole of one template are not numbered together in source order from 0";
863
+ }
864
+ const calls = new Set(facts("call").map((row) => row[0] ?? ""));
865
+ return leadsToOneOf(facts, hole[2] ?? "", calls) ? null : "the hole is keyed so that nothing leads from it to the value the name is given";
866
+ }
867
+ },
868
+ {
869
+ name: "a callback given each element of a list",
870
+ requires: "a function that maps its parameter through a callback returning the element it is given, `users.map((user) => user)`, a comprehension or the language's own spelling",
871
+ // The collection is a read of the function's parameter, and the
872
+ // callback's own read of its variable joins the key elementOf states.
873
+ check: (facts) => {
874
+ const [row, ...more] = facts("elementOf");
875
+ if (row === void 0 || more.length > 0) {
876
+ return `expected one elementOf row, found ${more.length + (row === void 0 ? 0 : 1)}`;
877
+ }
878
+ const params = new Set(facts("paramOf").map((one) => one[2] ?? ""));
879
+ if (!leadsToOneOf(facts, row[1] ?? "", params)) {
880
+ return "the collection is keyed so that nothing leads from it to the parameter it reads";
881
+ }
882
+ return someValueLeadsTo(facts, row[0] ?? "") ? null : "no read of the variable leads to the key elementOf gives it, so nothing knows what the read is given";
883
+ }
884
+ },
885
+ {
886
+ name: "a callback given the parts of each element of a list",
887
+ requires: "a function that maps its parameter through a callback taking each element apart into two names and returning the first, `pairs.map(([name, kind]) => name)`, `[name for name, kind in pairs]` or the language's own spelling",
888
+ check: (facts) => {
889
+ const parts = facts("elementPartOf");
890
+ const first = parts.find((row) => row[2] === "0");
891
+ const second = parts.find((row) => row[2] === "1");
892
+ if (first === void 0 || second === void 0) {
893
+ return "no two names are given parts 0 and 1 of each element, so nothing says what a name taken apart is given";
894
+ }
895
+ const params = new Set(facts("paramOf").map((one) => one[2] ?? ""));
896
+ if (first[1] !== second[1] || !leadsToOneOf(facts, first[1] ?? "", params)) {
897
+ return "the two parts name different collections, or one that nothing leads from to the parameter it reads";
898
+ }
899
+ return someValueLeadsTo(facts, first[0] ?? "") ? null : "no read of the first name leads to the key elementPartOf gives it";
900
+ }
901
+ },
902
+ {
903
+ name: "a callback given each position in a list",
904
+ requires: "a function that maps its parameter through a callback taking the element and then its position and returning the position, `users.map((user, at) => at)`, `enumerate` in a comprehension, or the language's own spelling",
905
+ check: (facts) => {
906
+ const [row, ...more] = facts("positionOf");
907
+ if (row === void 0 || more.length > 0) {
908
+ return `expected one positionOf row, found ${more.length + (row === void 0 ? 0 : 1)}`;
909
+ }
910
+ if (!facts("elementOf").some((one) => one[0] === row[1])) {
911
+ return "the position is tied to a variable that no elementOf row gives an element, so nothing says what it counts through";
912
+ }
913
+ return someValueLeadsTo(facts, row[0] ?? "") ? null : "no read of the position leads to the key positionOf gives it";
914
+ }
915
+ },
752
916
  {
753
917
  name: "a value another file declares",
754
918
  requires: "two files, one declaring a value and the other reading it by name",
@@ -1151,13 +1315,13 @@ var FactLog = class {
1151
1315
  hashesByKey(relation, row) {
1152
1316
  const hashes = /* @__PURE__ */ new Map();
1153
1317
  const atoms = row.map((atom) => this.hashesOf(String(atom)));
1154
- const term = (hash, column) => spread(hash + Math.imul(column + 1, 2654435761) >>> 0);
1318
+ const term = (hash, column2) => spread(hash + Math.imul(column2 + 1, 2654435761) >>> 0);
1155
1319
  let loose = hashString(relation);
1156
- atoms.forEach((atom, column) => {
1157
- loose = loose + term(atom.loose, column) >>> 0;
1320
+ atoms.forEach((atom, column2) => {
1321
+ loose = loose + term(atom.loose, column2) >>> 0;
1158
1322
  });
1159
- atoms.forEach((atom, column) => {
1160
- const key = String(row[column]);
1323
+ atoms.forEach((atom, column2) => {
1324
+ const key = String(row[column2]);
1161
1325
  if (!atom.key || hashes.has(key)) {
1162
1326
  return;
1163
1327
  }
@@ -1254,6 +1418,107 @@ var FactLog = class {
1254
1418
  }
1255
1419
  };
1256
1420
 
1421
+ // src/dispatch.ts
1422
+ function dispatchOutcomes(db, calls, program = resolutionProgram()) {
1423
+ const outcomes = /* @__PURE__ */ new Map();
1424
+ if (calls.length === 0) {
1425
+ return outcomes;
1426
+ }
1427
+ askResolution(db, calls, DISPATCH_QUESTION, program);
1428
+ return readDispatchOutcomes(db, calls);
1429
+ }
1430
+ var DISPATCH_QUESTION = "wantedDispatch";
1431
+ function readDispatchOutcomes(db, calls) {
1432
+ const outcomes = /* @__PURE__ */ new Map();
1433
+ for (const call of new Set(calls)) {
1434
+ const sites = column(db, "wantedDispatchSite", call, 1);
1435
+ if (sites.length === 0) {
1436
+ continue;
1437
+ }
1438
+ outcomes.set(call, outcomeOf(db, call, sites));
1439
+ }
1440
+ return outcomes;
1441
+ }
1442
+ function outcomeOf(db, call, sites) {
1443
+ const byEntry = /* @__PURE__ */ new Map();
1444
+ for (const entry of column(db, "wantedDispatchEntry", call, 1)) {
1445
+ byEntry.set(entry, /* @__PURE__ */ new Set());
1446
+ }
1447
+ for (const row of db.lookup("wantedDispatchLikeEntry", 0, call)) {
1448
+ const held = String(row[3]);
1449
+ if (!byEntry.has(held) && matchesWrittenPart(db, String(row[1]), String(row[2]))) {
1450
+ byEntry.set(held, /* @__PURE__ */ new Set());
1451
+ }
1452
+ }
1453
+ for (const relation of ENTRY_FUNCTIONS) {
1454
+ for (const row of db.lookup(relation, 0, call)) {
1455
+ byEntry.get(String(row[1]))?.add(String(row[2]));
1456
+ }
1457
+ }
1458
+ const functions = [
1459
+ ...new Set([...byEntry.values()].flatMap((found) => [...found]))
1460
+ ];
1461
+ const unresolved = [...byEntry.values()].filter(
1462
+ (found) => found.size === 0
1463
+ ).length;
1464
+ const through = /* @__PURE__ */ new Map();
1465
+ for (const row of db.lookup("wantedDispatchThrough", 0, call)) {
1466
+ const inner = through.get(String(row[1])) ?? [];
1467
+ inner.push(String(row[2]));
1468
+ through.set(String(row[1]), inner);
1469
+ }
1470
+ return {
1471
+ functions,
1472
+ certain: functions.length === 1 && unresolved === 0 && narrowedToOneName(db, sites) && column(db, "wantedDispatchComputed", call, 1).length === 0 && column(db, "wantedDispatchInBranch", call, 1).length === 0,
1473
+ unresolved,
1474
+ through,
1475
+ containers: [
1476
+ ...new Set(
1477
+ sites.flatMap(
1478
+ (site) => ENTRY_READS.flatMap((relation) => column(db, relation, site, 1))
1479
+ )
1480
+ )
1481
+ ]
1482
+ };
1483
+ }
1484
+ var ENTRY_READS = [
1485
+ "choosesEntry",
1486
+ "choosesNamedEntry",
1487
+ "choosesEntryLike",
1488
+ "readsEntry"
1489
+ ];
1490
+ var ENTRY_FUNCTIONS = [
1491
+ "wantedDispatchFunction",
1492
+ "wantedDispatchLikeFunction"
1493
+ ];
1494
+ function matchesWrittenPart(db, site, name) {
1495
+ return db.lookup("choosesEntryLike", 0, site).some((row) => {
1496
+ const prefix = String(row[2]);
1497
+ const suffix = String(row[3]);
1498
+ return name.length >= prefix.length + suffix.length && name.startsWith(prefix) && name.endsWith(suffix);
1499
+ });
1500
+ }
1501
+ function narrowedToOneName(db, sites) {
1502
+ const names = /* @__PURE__ */ new Set();
1503
+ for (const site of sites) {
1504
+ if (db.lookup("choosesEntry", 0, site).length > 0) {
1505
+ return false;
1506
+ }
1507
+ for (const relation of NAMED_READS) {
1508
+ for (const row of db.lookup(relation, 0, site)) {
1509
+ names.add(String(row[2]));
1510
+ }
1511
+ }
1512
+ }
1513
+ return names.size === 1;
1514
+ }
1515
+ var NAMED_READS = ["choosesNamedEntry", "readsEntry"];
1516
+ function column(db, relation, key, at) {
1517
+ return [
1518
+ ...new Set(db.lookup(relation, 0, key).map((row) => String(row[at])))
1519
+ ];
1520
+ }
1521
+
1257
1522
  // src/entryReuse.ts
1258
1523
  function fileOfKey(key) {
1259
1524
  return key.slice(0, key.lastIndexOf(":"));
@@ -1391,11 +1656,11 @@ function exportTrail(proof, describe) {
1391
1656
  }
1392
1657
  function argumentReason(proof, describe) {
1393
1658
  const [, p, a] = proof.tuple;
1394
- if (proof.kind !== "derived") {
1395
- return `${describe(p)} is a parameter, and a call passes it ${describe(a)}`;
1396
- }
1397
- const f = proof.premises[0].tuple[0];
1398
- return `${describe(p)} is a parameter of ${describe(f)}, and a call passes it ${describe(a)}`;
1659
+ const f = proof.kind === "derived" ? proof.premises.find(
1660
+ (premise) => premise.relation === "paramOf" || premise.relation === "paramNamed"
1661
+ )?.tuple[0] : void 0;
1662
+ const ofFunction = f === void 0 ? "" : ` of ${describe(f)}`;
1663
+ return `${describe(p)} is a parameter${ofFunction}, and a call passes it ${describe(a)}`;
1399
1664
  }
1400
1665
  function memberSaid(name) {
1401
1666
  const member = classMemberOf(name);
@@ -1780,14 +2045,24 @@ function startsAtName(node, name, rules) {
1780
2045
 
1781
2046
  // src/nodeKey.ts
1782
2047
  var SPAN_KEY = /^(.*):(\d+)-(\d+)$/;
1783
- function nodeOfKey(rootsByFile, key) {
2048
+ function spanFileOf(key) {
2049
+ return key.match(SPAN_KEY)?.[1] ?? null;
2050
+ }
2051
+ function spanOfKey(key) {
1784
2052
  const span = key.match(SPAN_KEY);
2053
+ return span === null ? null : {
2054
+ file: span[1],
2055
+ start: Number(span[2]),
2056
+ end: Number(span[3])
2057
+ };
2058
+ }
2059
+ function nodeOfKey(rootsByFile, key) {
2060
+ const span = spanOfKey(key);
1785
2061
  if (span === null) {
1786
2062
  return null;
1787
2063
  }
1788
- const root = rootsByFile.get(span[1]);
1789
- const start = Number(span[2]);
1790
- const end = Number(span[3]);
2064
+ const root = rootsByFile.get(span.file);
2065
+ const { start, end } = span;
1791
2066
  if (root === void 0 || end <= start) {
1792
2067
  return null;
1793
2068
  }
@@ -2160,12 +2435,27 @@ function hasRow(db, relation, key, member) {
2160
2435
 
2161
2436
  // src/writtenValue.ts
2162
2437
  var WRITTEN_AS = "wantedIsWrittenAs";
2163
- function writtenValueOf(db, key, ask, relation = WRITTEN_AS) {
2164
- const answers = settledByKey(db, [key], ask, relation).get(key) ?? [];
2438
+ function writtenValueOf(db, key, ask, relation = WRITTEN_AS, keep = () => true) {
2439
+ const settled = settledByKey(db, [key], ask, relation).get(key) ?? [];
2440
+ const answers = settled.filter(keep);
2165
2441
  if (answers.length === 1) {
2166
2442
  return answers[0];
2167
2443
  }
2168
- return relation === WRITTEN_AS ? fallbackWrittenAs(db, key, ask) : null;
2444
+ if (answers.length === 0 && settled.length > 0) {
2445
+ return null;
2446
+ }
2447
+ const fallback = relation === WRITTEN_AS ? fallbackWrittenAs(db, key, ask) : null;
2448
+ return fallback !== null && keep(fallback) ? fallback : null;
2449
+ }
2450
+ function answersFromTestsLeftOut(key, isTestFile) {
2451
+ const asked = spanFileOf(key);
2452
+ if (asked === null || isTestFile(asked)) {
2453
+ return () => true;
2454
+ }
2455
+ return (answer) => {
2456
+ const file = spanFileOf(answer);
2457
+ return file === null || !isTestFile(file);
2458
+ };
2169
2459
  }
2170
2460
  var FALLBACK_BEHIND = "wantedFallbackBehind";
2171
2461
  function fallbackWrittenAs(db, key, ask) {
@@ -2343,6 +2633,165 @@ var STEP_LATTICE = [
2343
2633
  { soFar: RESULT_STEP, next: INSTANCE_STEP, took: RESULT_STEP },
2344
2634
  { soFar: RESULT_STEP, next: RESULT_STEP, took: RESULT_STEP }
2345
2635
  ];
2636
+ var DISPATCH_RULES = [
2637
+ rule2(
2638
+ "entryRead",
2639
+ [v2("site"), v2("o")],
2640
+ [lit2("choosesEntry", v2("site"), v2("o"))]
2641
+ ),
2642
+ rule2(
2643
+ "entryRead",
2644
+ [v2("site"), v2("o")],
2645
+ [lit2("namedEntryRead", v2("site"), v2("o"), v2("n"))]
2646
+ ),
2647
+ rule2(
2648
+ "entryRead",
2649
+ [v2("site"), v2("o")],
2650
+ [lit2("choosesEntryLike", v2("site"), v2("o"), v2("p"), v2("s"))]
2651
+ ),
2652
+ // Every entry of a read whose key is partly written out, by name. A rule
2653
+ // cannot compare strings, so dispatch.ts keeps the names that match, and
2654
+ // these stay out of `dispatchesTo`.
2655
+ rule2(
2656
+ "likeEntryAt",
2657
+ [v2("site"), v2("n"), v2("held")],
2658
+ [
2659
+ lit2("choosesEntryLike", v2("site"), v2("o"), v2("p"), v2("s")),
2660
+ lit2("containerOf", v2("o"), v2("obj")),
2661
+ lit2("contains", v2("obj"), v2("n"), v2("held"))
2662
+ ]
2663
+ ),
2664
+ rule2(
2665
+ "namedEntryRead",
2666
+ [v2("site"), v2("o"), v2("n")],
2667
+ [lit2("choosesNamedEntry", v2("site"), v2("o"), v2("n"))]
2668
+ ),
2669
+ rule2(
2670
+ "namedEntryRead",
2671
+ [v2("site"), v2("o"), v2("n")],
2672
+ [lit2("readsEntry", v2("site"), v2("o"), v2("n"))]
2673
+ ),
2674
+ // The container a read takes its entry from: an object written out, or
2675
+ // a collection the program fills through its methods.
2676
+ rule2("containerOf", [v2("o"), v2("obj")], [lit2("objectOf", v2("o"), v2("obj"))]),
2677
+ rule2(
2678
+ "containerOf",
2679
+ [v2("o"), v2("c")],
2680
+ [
2681
+ lit2("reaches", v2("o"), v2("c"), VALUE_STEP),
2682
+ lit2("collectionValue", v2("c"))
2683
+ ]
2684
+ ),
2685
+ rule2("containerOf", [v2("c"), v2("c")], [lit2("collectionValue", v2("c"))]),
2686
+ rule2(
2687
+ "entryAt",
2688
+ [v2("site"), v2("held")],
2689
+ [
2690
+ lit2("choosesEntry", v2("site"), v2("o")),
2691
+ lit2("containerOf", v2("o"), v2("obj")),
2692
+ lit2("contains", v2("obj"), v2("n"), v2("held"))
2693
+ ],
2694
+ "any entry"
2695
+ ),
2696
+ rule2(
2697
+ "entryAt",
2698
+ [v2("site"), v2("held")],
2699
+ [
2700
+ lit2("namedEntryRead", v2("site"), v2("o"), v2("n")),
2701
+ lit2("containerOf", v2("o"), v2("obj")),
2702
+ lit2("contains", v2("obj"), v2("n"), v2("held"))
2703
+ ],
2704
+ "named entry"
2705
+ ),
2706
+ rule2(
2707
+ "entryAt",
2708
+ [v2("site"), v2("held")],
2709
+ [lit2("computedEntryAt", v2("site"), v2("held"))]
2710
+ ),
2711
+ // A write under a key the source computes, `T[name] = fn` or a map's
2712
+ // `set`, could be under any name, so every read of the container may
2713
+ // come back with it, a read narrowed to one name included.
2714
+ rule2(
2715
+ "computedEntryAt",
2716
+ [v2("site"), v2("held")],
2717
+ [
2718
+ lit2("entryRead", v2("site"), v2("o")),
2719
+ lit2("containerOf", v2("o"), v2("c")),
2720
+ lit2("refersToObject", v2("x"), v2("c")),
2721
+ lit2("holdsUnderKey", v2("x"), v2("held"))
2722
+ ],
2723
+ "entry under a computed key"
2724
+ ),
2725
+ // The read a call takes its function from: the callee itself, a name
2726
+ // for it, or the call when the call is the read, as Ruby's `send` is.
2727
+ rule2(
2728
+ "callsEntryAt",
2729
+ [v2("r"), v2("site")],
2730
+ [lit2("call", v2("r"), v2("site")), lit2("entryRead", v2("site"), v2("o"))]
2731
+ ),
2732
+ rule2(
2733
+ "callsEntryAt",
2734
+ [v2("r"), v2("site")],
2735
+ [
2736
+ lit2("call", v2("r"), v2("c")),
2737
+ lit2("reaches", v2("c"), v2("site"), VALUE_STEP),
2738
+ lit2("entryRead", v2("site"), v2("o"))
2739
+ ]
2740
+ ),
2741
+ rule2("callsEntryAt", [v2("r"), v2("r")], [lit2("callsChosenEntry", v2("r"))]),
2742
+ rule2(
2743
+ "callsEntryAt",
2744
+ [v2("r"), v2("site")],
2745
+ [lit2("callsEntryInBranch", v2("r"), v2("site"))]
2746
+ ),
2747
+ // `c ? T[k] : undefined` is one of its branches, so a call through it
2748
+ // may run what the read gives. The other branch may be some other
2749
+ // function, so dispatch.ts never counts such a call as certain.
2750
+ rule2(
2751
+ "callsEntryInBranch",
2752
+ [v2("r"), v2("site")],
2753
+ [lit2("call", v2("r"), v2("c")), lit2("branchReadsEntry", v2("c"), v2("site"))]
2754
+ ),
2755
+ rule2(
2756
+ "callsEntryInBranch",
2757
+ [v2("r"), v2("site")],
2758
+ [
2759
+ lit2("call", v2("r"), v2("c")),
2760
+ lit2("reaches", v2("c"), v2("x"), VALUE_STEP),
2761
+ lit2("branchReadsEntry", v2("x"), v2("site"))
2762
+ ]
2763
+ ),
2764
+ rule2(
2765
+ "branchReadsEntry",
2766
+ [v2("x"), v2("site")],
2767
+ [
2768
+ lit2("conditionalBranch", v2("x"), v2("site")),
2769
+ lit2("entryRead", v2("site"), v2("o"))
2770
+ ]
2771
+ ),
2772
+ rule2(
2773
+ "branchReadsEntry",
2774
+ [v2("x"), v2("site")],
2775
+ [
2776
+ lit2("conditionalBranch", v2("x"), v2("b")),
2777
+ lit2("reaches", v2("b"), v2("site"), VALUE_STEP),
2778
+ lit2("entryRead", v2("site"), v2("o"))
2779
+ ]
2780
+ ),
2781
+ rule2(
2782
+ "dispatchesTo",
2783
+ [v2("r"), v2("f")],
2784
+ [
2785
+ lit2("callsEntryAt", v2("r"), v2("site")),
2786
+ lit2("entryAt", v2("site"), v2("held")),
2787
+ lit2("comesTo", v2("held"), v2("f")),
2788
+ lit2("func", v2("f"))
2789
+ ]
2790
+ ),
2791
+ // A collection a program fills is an object the way a written one is,
2792
+ // so a write through a name for it lands on it.
2793
+ rule2("refersToObject", [v2("c"), v2("c")], [lit2("collectionValue", v2("c"))])
2794
+ ];
2346
2795
  var STATED_RULES = [
2347
2796
  // Aliasing: const x = y, or an identifier referencing a declaration.
2348
2797
  // Every chain that follows a name takes the name hops, so a hop added
@@ -2845,19 +3294,29 @@ var STATED_RULES = [
2845
3294
  ],
2846
3295
  "construction enters its constructor"
2847
3296
  ),
3297
+ // When methodCallUnder and objectOfUnder bind as many columns, as they do
3298
+ // once contains has run, the rewrite joins the one written first. This
3299
+ // order reads the receiver under the call's own sites, not every site.
2848
3300
  rule2(
2849
3301
  "entersUnder",
2850
3302
  [v2("r"), v2("f"), v2("site"), v2("caller")],
2851
3303
  [
2852
- lit2("call", v2("r"), v2("cal")),
2853
- lit2("readsProperty", v2("cal"), v2("o"), v2("m")),
3304
+ lit2("methodCallUnder", v2("r"), v2("o"), v2("m"), v2("caller")),
2854
3305
  lit2("objectOfUnder", v2("o"), v2("caller"), v2("site")),
2855
3306
  lit2("allocates", v2("site"), v2("cls")),
2856
- lit2("contains", v2("site"), v2("m"), v2("f")),
2857
- lit2("callUnder", v2("r"), v2("caller"))
3307
+ lit2("contains", v2("site"), v2("m"), v2("f"))
2858
3308
  ],
2859
3309
  "method call enters under its receiver"
2860
3310
  ),
3311
+ rule2(
3312
+ "methodCallUnder",
3313
+ [v2("r"), v2("o"), v2("m"), v2("caller")],
3314
+ [
3315
+ lit2("call", v2("r"), v2("cal")),
3316
+ lit2("readsProperty", v2("cal"), v2("o"), v2("m")),
3317
+ lit2("callUnder", v2("r"), v2("caller"))
3318
+ ]
3319
+ ),
2861
3320
  // A call written as a name runs with whatever receiver the body
2862
3321
  // around it has, so a plain function called from a method keeps the
2863
3322
  // site rather than taking every caller of it.
@@ -3145,13 +3604,15 @@ var STATED_RULES = [
3145
3604
  ),
3146
3605
  // An argument arriving at the parameter it is passed to, by position
3147
3606
  // or by the name the caller wrote, keeping the call it went through
3148
- // so a caller can tell two call sites apart.
3607
+ // so a caller can tell two call sites apart. The callee comes before the
3608
+ // parameter, so a walk that starts from the argument asks which function
3609
+ // its call runs instead of trying every function with a parameter there.
3149
3610
  rule2(
3150
3611
  "passesArgument",
3151
3612
  [v2("r"), v2("p"), v2("a")],
3152
3613
  [
3153
- lit2("paramOf", v2("f"), v2("k"), v2("p")),
3154
3614
  lit2("callsFunction", v2("r"), v2("f")),
3615
+ lit2("paramOf", v2("f"), v2("k"), v2("p")),
3155
3616
  lit2("callArg", v2("r"), v2("k"), v2("a"))
3156
3617
  ]
3157
3618
  ),
@@ -3159,8 +3620,8 @@ var STATED_RULES = [
3159
3620
  "passesArgument",
3160
3621
  [v2("r"), v2("p"), v2("a")],
3161
3622
  [
3162
- lit2("paramNamed", v2("f"), v2("n"), v2("p")),
3163
3623
  lit2("callsFunction", v2("r"), v2("f")),
3624
+ lit2("paramNamed", v2("f"), v2("n"), v2("p")),
3164
3625
  lit2("callKeywordArg", v2("r"), v2("n"), v2("a"))
3165
3626
  ]
3166
3627
  ),
@@ -3433,6 +3894,17 @@ var STATED_RULES = [
3433
3894
  lit2("callsNamed", v2("r"), v2("r0"))
3434
3895
  ]
3435
3896
  ),
3897
+ // The same, called straight off the factory's call, as
3898
+ // `register("created")(handler)` or a Python decorator written that way.
3899
+ rule2(
3900
+ "callsFunction",
3901
+ [v2("r"), v2("f")],
3902
+ [
3903
+ lit2("returnsValue", v2("g"), v2("f")),
3904
+ lit2("callsFunction", v2("r0"), v2("g")),
3905
+ lit2("call", v2("r"), v2("r0"))
3906
+ ]
3907
+ ),
3436
3908
  // The expressions that refer to an object under another name, through
3437
3909
  // a name hop or an import. Asked from the object, so it visits only
3438
3910
  // them.
@@ -3878,6 +4350,7 @@ var STATED_RULES = [
3878
4350
  [v2("x"), v2("z")],
3879
4351
  [lit2("comesTo", v2("x"), v2("z")), lit2("func", v2("z"))]
3880
4352
  ),
4353
+ ...DISPATCH_RULES,
3881
4354
  ...PASSED_ON_RULES
3882
4355
  ];
3883
4356
  function alsoSteps(rules) {
@@ -4081,18 +4554,112 @@ var RESOLUTION_QUESTIONS = [
4081
4554
  ),
4082
4555
  // The functions whose return becomes the exit code: a call the value
4083
4556
  // that ends up as the code is, or one it reaches. DESIGN.md has the seeds.
4557
+ rule2("exitStart", [v2("x"), v2("x")], [lit2("wantedExitSink", v2("x"))]),
4558
+ // `reaches` takes no step through a call that picks its function from a
4559
+ // table, so the walk starts again from what each function there returns.
4560
+ rule2(
4561
+ "exitStart",
4562
+ [v2("x"), v2("ret")],
4563
+ [
4564
+ lit2("exitReached", v2("x"), v2("y")),
4565
+ lit2("dispatchesTo", v2("y"), v2("f")),
4566
+ lit2("returnsValue", v2("f"), v2("ret"))
4567
+ ]
4568
+ ),
4569
+ rule2("exitReached", [v2("x"), v2("s")], [lit2("exitStart", v2("x"), v2("s"))]),
4570
+ rule2(
4571
+ "exitReached",
4572
+ [v2("x"), v2("y")],
4573
+ [
4574
+ lit2("exitStart", v2("x"), v2("s")),
4575
+ lit2("reaches", v2("s"), v2("y"), v2("kind"))
4576
+ ]
4577
+ ),
4084
4578
  rule2(
4085
4579
  "wantedExitCodeFrom",
4086
4580
  [v2("x"), v2("f")],
4087
- [lit2("wantedExitSink", v2("x")), lit2("invokes", v2("x"), v2("f"))]
4581
+ [lit2("exitReached", v2("x"), v2("y")), lit2("invokes", v2("y"), v2("f"))]
4088
4582
  ),
4089
4583
  rule2(
4090
4584
  "wantedExitCodeFrom",
4091
4585
  [v2("x"), v2("f")],
4586
+ [lit2("exitReached", v2("x"), v2("y")), lit2("dispatchesTo", v2("y"), v2("f"))]
4587
+ ),
4588
+ // The functions a call may run when it takes one from a table, with the
4589
+ // entry each came from, and the entries that come to no function. The
4590
+ // reader in dispatch.ts decides whether the call runs exactly one.
4591
+ rule2(
4592
+ "wantedDispatchSite",
4593
+ [v2("r"), v2("site")],
4594
+ [lit2("wantedDispatch", v2("r")), lit2("callsEntryAt", v2("r"), v2("site"))]
4595
+ ),
4596
+ rule2(
4597
+ "wantedDispatchEntry",
4598
+ [v2("r"), v2("held")],
4599
+ [
4600
+ lit2("wantedDispatch", v2("r")),
4601
+ lit2("callsEntryAt", v2("r"), v2("site")),
4602
+ lit2("entryAt", v2("site"), v2("held"))
4603
+ ]
4604
+ ),
4605
+ rule2(
4606
+ "wantedDispatchInBranch",
4607
+ [v2("r"), v2("site")],
4608
+ [
4609
+ lit2("wantedDispatch", v2("r")),
4610
+ lit2("callsEntryInBranch", v2("r"), v2("site"))
4611
+ ]
4612
+ ),
4613
+ rule2(
4614
+ "wantedDispatchComputed",
4615
+ [v2("r"), v2("held")],
4616
+ [
4617
+ lit2("wantedDispatch", v2("r")),
4618
+ lit2("callsEntryAt", v2("r"), v2("site")),
4619
+ lit2("computedEntryAt", v2("site"), v2("held"))
4620
+ ]
4621
+ ),
4622
+ rule2(
4623
+ "wantedDispatchFunction",
4624
+ [v2("r"), v2("held"), v2("f")],
4625
+ [
4626
+ lit2("wantedDispatch", v2("r")),
4627
+ lit2("callsEntryAt", v2("r"), v2("site")),
4628
+ lit2("entryAt", v2("site"), v2("held")),
4629
+ lit2("comesTo", v2("held"), v2("f")),
4630
+ lit2("func", v2("f"))
4631
+ ]
4632
+ ),
4633
+ rule2(
4634
+ "wantedDispatchLikeEntry",
4635
+ [v2("r"), v2("site"), v2("n"), v2("held")],
4636
+ [
4637
+ lit2("wantedDispatch", v2("r")),
4638
+ lit2("callsEntryAt", v2("r"), v2("site")),
4639
+ lit2("likeEntryAt", v2("site"), v2("n"), v2("held"))
4640
+ ]
4641
+ ),
4642
+ rule2(
4643
+ "wantedDispatchLikeFunction",
4644
+ [v2("r"), v2("held"), v2("f")],
4645
+ [
4646
+ lit2("wantedDispatch", v2("r")),
4647
+ lit2("callsEntryAt", v2("r"), v2("site")),
4648
+ lit2("likeEntryAt", v2("site"), v2("n"), v2("held")),
4649
+ lit2("comesTo", v2("held"), v2("f")),
4650
+ lit2("func", v2("f"))
4651
+ ]
4652
+ ),
4653
+ // What a function there calls in its own body, for an entry written as
4654
+ // a lambda, which no summary describes.
4655
+ rule2(
4656
+ "wantedDispatchThrough",
4657
+ [v2("r"), v2("f"), v2("g")],
4092
4658
  [
4093
- lit2("wantedExitSink", v2("x")),
4094
- lit2("reaches", v2("x"), v2("y"), v2("kind")),
4095
- lit2("invokes", v2("y"), v2("f"))
4659
+ lit2("wantedDispatch", v2("r")),
4660
+ lit2("dispatchesTo", v2("r"), v2("f")),
4661
+ lit2("makesCall", v2("f"), v2("r2")),
4662
+ lit2("invokes", v2("r2"), v2("g"))
4096
4663
  ]
4097
4664
  ),
4098
4665
  // The calls that hand a stream on, so the asker can read the callee's file.
@@ -4526,6 +5093,7 @@ export {
4526
5093
  ASKING_RELATIONS,
4527
5094
  BASE_CLASS_RULE,
4528
5095
  DEFAULT_IMPORT_NAME,
5096
+ DISPATCH_QUESTION,
4529
5097
  DependencyLedger,
4530
5098
  EntryReuse,
4531
5099
  FACT_CONTRACT_CASES,
@@ -4543,18 +5111,23 @@ export {
4543
5111
  RESOLUTION_QUESTIONS,
4544
5112
  RESOLUTION_RULES,
4545
5113
  RESULT_STEP,
5114
+ SPREAD_ENTRY,
4546
5115
  UNDER_ANSWER_RELATIONS,
4547
5116
  UNDER_QUESTION_ROW_BUDGET,
4548
5117
  UNDER_RUN_ROW_BUDGET,
4549
5118
  UNPLACED_STORE_NAME,
4550
5119
  USES_QUESTION,
5120
+ VALUE_ENTRY,
4551
5121
  VALUE_STEP,
4552
5122
  WatchedMap,
5123
+ addEntryRows,
4553
5124
  addPackWords,
5125
+ addTemplateRows,
4554
5126
  agreedMountPrefix,
4555
5127
  allocationSitesOf,
4556
5128
  alsoSteps,
4557
5129
  answersFor,
5130
+ answersFromTestsLeftOut,
4558
5131
  askResolution,
4559
5132
  askResolutionUnder,
4560
5133
  askSources,
@@ -4566,6 +5139,7 @@ export {
4566
5139
  comesToUnder,
4567
5140
  couldBeSettled,
4568
5141
  declaredTypesOf,
5142
+ dispatchOutcomes,
4569
5143
  exitCodeFunctions,
4570
5144
  explainResolutionProof,
4571
5145
  explainResolvedKey,
@@ -4587,6 +5161,7 @@ export {
4587
5161
  packHashes,
4588
5162
  proofRules,
4589
5163
  queryFacts,
5164
+ readDispatchOutcomes,
4590
5165
  readUses,
4591
5166
  recordsToReplay,
4592
5167
  renderExplanation,
@@ -4595,6 +5170,7 @@ export {
4595
5170
  resolvedFunctions,
4596
5171
  settledFunction,
4597
5172
  sourceLeavesOf,
5173
+ spanOfKey,
4598
5174
  startsAtName,
4599
5175
  staysInItsFunction,
4600
5176
  stillValid,