@suss/adapter-ruby 0.27.0 → 0.28.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.cjs CHANGED
@@ -1346,7 +1346,7 @@ function calledMethod(node) {
1346
1346
  }
1347
1347
 
1348
1348
  // src/values/evaluator.ts
1349
- var import_resolution2 = require("@suss/resolution");
1349
+ var import_resolution4 = require("@suss/resolution");
1350
1350
  var import_values2 = require("@suss/values");
1351
1351
 
1352
1352
  // src/facts/resolve.ts
@@ -1366,18 +1366,9 @@ var RUBY_RULES = [
1366
1366
  ]
1367
1367
  )
1368
1368
  ];
1369
- var RESOLUTION_PROGRAM = (0, import_datalog.deriveOnDemand)(
1370
- [...import_resolution.RESOLUTION_RULES, ...RUBY_RULES, ...import_resolution.RESOLUTION_QUESTIONS],
1371
- import_resolution.ANSWER_RELATIONS
1372
- );
1369
+ var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
1373
1370
  function resolveValues(db, keys) {
1374
- if (keys.length === 0) {
1375
- return;
1376
- }
1377
- for (const key of keys) {
1378
- db.add("wanted", [key]);
1379
- }
1380
- (0, import_datalog.evaluate)(db, RESOLUTION_PROGRAM.rules);
1371
+ (0, import_resolution.askResolution)(db, keys, "wanted", RUBY_PROGRAM);
1381
1372
  }
1382
1373
  function resolvedFunctions(db, key) {
1383
1374
  return db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
@@ -1388,27 +1379,262 @@ function writtenValueOf(db, key) {
1388
1379
  }
1389
1380
 
1390
1381
  // src/facts/values.ts
1391
- function nodeId(filePath, node) {
1392
- return `${filePath}:${node.startIndex}-${node.endIndex}`;
1393
- }
1394
- function nameId(filePath, name) {
1395
- return `${filePath}#${name}`;
1396
- }
1382
+ var import_resolution3 = require("@suss/resolution");
1383
+
1384
+ // src/facts/locals.ts
1385
+ var import_resolution2 = require("@suss/resolution");
1386
+ var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
1387
+ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([...OWN_BODY_TYPES, ...BLOCK_TYPES2]);
1388
+ var RUBY_NAME_TYPES = /* @__PURE__ */ new Set([
1389
+ "identifier",
1390
+ "instance_variable"
1391
+ ]);
1392
+ var TARGET_LIST_TYPES = /* @__PURE__ */ new Set([
1393
+ "left_assignment_list",
1394
+ "destructured_left_assignment",
1395
+ "rest_assignment"
1396
+ ]);
1397
+ var WHOLE_VALUE_OPERATORS = /* @__PURE__ */ new Set([
1398
+ "||=",
1399
+ "&&="
1400
+ ]);
1397
1401
  function paramNameOf(param) {
1398
1402
  return param.type === "identifier" ? param : field(param, "name") ?? null;
1399
1403
  }
1400
- function declaredParamNames(method) {
1401
- const params = field(method, "parameters");
1402
- const declared = /* @__PURE__ */ new Set();
1403
- for (const param of params === null ? [] : children2(params)) {
1404
+ function parametersOf(owner) {
1405
+ const params = field(owner, "parameters");
1406
+ return params === null ? [] : bodyStatements(params);
1407
+ }
1408
+ function parameterNames2(owner) {
1409
+ const names = /* @__PURE__ */ new Set();
1410
+ for (const param of parametersOf(owner)) {
1404
1411
  const name = paramNameOf(param);
1405
1412
  if (name !== null) {
1406
- declared.add(name.text);
1413
+ names.add(name.text);
1407
1414
  }
1408
1415
  }
1409
- return declared;
1416
+ return names;
1417
+ }
1418
+ function targetsOf(left) {
1419
+ if (left.type === "identifier" || left.type === "constant") {
1420
+ return [left];
1421
+ }
1422
+ if (TARGET_LIST_TYPES.has(left.type)) {
1423
+ return bodyStatements(left).flatMap(targetsOf);
1424
+ }
1425
+ return [];
1410
1426
  }
1411
- function readKey(filePath, node, enclosing) {
1427
+ function writeOf(target, value, at, fromParameter) {
1428
+ return { name: target.text, target, value, at, fromParameter };
1429
+ }
1430
+ function readAssignment(node) {
1431
+ const left = field(node, "left");
1432
+ const right = field(node, "right");
1433
+ if (left === null || right === null) {
1434
+ return [];
1435
+ }
1436
+ const value = left.type === "left_assignment_list" ? null : right;
1437
+ return targetsOf(left).map((target) => writeOf(target, value, node, false));
1438
+ }
1439
+ function readOperatorAssignment(node) {
1440
+ const left = field(node, "left");
1441
+ const right = field(node, "right");
1442
+ const operator = field(node, "operator");
1443
+ if (left === null || right === null || left.type !== "identifier") {
1444
+ return [];
1445
+ }
1446
+ const whole = operator !== null && WHOLE_VALUE_OPERATORS.has(operator.text);
1447
+ return [writeOf(left, whole ? right : null, node, false)];
1448
+ }
1449
+ function readForVariable(node) {
1450
+ const pattern = field(node, "pattern");
1451
+ if (pattern === null) {
1452
+ return [];
1453
+ }
1454
+ return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
1455
+ }
1456
+ function readBlockParameters(node) {
1457
+ const found = [];
1458
+ for (const param of parametersOf(node)) {
1459
+ const name = paramNameOf(param);
1460
+ if (name !== null) {
1461
+ found.push(writeOf(name, null, param, true));
1462
+ }
1463
+ }
1464
+ return found;
1465
+ }
1466
+ var WRITE_READERS = {
1467
+ assignment: readAssignment,
1468
+ operator_assignment: readOperatorAssignment,
1469
+ for: readForVariable,
1470
+ block: readBlockParameters,
1471
+ do_block: readBlockParameters
1472
+ };
1473
+ function writesUnder(body) {
1474
+ const found = [];
1475
+ const visit = (node) => {
1476
+ for (const child of bodyStatements(node)) {
1477
+ if (OWN_BODY_TYPES.has(child.type)) {
1478
+ continue;
1479
+ }
1480
+ found.push(...WRITE_READERS[child.type]?.(child) ?? []);
1481
+ visit(child);
1482
+ }
1483
+ };
1484
+ visit(body);
1485
+ return found;
1486
+ }
1487
+ var namesByTree = /* @__PURE__ */ new WeakMap();
1488
+ function cacheFor(tree) {
1489
+ const known = namesByTree.get(tree);
1490
+ if (known !== void 0) {
1491
+ return known;
1492
+ }
1493
+ const fresh = { file: null, byMethod: new NodeMap() };
1494
+ namesByTree.set(tree, fresh);
1495
+ return fresh;
1496
+ }
1497
+ function scopeNames(parameters, body) {
1498
+ const locals = new Set(parameters);
1499
+ const blockParams = /* @__PURE__ */ new Set();
1500
+ for (const write of body === null ? [] : writesUnder(body)) {
1501
+ if (write.fromParameter) {
1502
+ blockParams.add(write.name);
1503
+ continue;
1504
+ }
1505
+ locals.add(write.name);
1506
+ }
1507
+ return { locals, blockParams };
1508
+ }
1509
+ function namesOfMethod(method) {
1510
+ const cache = cacheFor(method.tree);
1511
+ const remembered = cache.byMethod.get(method);
1512
+ if (remembered !== void 0) {
1513
+ return remembered;
1514
+ }
1515
+ const names = scopeNames(parameterNames2(method), field(method, "body"));
1516
+ cache.byMethod.set(method, names);
1517
+ return names;
1518
+ }
1519
+ function namesOfFile(tree) {
1520
+ const cache = cacheFor(tree);
1521
+ cache.file ??= scopeNames(/* @__PURE__ */ new Set(), tree.rootNode);
1522
+ return cache.file;
1523
+ }
1524
+ function ownerOfName(node, name, enclosing) {
1525
+ if (node.type === "constant") {
1526
+ return null;
1527
+ }
1528
+ const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
1529
+ if (names.blockParams.has(name)) {
1530
+ const block = blockDeclaring(node, name);
1531
+ if (block !== null) {
1532
+ return block;
1533
+ }
1534
+ }
1535
+ return enclosing !== null && names.locals.has(name) ? enclosing : null;
1536
+ }
1537
+ function blockDeclaring(node, name) {
1538
+ let current = node.parent;
1539
+ while (current !== null) {
1540
+ if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
1541
+ return current;
1542
+ }
1543
+ if (OWN_BODY_TYPES.has(current.type)) {
1544
+ return null;
1545
+ }
1546
+ current = current.parent;
1547
+ }
1548
+ return null;
1549
+ }
1550
+ function parameterWrites(method) {
1551
+ const found = [];
1552
+ for (const param of method === null ? [] : parametersOf(method)) {
1553
+ const name = paramNameOf(param);
1554
+ if (name !== null) {
1555
+ found.push(writeOf(name, name, param, true));
1556
+ }
1557
+ }
1558
+ return found;
1559
+ }
1560
+ function collectWrites(method, body) {
1561
+ const writes = [...parameterWrites(method), ...writesUnder(body)].sort(
1562
+ (left, right) => left.target.startIndex - right.target.startIndex
1563
+ );
1564
+ const targetIds = new Set(writes.map((write) => write.target.id));
1565
+ const groups = /* @__PURE__ */ new Map();
1566
+ for (const write of writes) {
1567
+ const owner = ownerOfName(write.target, write.name, method);
1568
+ const key = `${owner === null ? "" : owner.id} ${write.name}`;
1569
+ const group = groups.get(key);
1570
+ if (group === void 0) {
1571
+ groups.set(key, {
1572
+ owner,
1573
+ name: write.name,
1574
+ writes: [write],
1575
+ ordered: false
1576
+ });
1577
+ continue;
1578
+ }
1579
+ group.writes.push(write);
1580
+ }
1581
+ const rules = nameReads(targetIds);
1582
+ for (const group of groups.values()) {
1583
+ const scope = group.owner === null ? body : field(group.owner, "body");
1584
+ if (scope === null) {
1585
+ continue;
1586
+ }
1587
+ group.ordered = (0, import_resolution2.writesRunInOrder)(
1588
+ scope,
1589
+ group.name,
1590
+ group.writes.map((write) => orderedWrite(write, scope)),
1591
+ rules
1592
+ );
1593
+ }
1594
+ return [...groups.values()];
1595
+ }
1596
+ function orderedWrite(write, scope) {
1597
+ return {
1598
+ at: write.at,
1599
+ direct: !write.fromParameter && write.at.parent?.id === scope.id
1600
+ };
1601
+ }
1602
+ function nameReads(targetIds) {
1603
+ return {
1604
+ nameTypes: RUBY_NAME_TYPES,
1605
+ laterBodies: LATER_BODY_TYPES,
1606
+ childrenOf: bodyStatements,
1607
+ isRead: (node) => isNameRead(node, targetIds)
1608
+ };
1609
+ }
1610
+ function isNameRead(node, targetIds) {
1611
+ if (targetIds.has(node.id)) {
1612
+ return false;
1613
+ }
1614
+ const parent = node.parent;
1615
+ return !(parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null);
1616
+ }
1617
+
1618
+ // src/facts/values.ts
1619
+ function nodeId(filePath, node) {
1620
+ return `${filePath}:${node.startIndex}-${node.endIndex}`;
1621
+ }
1622
+ function nameId(filePath, name) {
1623
+ return `${filePath}#${name}`;
1624
+ }
1625
+ function readThrough(node) {
1626
+ if (node.type !== "parenthesized_statements") {
1627
+ return node;
1628
+ }
1629
+ const inner = node.namedChildren.filter((child) => child !== null);
1630
+ return inner.length === 1 ? readThrough(inner[0]) : node;
1631
+ }
1632
+ function nameKey(filePath, node, enclosing) {
1633
+ const owner = ownerOfName(node, node.text, enclosing);
1634
+ return owner === null ? nameId(filePath, node.text) : `${nodeId(filePath, owner)}#${node.text}`;
1635
+ }
1636
+ function readKey(filePath, written, enclosing) {
1637
+ const node = readThrough(written);
1412
1638
  if (node.type !== "identifier" && node.type !== "constant") {
1413
1639
  return nodeId(filePath, node);
1414
1640
  }
@@ -1416,10 +1642,7 @@ function readKey(filePath, node, enclosing) {
1416
1642
  if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
1417
1643
  return isPropertyRead2(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
1418
1644
  }
1419
- if (enclosing !== null && declaredParamNames(enclosing).has(node.text)) {
1420
- return `${nodeId(filePath, enclosing)}#${node.text}`;
1421
- }
1422
- return nameId(filePath, node.text);
1645
+ return nameKey(filePath, node, enclosing);
1423
1646
  }
1424
1647
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
1425
1648
  "string",
@@ -1448,15 +1671,12 @@ function children2(node) {
1448
1671
  function add(emitter, relation, ...tuple) {
1449
1672
  emitter.db.add(relation, tuple);
1450
1673
  }
1451
- function valueKey(emitter, value) {
1674
+ function valueKey(emitter, written) {
1675
+ const value = readThrough(written);
1452
1676
  if (value.type !== "identifier" && value.type !== "constant") {
1453
1677
  return nodeId(emitter.filePath, value);
1454
1678
  }
1455
- const enclosing = emitter.enclosing;
1456
- if (enclosing !== null && enclosing.params.has(value.text)) {
1457
- return `${enclosing.funcKey}#${value.text}`;
1458
- }
1459
- return nameId(emitter.filePath, value.text);
1679
+ return nameKey(emitter.filePath, value, emitter.enclosing);
1460
1680
  }
1461
1681
  function pairKeyText(key) {
1462
1682
  if (key.type === "hash_key_symbol") {
@@ -1595,8 +1815,72 @@ function emitExpressionFacts(emitter, node) {
1595
1815
  if (child.type === "nil") {
1596
1816
  add(emitter, "placeholderValue", nodeId(emitter.filePath, child));
1597
1817
  }
1818
+ if (child.type === "self" && emitter.selfKey !== null) {
1819
+ add(emitter, "binds", nodeId(emitter.filePath, child), emitter.selfKey);
1820
+ }
1821
+ if (child.type === "instance_variable") {
1822
+ emitInstanceRead(emitter, child);
1823
+ }
1824
+ if (ASSIGNMENT_TYPES.has(child.type)) {
1825
+ collectInstanceWrite(emitter, child);
1826
+ }
1598
1827
  });
1599
1828
  }
1829
+ var ASSIGNMENT_TYPES = /* @__PURE__ */ new Set(["assignment", "operator_assignment"]);
1830
+ function isWriteTarget(node) {
1831
+ const parent = node.parent;
1832
+ return parent !== null && ASSIGNMENT_TYPES.has(parent.type) && field(parent, "left")?.id === node.id;
1833
+ }
1834
+ function emitInstanceRead(emitter, node) {
1835
+ if (emitter.selfKey === null || isWriteTarget(node)) {
1836
+ return;
1837
+ }
1838
+ add(
1839
+ emitter,
1840
+ "readsProperty",
1841
+ nodeId(emitter.filePath, node),
1842
+ emitter.selfKey,
1843
+ node.text
1844
+ );
1845
+ }
1846
+ function collectInstanceWrite(emitter, node) {
1847
+ const left = field(node, "left");
1848
+ const right = field(node, "right");
1849
+ const collector = emitter.instanceWrites;
1850
+ if (left === null || right === null || collector === null) {
1851
+ return;
1852
+ }
1853
+ if (left.type !== "instance_variable") {
1854
+ return;
1855
+ }
1856
+ const operator = field(node, "operator")?.text;
1857
+ const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
1858
+ const written = collector.get(left.text) ?? [];
1859
+ written.push(
1860
+ describeWrite(emitter, {
1861
+ name: left.text,
1862
+ target: left,
1863
+ value,
1864
+ at: node,
1865
+ fromParameter: false
1866
+ })
1867
+ );
1868
+ collector.set(left.text, written);
1869
+ }
1870
+ function emitInstanceWrites(emitter, classKey, collected) {
1871
+ for (const [name, writes] of collected) {
1872
+ const settled = (0, import_resolution3.valueLeftByWrites)(writes, false);
1873
+ if (settled !== null) {
1874
+ add(emitter, "holdsProperty", classKey, name, settled);
1875
+ continue;
1876
+ }
1877
+ for (const write of writes) {
1878
+ if (write.value !== null && !write.narrowsName) {
1879
+ add(emitter, "holdsProperty", classKey, name, write.value);
1880
+ }
1881
+ }
1882
+ }
1883
+ }
1600
1884
  function implicitReturn(body) {
1601
1885
  const statements = children2(body).filter(
1602
1886
  (child) => child.type !== "rescue" && child.type !== "ensure"
@@ -1607,23 +1891,17 @@ function implicitReturn(body) {
1607
1891
  function emitMethodFacts(emitter, method) {
1608
1892
  const funcKey = nodeId(emitter.filePath, method);
1609
1893
  add(emitter, "func", funcKey);
1610
- const params = field(method, "parameters");
1611
- const declared = /* @__PURE__ */ new Set();
1612
1894
  let position = 0;
1613
- for (const param of params === null ? [] : children2(params)) {
1895
+ for (const param of parametersOf(method)) {
1614
1896
  const paramName = paramNameOf(param);
1615
1897
  if (paramName !== null) {
1616
- declared.add(paramName.text);
1617
1898
  const paramKey = `${funcKey}#${paramName.text}`;
1618
1899
  add(emitter, "paramOf", funcKey, String(position), paramKey);
1619
1900
  add(emitter, "paramNamed", funcKey, paramName.text, paramKey);
1620
1901
  }
1621
1902
  position += 1;
1622
1903
  }
1623
- const inside = {
1624
- ...emitter,
1625
- enclosing: { funcKey, params: declared }
1626
- };
1904
+ const inside = { ...emitter, enclosing: method };
1627
1905
  const body = field(method, "body");
1628
1906
  if (body === null) {
1629
1907
  return funcKey;
@@ -1655,31 +1933,78 @@ function emitMethodFacts(emitter, method) {
1655
1933
  add(inside, "returnsValue", funcKey, valueKey(inside, implicit));
1656
1934
  }
1657
1935
  emitExpressionFacts(inside, body);
1936
+ emitScopeWrites(inside, method, body);
1658
1937
  return funcKey;
1659
1938
  }
1660
- function emitAssignment(emitter, assignment2) {
1661
- const left = field(assignment2, "left");
1662
- const right = field(assignment2, "right");
1663
- if (right === null) {
1664
- return;
1939
+ function isConstruction(value) {
1940
+ return value.type === "call" || ARRAY_TYPES.has(value.type) || value.type === "hash";
1941
+ }
1942
+ function sourceOf(node) {
1943
+ return node.text.replace(/\s+/g, " ").trim();
1944
+ }
1945
+ function readFirst(node) {
1946
+ const inner = readThrough(node);
1947
+ if (inner !== node) {
1948
+ return inner;
1665
1949
  }
1666
- if (left?.type !== "identifier" && left?.type !== "constant") {
1667
- return;
1950
+ return node.type === "call" ? field(node, "receiver") : null;
1951
+ }
1952
+ var CHAIN_READS = {
1953
+ nameTypes: RUBY_NAME_TYPES,
1954
+ readFirst
1955
+ };
1956
+ function describeWrite(emitter, write) {
1957
+ const value = write.value === null ? null : readThrough(write.value);
1958
+ return {
1959
+ value: value === null ? null : valueKey(emitter, value),
1960
+ placeholder: value?.type === "nil",
1961
+ construction: value !== null && isConstruction(value) ? sourceOf(value) : null,
1962
+ narrowsName: value !== null && value.type === "call" && (0, import_resolution3.startsAtName)(value, write.name, CHAIN_READS)
1963
+ };
1964
+ }
1965
+ function settledValue(emitter, group) {
1966
+ const only = group.writes.length === 1 ? group.writes[0] : void 0;
1967
+ if (only !== void 0) {
1968
+ return only.fromParameter || only.value === null ? null : valueKey(emitter, only.value);
1668
1969
  }
1669
- add(
1670
- emitter,
1671
- "binds",
1672
- nameId(emitter.filePath, left.text),
1673
- valueKey(emitter, right)
1674
- );
1675
- add(
1676
- emitter,
1677
- "exportsAs",
1678
- emitter.filePath,
1679
- left.text,
1680
- valueKey(emitter, right)
1970
+ return (0, import_resolution3.valueLeftByWrites)(
1971
+ group.writes.map((write) => describeWrite(emitter, write)),
1972
+ group.ordered
1681
1973
  );
1682
1974
  }
1975
+ function emitScopeWrites(emitter, method, body) {
1976
+ for (const group of collectWrites(method, body)) {
1977
+ const settled = settledValue(emitter, group);
1978
+ const key = group.owner === null ? nameId(emitter.filePath, group.name) : `${nodeId(emitter.filePath, group.owner)}#${group.name}`;
1979
+ if (settled === null) {
1980
+ emitCandidates(emitter, key, group);
1981
+ continue;
1982
+ }
1983
+ add(
1984
+ emitter,
1985
+ group.writes.length === 1 ? "binds" : "endsHolding",
1986
+ key,
1987
+ settled
1988
+ );
1989
+ if (group.owner === null) {
1990
+ add(emitter, "exportsAs", emitter.filePath, group.name, settled);
1991
+ }
1992
+ }
1993
+ }
1994
+ function emitCandidates(emitter, key, group) {
1995
+ for (const write of group.writes) {
1996
+ if (write.value === null) {
1997
+ add(emitter, "writesUnstated", key);
1998
+ continue;
1999
+ }
2000
+ if (write.fromParameter) {
2001
+ continue;
2002
+ }
2003
+ if (!describeWrite(emitter, write).narrowsName) {
2004
+ add(emitter, "mayHold", key, valueKey(emitter, write.value));
2005
+ }
2006
+ }
2007
+ }
1683
2008
  var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1684
2009
  function emitClassFacts(emitter, cls) {
1685
2010
  const classKey = nodeId(emitter.filePath, cls);
@@ -1691,6 +2016,12 @@ function emitClassFacts(emitter, cls) {
1691
2016
  add(emitter, "extendsNamed", classKey, base.text);
1692
2017
  }
1693
2018
  const body = field(cls, "body");
2019
+ const collected = /* @__PURE__ */ new Map();
2020
+ const within = {
2021
+ ...emitter,
2022
+ selfKey: classKey,
2023
+ instanceWrites: collected
2024
+ };
1694
2025
  for (const statement of body === null ? [] : children2(body)) {
1695
2026
  if (statement.type === "assignment") {
1696
2027
  const left = field(statement, "left");
@@ -1714,16 +2045,23 @@ function emitClassFacts(emitter, cls) {
1714
2045
  if (!METHOD_TYPES.has(statement.type)) {
1715
2046
  continue;
1716
2047
  }
1717
- const funcKey = emitMethodFacts(emitter, statement);
2048
+ const funcKey = emitMethodFacts(within, statement);
1718
2049
  const name = field(statement, "name");
1719
2050
  if (name !== null) {
1720
2051
  add(emitter, "holdsProperty", classKey, name.text, funcKey);
1721
2052
  }
1722
2053
  }
2054
+ emitInstanceWrites(within, classKey, collected);
1723
2055
  return classKey;
1724
2056
  }
1725
2057
  function emitValueFacts(db, filePath, root) {
1726
- const emitter = { db, filePath, enclosing: null };
2058
+ const emitter = {
2059
+ db,
2060
+ filePath,
2061
+ enclosing: null,
2062
+ selfKey: null,
2063
+ instanceWrites: null
2064
+ };
1727
2065
  const declaresName = (child, key) => {
1728
2066
  const name = field(child, "name");
1729
2067
  if (name === null) {
@@ -1741,14 +2079,12 @@ function emitValueFacts(db, filePath, root) {
1741
2079
  if (METHOD_TYPES.has(child.type)) {
1742
2080
  declaresName(child, emitMethodFacts(emitter, child));
1743
2081
  }
1744
- if (child.type === "assignment") {
1745
- emitAssignment(emitter, child);
1746
- }
1747
2082
  walk(child);
1748
2083
  }
1749
2084
  };
1750
2085
  walk(root);
1751
2086
  emitExpressionFacts(emitter, root);
2087
+ emitScopeWrites(emitter, null, root);
1752
2088
  }
1753
2089
 
1754
2090
  // src/values/lowering.ts
@@ -1764,7 +2100,7 @@ var OWN_ROOT_TYPES = /* @__PURE__ */ new Set([
1764
2100
  "module",
1765
2101
  "singleton_class"
1766
2102
  ]);
1767
- var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
2103
+ var BLOCK_TYPES3 = /* @__PURE__ */ new Set(["block", "do_block"]);
1768
2104
  var METHOD_TYPES2 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1769
2105
  var NAME_TYPES = /* @__PURE__ */ new Set([
1770
2106
  "identifier",
@@ -2270,7 +2606,7 @@ function isTailExpression(node) {
2270
2606
  if (owner.type === "block" && owner.parent?.type === "lambda") {
2271
2607
  return true;
2272
2608
  }
2273
- if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES2.has(owner.type)) {
2609
+ if (METHOD_TYPES2.has(owner.type) || BLOCK_TYPES3.has(owner.type)) {
2274
2610
  return true;
2275
2611
  }
2276
2612
  if (owner.type === "when") {
@@ -2289,7 +2625,7 @@ function isRoot(node) {
2289
2625
  if (OWN_ROOT_TYPES.has(node.type)) {
2290
2626
  return true;
2291
2627
  }
2292
- return BLOCK_TYPES2.has(node.type) && node.parent?.type === "call";
2628
+ return BLOCK_TYPES3.has(node.type) && node.parent?.type === "call";
2293
2629
  }
2294
2630
  function isPathStatement(node, parent) {
2295
2631
  if (node.type === "elsif") {
@@ -2317,7 +2653,7 @@ function siteOf(node) {
2317
2653
  current = parent;
2318
2654
  }
2319
2655
  }
2320
- function parametersOf(parameters) {
2656
+ function parametersOf2(parameters) {
2321
2657
  if (parameters === null) {
2322
2658
  return [];
2323
2659
  }
@@ -2339,19 +2675,19 @@ function functionOf(node) {
2339
2675
  return null;
2340
2676
  }
2341
2677
  return {
2342
- parameters: parametersOf(field(node, "parameters")),
2678
+ parameters: parametersOf2(field(node, "parameters")),
2343
2679
  body: bodyOf(block)
2344
2680
  };
2345
2681
  }
2346
- if (BLOCK_TYPES2.has(node.type)) {
2682
+ if (BLOCK_TYPES3.has(node.type)) {
2347
2683
  return {
2348
- parameters: parametersOf(field(node, "parameters")),
2684
+ parameters: parametersOf2(field(node, "parameters")),
2349
2685
  body: bodyOf(node)
2350
2686
  };
2351
2687
  }
2352
2688
  if (METHOD_TYPES2.has(node.type)) {
2353
2689
  const body = field(node, "body");
2354
- const parameters = parametersOf(field(node, "parameters"));
2690
+ const parameters = parametersOf2(field(node, "parameters"));
2355
2691
  if (body === null) {
2356
2692
  return { parameters, body: [] };
2357
2693
  }
@@ -2763,7 +3099,7 @@ function contextOver(db, nodes) {
2763
3099
  db,
2764
3100
  readKey(file, node, enclosingMethod(node))
2765
3101
  );
2766
- return answer === null ? null : (0, import_resolution2.nodeOfKey)(rootsByFile, answer);
3102
+ return answer === null ? null : (0, import_resolution4.nodeOfKey)(rootsByFile, answer);
2767
3103
  },
2768
3104
  callable: (call) => {
2769
3105
  const file = fileOf(call);
@@ -3546,9 +3882,8 @@ function namesUnder(node) {
3546
3882
  }
3547
3883
 
3548
3884
  // src/storage.ts
3549
- var import_datalog2 = require("@suss/datalog");
3550
3885
  var import_ir_core = require("@suss/ir-core");
3551
- var import_resolution3 = require("@suss/resolution");
3886
+ var import_resolution5 = require("@suss/resolution");
3552
3887
  function children3(node) {
3553
3888
  return node.namedChildren.filter((child) => child !== null);
3554
3889
  }
@@ -3574,13 +3909,8 @@ function isConstant(node) {
3574
3909
  function constantName(constant2) {
3575
3910
  return constant2.type === "constant" ? constant2.text : compoundName(constant2);
3576
3911
  }
3577
- var ANCESTRY_PROGRAM = (0, import_datalog2.deriveOnDemand)(
3578
- [...import_resolution3.RESOLUTION_RULES, ...import_resolution3.RESOLUTION_QUESTIONS],
3579
- import_resolution3.ANSWER_RELATIONS
3580
- );
3581
3912
  function reachesBase(facts, classKey, bases) {
3582
- facts.add("wantedAncestry", [classKey]);
3583
- (0, import_datalog2.evaluate)(facts, ANCESTRY_PROGRAM.rules);
3913
+ (0, import_resolution5.askResolution)(facts, [classKey], "wantedAncestry", RUBY_PROGRAM);
3584
3914
  return facts.lookup("wantedBaseName", 0, classKey).some((row) => bases.includes(String(row[1])));
3585
3915
  }
3586
3916
  function classBehind(facts, file, constant2) {
@@ -4518,7 +4848,8 @@ function collectFileConstants(filePath, root) {
4518
4848
  if (written !== null) {
4519
4849
  definitions.push({
4520
4850
  qualifiedName: qualify(nesting, written),
4521
- key: nodeId(filePath, node)
4851
+ key: nodeId(filePath, node),
4852
+ kind: "declaration"
4522
4853
  });
4523
4854
  const inside = [...nesting, ...written.split("::")];
4524
4855
  for (const child of children4(node)) {
@@ -4533,7 +4864,8 @@ function collectFileConstants(filePath, root) {
4533
4864
  if (left !== null && left.type === "constant" && right !== null) {
4534
4865
  definitions.push({
4535
4866
  qualifiedName: qualify(nesting, left.text),
4536
- key: nodeId(filePath, right)
4867
+ key: nodeId(filePath, right),
4868
+ kind: "assignment"
4537
4869
  });
4538
4870
  }
4539
4871
  }
@@ -4560,14 +4892,18 @@ function emitConstantBindings(db, perFile) {
4560
4892
  for (const file of files) {
4561
4893
  for (const definition of file.definitions) {
4562
4894
  const found = byName.get(definition.qualifiedName) ?? [];
4563
- found.push(definition.key);
4895
+ found.push(definition);
4564
4896
  byName.set(definition.qualifiedName, found);
4565
4897
  fileOfDefinition.set(definition.key, file.filePath);
4566
4898
  }
4567
4899
  }
4568
4900
  const settles = (candidate) => {
4569
- const found = byName.get(candidate);
4570
- return found !== void 0 && found.length === 1 ? found[0] ?? null : null;
4901
+ const found = byName.get(candidate) ?? [];
4902
+ const declared = found.filter((one) => one.kind === "declaration");
4903
+ if (declared.length > 0) {
4904
+ return declared[0].key;
4905
+ }
4906
+ return found.length === 1 ? found[0].key : null;
4571
4907
  };
4572
4908
  for (const file of files) {
4573
4909
  for (const reference of file.references) {
@@ -4593,8 +4929,8 @@ function emitConstantBindings(db, perFile) {
4593
4929
  // src/facts/why.ts
4594
4930
  var import_node_fs4 = __toESM(require("fs"), 1);
4595
4931
  var import_node_path5 = __toESM(require("path"), 1);
4596
- var import_datalog5 = require("@suss/datalog");
4597
- var import_resolution4 = require("@suss/resolution");
4932
+ var import_datalog4 = require("@suss/datalog");
4933
+ var import_resolution7 = require("@suss/resolution");
4598
4934
 
4599
4935
  // src/parser.ts
4600
4936
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -4656,7 +4992,7 @@ function parseWith(language, source) {
4656
4992
  var import_node_fs3 = __toESM(require("fs"), 1);
4657
4993
  var import_node_path4 = __toESM(require("path"), 1);
4658
4994
  var import_behavioral_ir5 = require("@suss/behavioral-ir");
4659
- var import_datalog4 = require("@suss/datalog");
4995
+ var import_datalog3 = require("@suss/datalog");
4660
4996
  var import_extractor10 = require("@suss/extractor");
4661
4997
 
4662
4998
  // src/diagnostics.ts
@@ -4711,34 +5047,118 @@ function requiredRelativePath(node) {
4711
5047
 
4712
5048
  // src/reach/closure.ts
4713
5049
  var import_behavioral_ir4 = require("@suss/behavioral-ir");
4714
- var import_datalog3 = require("@suss/datalog");
5050
+ var import_datalog2 = require("@suss/datalog");
4715
5051
  var import_extractor8 = require("@suss/extractor");
4716
5052
 
4717
5053
  // src/reach/resolveCallee.ts
5054
+ var import_resolution6 = require("@suss/resolution");
4718
5055
  var DYNAMIC_SEND_NAMES = /* @__PURE__ */ new Set(["send", "public_send", "__send__"]);
5056
+ var INVOKES_RECEIVER = "call";
5057
+ var STOP_FOR = {
5058
+ severalSources: "multipleSources",
5059
+ outsideRun: "outsideRun",
5060
+ unsettled: "unsettledValue",
5061
+ undeclared: "noDeclaration"
5062
+ };
4719
5063
  var stop = (reason) => ({
4720
5064
  kind: "stopped",
4721
5065
  reason
4722
5066
  });
5067
+ var NO_DECLARATION = stop("noDeclaration");
4723
5068
  function followed(target) {
4724
5069
  return { kind: "followed", target };
4725
5070
  }
4726
- async function resolveCallee(call, site, ctx) {
5071
+ function calleeSpellings(calls, ctx) {
5072
+ const spellingOf = /* @__PURE__ */ new Map();
5073
+ const keys = /* @__PURE__ */ new Set();
5074
+ for (const { call, site } of calls) {
5075
+ const spelling = spellingFor(call, site);
5076
+ spellingOf.set(call.id, spelling);
5077
+ if (spelling.kind === "receiver") {
5078
+ keys.add(spelling.key);
5079
+ }
5080
+ }
5081
+ return {
5082
+ spellingOf,
5083
+ outcomes: (0, import_resolution6.calleeOutcomes)(ctx.facts, [...keys], RUBY_PROGRAM)
5084
+ };
5085
+ }
5086
+ async function resolveCallee(call, site, ctx, read) {
5087
+ const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site);
5088
+ if (spelling.kind === "stopped") {
5089
+ return spelling;
5090
+ }
5091
+ if (spelling.kind === "implicitSelf") {
5092
+ return resolveImplicitSelf(spelling.name, site, ctx);
5093
+ }
5094
+ const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution6.calleeOutcomeOf)(ctx.facts, spelling.key, RUBY_PROGRAM);
5095
+ return asCallee(spelling, outcome, site, ctx);
5096
+ }
5097
+ function spellingFor(call, site) {
4727
5098
  const methodName = calleeMethodName(call);
4728
5099
  if (methodName === void 0) {
4729
- return stop("noDeclaration");
5100
+ return { kind: "stopped", reason: "noDeclaration" };
4730
5101
  }
4731
5102
  if (DYNAMIC_SEND_NAMES.has(methodName)) {
4732
- return stop("unsettledValue");
5103
+ return { kind: "stopped", reason: "unsettledValue" };
4733
5104
  }
4734
5105
  const receiver = field(call, "receiver");
4735
5106
  if (receiver === null || receiver.type === "self") {
4736
- return resolveImplicitSelf(methodName, site, ctx);
5107
+ return { kind: "implicitSelf", name: methodName };
5108
+ }
5109
+ return {
5110
+ kind: "receiver",
5111
+ key: readKey(site.file, receiver, site.method),
5112
+ method: methodName,
5113
+ onClassItself: receiver.type === "constant" || receiver.type === "scope_resolution"
5114
+ };
5115
+ }
5116
+ async function asCallee(spelling, outcome, site, ctx) {
5117
+ if (outcome.kind === "severalSources") {
5118
+ return stop("multipleSources");
5119
+ }
5120
+ if (outcome.kind === "callerSupplied") {
5121
+ return fromCaller(spelling, outcome.key, site);
5122
+ }
5123
+ const objects = objectsBehind(ctx.facts, spelling.key);
5124
+ if (objects.length > 1) {
5125
+ return stop("multipleSources");
4737
5126
  }
4738
- if (receiver.type === "identifier" && methodName === "call" && site.ownParameters.includes(receiver.text)) {
4739
- return stop("callerSupplied");
5127
+ const objectKey = objects[0] ?? (outcome.kind === "object" ? outcome.key : void 0);
5128
+ if (objectKey !== void 0) {
5129
+ return methodOnObject(spelling, objectKey, ctx);
4740
5130
  }
4741
- return resolveOnReceiver(receiver, methodName, site, ctx);
5131
+ if (outcome.kind === "function") {
5132
+ return spelling.method === INVOKES_RECEIVER ? functionCallee(outcome.key, ctx) : NO_DECLARATION;
5133
+ }
5134
+ return stop(STOP_FOR[outcome.kind] ?? "noDeclaration");
5135
+ }
5136
+ function objectsBehind(facts, key) {
5137
+ return [
5138
+ ...new Set(
5139
+ facts.lookup("wantedObjectOf", 0, key).map((row) => String(row[1]))
5140
+ )
5141
+ ];
5142
+ }
5143
+ async function methodOnObject(spelling, objectKey, ctx) {
5144
+ const qualifiedName = ctx.classNames.get(objectKey);
5145
+ if (qualifiedName === void 0) {
5146
+ return NO_DECLARATION;
5147
+ }
5148
+ if (!spelling.onClassItself) {
5149
+ return methodOnAncestryOf(qualifiedName, spelling.method, ctx);
5150
+ }
5151
+ return spelling.method === "new" ? methodOnAncestryOf(qualifiedName, "initialize", ctx) : singletonMethodOn(qualifiedName, spelling.method, ctx);
5152
+ }
5153
+ function fromCaller(spelling, parameterKey, site) {
5154
+ if (!parameterKey.startsWith(`${site.owner}#`) || spelling.method !== INVOKES_RECEIVER) {
5155
+ return stop("unsettledValue");
5156
+ }
5157
+ return stop("callerSupplied");
5158
+ }
5159
+ function functionCallee(key, ctx) {
5160
+ const target = ctx.definitions.get(key);
5161
+ return target === void 0 ? NO_DECLARATION : followed(target);
4742
5162
  }
4743
5163
  async function resolveMethodReference(name, site, ctx) {
4744
5164
  const resolved = await resolveImplicitSelf(name, site, ctx);
@@ -4763,58 +5183,13 @@ async function resolveImplicitSelf(methodName, site, ctx) {
4763
5183
  function resolveTopLevelName(methodName, ctx) {
4764
5184
  const candidates = ctx.topLevelMethods.get(methodName);
4765
5185
  if (candidates === void 0 || candidates.length === 0) {
4766
- return stop("noDeclaration");
5186
+ return NO_DECLARATION;
4767
5187
  }
4768
5188
  if (candidates.length > 1) {
4769
5189
  return stop("multipleSources");
4770
5190
  }
4771
5191
  return followed(candidates[0]);
4772
5192
  }
4773
- async function resolveOnReceiver(receiver, methodName, site, ctx) {
4774
- if (receiver.type === "call") {
4775
- const innerName = calleeMethodName(receiver);
4776
- const innerReceiver = field(receiver, "receiver");
4777
- if (innerName === "new" && innerReceiver !== null) {
4778
- const classRef = classRefOf(innerReceiver, site, ctx);
4779
- return classRef === null ? stop("unsettledValue") : methodOnAncestryOf(classRef, methodName, ctx);
4780
- }
4781
- const root = rootConstantOf(receiver);
4782
- if (root === null) {
4783
- return stop("unsettledValue");
4784
- }
4785
- return classRefOf(root, site, ctx) === null ? stop("outsideRun") : stop("unsettledValue");
4786
- }
4787
- if (receiver.type === "constant" || receiver.type === "scope_resolution") {
4788
- const classRef = classRefOf(receiver, site, ctx);
4789
- return classRef === null ? stop("outsideRun") : singletonMethodOn(classRef, methodName, ctx);
4790
- }
4791
- return stop("unsettledValue");
4792
- }
4793
- function rootConstantOf(node) {
4794
- if (node.type === "constant" || node.type === "scope_resolution") {
4795
- return node;
4796
- }
4797
- if (node.type !== "call") {
4798
- return null;
4799
- }
4800
- const receiver = field(node, "receiver");
4801
- return receiver === null ? null : rootConstantOf(receiver);
4802
- }
4803
- function classRefOf(node, site, ctx) {
4804
- if (node.type === "constant") {
4805
- const shadow = shadowingClassFor(node, site.nesting, ctx.knownClasses);
4806
- if (shadow !== null) {
4807
- return shadow;
4808
- }
4809
- const qualified = qualifyConstantRef(node, site.nesting);
4810
- return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
4811
- }
4812
- if (node.type === "scope_resolution") {
4813
- const qualified = qualifyConstantRef(node, site.nesting);
4814
- return qualified !== null && ctx.knownClasses.has(qualified) ? qualified : null;
4815
- }
4816
- return null;
4817
- }
4818
5193
  async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
4819
5194
  const ownBlocks = ctx.lookup.localDefinition?.(qualifiedName) ?? null;
4820
5195
  if (ownBlocks === null) {
@@ -4830,7 +5205,7 @@ async function methodOnAncestryOf(qualifiedName, methodName, ctx) {
4830
5205
  found.cause === "dynamicDefine" ? "unsettledValue" : "outsideRun"
4831
5206
  );
4832
5207
  }
4833
- return stop("noDeclaration");
5208
+ return NO_DECLARATION;
4834
5209
  }
4835
5210
  function reachedMethod(node, block, name) {
4836
5211
  return {
@@ -4855,21 +5230,21 @@ function singletonMethodOn(qualifiedName, methodName, ctx) {
4855
5230
  return followed(reachedMethod(found, block, methodName));
4856
5231
  }
4857
5232
  }
4858
- return stop("noDeclaration");
5233
+ return NO_DECLARATION;
4859
5234
  }
4860
5235
 
4861
5236
  // src/reach/closure.ts
4862
5237
  var REACHABLE_RULES = [
4863
- (0, import_datalog3.rule)("reachable", [(0, import_datalog3.variable)("f")], [(0, import_datalog3.lit)("entry", (0, import_datalog3.variable)("f"))]),
4864
- (0, import_datalog3.rule)(
5238
+ (0, import_datalog2.rule)("reachable", [(0, import_datalog2.variable)("f")], [(0, import_datalog2.lit)("entry", (0, import_datalog2.variable)("f"))]),
5239
+ (0, import_datalog2.rule)(
4865
5240
  "reachable",
4866
- [(0, import_datalog3.variable)("g")],
4867
- [(0, import_datalog3.lit)("reachable", (0, import_datalog3.variable)("f")), (0, import_datalog3.lit)("calls", (0, import_datalog3.variable)("f"), (0, import_datalog3.variable)("g"))]
5241
+ [(0, import_datalog2.variable)("g")],
5242
+ [(0, import_datalog2.lit)("reachable", (0, import_datalog2.variable)("f")), (0, import_datalog2.lit)("calls", (0, import_datalog2.variable)("f"), (0, import_datalog2.variable)("g"))]
4868
5243
  )
4869
5244
  ];
4870
5245
  async function reachedFunctions(seeds, options) {
4871
- const ctx = buildReachContext(options.files);
4872
- const db = new import_datalog3.Database();
5246
+ const ctx = buildReachContext(options.files, options.facts);
5247
+ const db = new import_datalog2.Database();
4873
5248
  const functionByKey = /* @__PURE__ */ new Map();
4874
5249
  const seedKeys = /* @__PURE__ */ new Set();
4875
5250
  const scanned = /* @__PURE__ */ new Set();
@@ -4890,18 +5265,26 @@ async function reachedFunctions(seeds, options) {
4890
5265
  db.add("entry", [seed.key]);
4891
5266
  }
4892
5267
  for (; ; ) {
4893
- (0, import_datalog3.evaluate)(db, REACHABLE_RULES);
5268
+ (0, import_datalog2.evaluate)(db, REACHABLE_RULES);
4894
5269
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
4895
5270
  if (frontier.length === 0) {
4896
5271
  break;
4897
5272
  }
4898
- for (const key of frontier) {
5273
+ const bodies = frontier.flatMap((key) => {
4899
5274
  scanned.add(key);
4900
5275
  const source = functionByKey.get(key);
4901
- if (source === void 0) {
4902
- continue;
4903
- }
4904
- const scan = await scanBody(source, ctx, options);
5276
+ return source === void 0 ? [] : [{ key, source, ...bodyOf2(source, options) }];
5277
+ });
5278
+ const spellings = calleeSpellings(
5279
+ bodies.flatMap((body) => body.written),
5280
+ ctx
5281
+ );
5282
+ for (const { key, source, calls, site } of bodies) {
5283
+ const scan = await scanBody(source, ctx, options, {
5284
+ calls,
5285
+ site,
5286
+ spellings
5287
+ });
4905
5288
  if (scan.stops.length > 0) {
4906
5289
  stopsByKey.set(key, scan.stops);
4907
5290
  }
@@ -4954,11 +5337,22 @@ async function reachedFunctions(seeds, options) {
4954
5337
  function keyOf(target) {
4955
5338
  return nodeId(target.file, target.node);
4956
5339
  }
4957
- function buildReachContext(files) {
5340
+ var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
5341
+ function buildReachContext(files, facts) {
4958
5342
  const blocksByQualifiedName = /* @__PURE__ */ new Map();
4959
5343
  const classes = [];
5344
+ const classNames = /* @__PURE__ */ new Map();
5345
+ const classOfMethod = /* @__PURE__ */ new Map();
4960
5346
  for (const { file, root } of files) {
4961
- walkDefinitions(root, (info) => classes.push({ file, info }));
5347
+ walkDefinitions(root, (info) => {
5348
+ classes.push({ file, info });
5349
+ classNames.set(nodeId(file, info.node), info.qualifiedName);
5350
+ for (const statement of namedChildren(info.bodyNode)) {
5351
+ if (METHOD_TYPES4.has(statement.type)) {
5352
+ classOfMethod.set(nodeId(file, statement), info.qualifiedName);
5353
+ }
5354
+ }
5355
+ });
4962
5356
  }
4963
5357
  const knownClasses = new Set(classes.map(({ info }) => info.qualifiedName));
4964
5358
  for (const { file, info } of classes) {
@@ -4966,22 +5360,33 @@ function buildReachContext(files) {
4966
5360
  list.push({ info, knownClasses, file });
4967
5361
  blocksByQualifiedName.set(info.qualifiedName, list);
4968
5362
  }
4969
- const topLevelMethods = /* @__PURE__ */ new Map();
5363
+ const definitions = /* @__PURE__ */ new Map();
4970
5364
  for (const { file, root } of files) {
4971
- for (const method of topLevelMethodNodes(root)) {
5365
+ for (const [key, method] of methodDefinitionsIn(file, root)) {
4972
5366
  const name = field(method, "name")?.text;
4973
5367
  if (name === void 0) {
4974
5368
  continue;
4975
5369
  }
4976
- const list = topLevelMethods.get(name) ?? [];
4977
- list.push({
5370
+ const owner = classOfMethod.get(key) ?? null;
5371
+ definitions.set(key, {
4978
5372
  file,
4979
5373
  node: method,
4980
5374
  name,
4981
- exportPath: [name],
4982
- enclosingQualifiedName: null
5375
+ exportPath: owner === null ? [name] : [owner, name],
5376
+ enclosingQualifiedName: owner
4983
5377
  });
4984
- topLevelMethods.set(name, list);
5378
+ }
5379
+ }
5380
+ const topLevelMethods = /* @__PURE__ */ new Map();
5381
+ for (const { file, root } of files) {
5382
+ for (const method of topLevelMethodNodes(root)) {
5383
+ const found = definitions.get(nodeId(file, method));
5384
+ if (found === void 0) {
5385
+ continue;
5386
+ }
5387
+ const list = topLevelMethods.get(found.name) ?? [];
5388
+ list.push(found);
5389
+ topLevelMethods.set(found.name, list);
4985
5390
  }
4986
5391
  }
4987
5392
  const lookup = {
@@ -4994,7 +5399,13 @@ function buildReachContext(files) {
4994
5399
  parsedFile: async () => null,
4995
5400
  localDefinition: (name) => blocksByQualifiedName.get(name) ?? null
4996
5401
  };
4997
- return { lookup, knownClasses, topLevelMethods };
5402
+ return { lookup, topLevelMethods, facts, classNames, definitions };
5403
+ }
5404
+ function namedChildren(node) {
5405
+ if (node === null) {
5406
+ return [];
5407
+ }
5408
+ return node.namedChildren.filter((child) => child !== null);
4998
5409
  }
4999
5410
  function topLevelMethodNodes(root, found = []) {
5000
5411
  for (const child of bodyStatements(root)) {
@@ -5009,14 +5420,6 @@ function topLevelMethodNodes(root, found = []) {
5009
5420
  }
5010
5421
  return found;
5011
5422
  }
5012
- function nestingOf(qualifiedName) {
5013
- const parts = qualifiedName.split("::");
5014
- const out = [];
5015
- for (let depth = parts.length; depth >= 1; depth--) {
5016
- out.push(parts.slice(0, depth).join("::"));
5017
- }
5018
- return out;
5019
- }
5020
5423
  var EMPTY_SCAN = {
5021
5424
  followed: [],
5022
5425
  stops: [],
@@ -5034,21 +5437,23 @@ function methodReferenceSymbol(node) {
5034
5437
  const argChildren = args === null ? [] : bodyStatements(args);
5035
5438
  return argChildren.length === 1 && argChildren[0]?.type === "simple_symbol" ? argChildren[0] : null;
5036
5439
  }
5037
- async function scanBody(source, ctx, options) {
5440
+ function bodyOf2(source, options) {
5441
+ const site = {
5442
+ file: source.file,
5443
+ method: source.node,
5444
+ owner: keyOf(source),
5445
+ enclosingQualifiedName: source.enclosingQualifiedName
5446
+ };
5447
+ const calls = field(source.node, "body") === null ? [] : withoutChainLinks(bodyCalls(source.node, options.inheritedMethods));
5448
+ return { calls, site, written: calls.map((call) => ({ call, site })) };
5449
+ }
5450
+ async function scanBody(source, ctx, options, read) {
5038
5451
  const displayPathOf = options.displayPathOf;
5039
- const body = field(source.node, "body");
5040
- if (body === null) {
5452
+ if (field(source.node, "body") === null) {
5041
5453
  return EMPTY_SCAN;
5042
5454
  }
5043
- const calls = withoutChainLinks(
5044
- bodyCalls(source.node, options.inheritedMethods)
5045
- );
5455
+ const { calls, site } = read;
5046
5456
  const ownParameters = positionalParameters(source.node).map((p) => p.name);
5047
- const site = {
5048
- enclosingQualifiedName: source.enclosingQualifiedName,
5049
- nesting: source.enclosingQualifiedName === null ? [] : nestingOf(source.enclosingQualifiedName),
5050
- ownParameters
5051
- };
5052
5457
  const followed2 = [];
5053
5458
  const stops = [];
5054
5459
  const placements = new import_behavioral_ir4.TargetPlacements();
@@ -5091,7 +5496,7 @@ async function scanBody(source, ctx, options) {
5091
5496
  };
5092
5497
  for (const call of calls) {
5093
5498
  const callee = calleeText(call);
5094
- const outcome = await resolveCallee(call, site, ctx);
5499
+ const outcome = await resolveCallee(call, site, ctx, read.spellings);
5095
5500
  const placed = outcome.kind === "followed" ? {
5096
5501
  file: displayPathOf(outcome.target.file),
5097
5502
  span: spanOf(outcome.target.node)
@@ -5259,9 +5664,9 @@ async function extractRubyProject(options) {
5259
5664
  options.onCacheDiagnostic?.(lookup.diagnostic);
5260
5665
  if (lookup.kind === "hit") {
5261
5666
  options.onTiming?.(timer.report());
5262
- return { summaries: lookup.summaries, facts: new import_datalog4.Database() };
5667
+ return { summaries: lookup.summaries, facts: new import_datalog3.Database() };
5263
5668
  }
5264
- const db = new import_datalog4.Database();
5669
+ const db = new import_datalog3.Database();
5265
5670
  const summaries = [];
5266
5671
  const tallies = (0, import_extractor6.createPackTallies)(options.packs);
5267
5672
  const constants = [];
@@ -5390,6 +5795,7 @@ async function extractRubyProject(options) {
5390
5795
  () => reachedFunctions(seeds, {
5391
5796
  files: parsed,
5392
5797
  displayPathOf,
5798
+ facts: db,
5393
5799
  ...storage === void 0 ? {} : { storage },
5394
5800
  inheritedMethods
5395
5801
  })
@@ -5486,15 +5892,15 @@ function findRubyFiles(root) {
5486
5892
  }
5487
5893
 
5488
5894
  // src/facts/why.ts
5489
- var WITNESS_RULES = [...import_resolution4.RESOLUTION_RULES, ...RUBY_RULES];
5490
- var METHOD_TYPES4 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
5895
+ var WITNESS_RULES = [...import_resolution7.RESOLUTION_RULES, ...RUBY_RULES];
5896
+ var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
5491
5897
  function namedChildrenOf(node) {
5492
5898
  return node.namedChildren.filter((child) => child !== null);
5493
5899
  }
5494
5900
  function enclosingMethod2(node) {
5495
5901
  let current = node.parent;
5496
5902
  while (current !== null) {
5497
- if (METHOD_TYPES4.has(current.type)) {
5903
+ if (METHOD_TYPES5.has(current.type)) {
5498
5904
  return current;
5499
5905
  }
5500
5906
  current = current.parent;
@@ -5537,7 +5943,7 @@ function width(node) {
5537
5943
  }
5538
5944
  var RubyWhySession = class {
5539
5945
  root;
5540
- db = new import_datalog5.Database();
5946
+ db = new import_datalog4.Database();
5541
5947
  locations = /* @__PURE__ */ new Map();
5542
5948
  trees = /* @__PURE__ */ new Map();
5543
5949
  constructor(options) {
@@ -5607,7 +6013,7 @@ var RubyWhySession = class {
5607
6013
  * which the caller says in its own words.
5608
6014
  */
5609
6015
  explain(value, options = {}) {
5610
- return (0, import_resolution4.explainResolvedKey)({
6016
+ return (0, import_resolution7.explainResolvedKey)({
5611
6017
  db: this.db,
5612
6018
  rules: WITNESS_RULES,
5613
6019
  key: readKey(value.file, value.node, enclosingMethod2(value.node)),