@suss/adapter-python 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.js CHANGED
@@ -24,6 +24,11 @@ function isType(node, ...types) {
24
24
  function isFunction(node) {
25
25
  return node.type === "function_definition" || node.type === "lambda";
26
26
  }
27
+ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([
28
+ "function_definition",
29
+ "lambda",
30
+ "class_definition"
31
+ ]);
27
32
  function enclosingFunction(node) {
28
33
  let current = node.parent;
29
34
  while (current !== null) {
@@ -1408,14 +1413,14 @@ function invocationEffects(definitionNode) {
1408
1413
  if (calls.length === 0) {
1409
1414
  return [];
1410
1415
  }
1411
- const statementOf2 = /* @__PURE__ */ new Map();
1416
+ const statementOf3 = /* @__PURE__ */ new Map();
1412
1417
  const statements = [];
1413
1418
  for (const call of calls) {
1414
1419
  const statement = enclosingStatement(call, body);
1415
1420
  if (statement === null) {
1416
1421
  continue;
1417
1422
  }
1418
- statementOf2.set(call.id, statement);
1423
+ statementOf3.set(call.id, statement);
1419
1424
  statements.push(statement);
1420
1425
  }
1421
1426
  const lowered = lowerPythonBody(body, statements);
@@ -1427,7 +1432,7 @@ function invocationEffects(definitionNode) {
1427
1432
  statements
1428
1433
  );
1429
1434
  return calls.map((call) => {
1430
- const statement = statementOf2.get(call.id);
1435
+ const statement = statementOf3.get(call.id);
1431
1436
  const conditions = sharedGatingConditions(
1432
1437
  statement === void 0 ? void 0 : enumerated.byTerminal.get(statement),
1433
1438
  predicateOf
@@ -1446,40 +1451,22 @@ function invocationEffects(definitionNode) {
1446
1451
  import { mountPathsOf } from "@suss/resolution";
1447
1452
 
1448
1453
  // src/facts/resolve.ts
1449
- import { deriveOnDemand, evaluate } from "@suss/datalog";
1450
1454
  import {
1451
- ANSWER_RELATIONS,
1455
+ askResolution,
1452
1456
  placeholderValues,
1453
- RESOLUTION_QUESTIONS,
1454
- RESOLUTION_RULES,
1455
1457
  writtenValueOf as sharedWrittenValueOf,
1456
1458
  singleAnswers
1457
1459
  } from "@suss/resolution";
1458
- var RESOLUTION_PROGRAM = deriveOnDemand(
1459
- [...RESOLUTION_RULES, ...RESOLUTION_QUESTIONS],
1460
- ANSWER_RELATIONS
1461
- );
1462
1460
  function resolveCalls(db, callKeys) {
1463
- const asked = new Set(db.facts("wanted").map((row) => String(row[0])));
1464
- const fresh = callKeys.filter((key) => !asked.has(key));
1465
- if (fresh.length === 0) {
1466
- return;
1467
- }
1468
- for (const callKey of fresh) {
1469
- db.add("wanted", [callKey]);
1470
- }
1471
- evaluate(db, RESOLUTION_PROGRAM.rules);
1461
+ askResolution(db, callKeys);
1472
1462
  }
1473
1463
  function writtenValueOf(db, key) {
1474
1464
  resolveCalls(db, [key]);
1475
1465
  return sharedWrittenValueOf(db, key, (keys) => resolveCalls(db, keys));
1476
1466
  }
1477
- function originsOf(db, nameKey) {
1478
- if (!db.facts("wantedOrigin").some((row) => String(row[0]) === nameKey)) {
1479
- db.add("wantedOrigin", [nameKey]);
1480
- evaluate(db, RESOLUTION_PROGRAM.rules);
1481
- }
1482
- return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
1467
+ function originsOf(db, nameKey2) {
1468
+ askResolution(db, [nameKey2], "wantedOrigin");
1469
+ return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey2).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
1483
1470
  }
1484
1471
  function subjectConstructions(db, valueKeys) {
1485
1472
  const found = /* @__PURE__ */ new Map();
@@ -1487,10 +1474,7 @@ function subjectConstructions(db, valueKeys) {
1487
1474
  return found;
1488
1475
  }
1489
1476
  const askSubjects = (keys) => {
1490
- for (const key of keys) {
1491
- db.add("wantedSubject", [key]);
1492
- }
1493
- evaluate(db, RESOLUTION_PROGRAM.rules);
1477
+ askResolution(db, keys, "wantedSubject");
1494
1478
  };
1495
1479
  askSubjects(valueKeys);
1496
1480
  const placeholders = placeholderValues(db);
@@ -1537,6 +1521,11 @@ function objectReturnedBy(db, callKey) {
1537
1521
  }
1538
1522
 
1539
1523
  // src/facts/values.ts
1524
+ import {
1525
+ startsAtName,
1526
+ valueLeftByWrites,
1527
+ writesRunInOrder
1528
+ } from "@suss/resolution";
1540
1529
  function nodeId(filePath, node) {
1541
1530
  return `${filePath}:${node.startIndex}-${node.endIndex}`;
1542
1531
  }
@@ -1547,10 +1536,10 @@ function readKey(filePath, node, enclosing) {
1547
1536
  if (node.type !== "identifier") {
1548
1537
  return nodeId(filePath, node);
1549
1538
  }
1550
- if (enclosing !== null && parameterNames(enclosing).has(node.text)) {
1551
- return `${nodeId(filePath, enclosing)}#${node.text}`;
1552
- }
1553
- return nameId(filePath, node.text);
1539
+ return nameKeyIn(filePath, enclosing, node.text);
1540
+ }
1541
+ function nameKeyIn(filePath, enclosing, name) {
1542
+ return nameKey(filePath, scopeChainOf(filePath, enclosing), name);
1554
1543
  }
1555
1544
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
1556
1545
  "string",
@@ -1588,9 +1577,6 @@ function parameterName2(param) {
1588
1577
  }
1589
1578
  return children(param).find((child) => child.type === "identifier") ?? null;
1590
1579
  }
1591
- function parameterList(fn) {
1592
- return parameterShapes(fn).map((parameter) => parameter.name);
1593
- }
1594
1580
  function parameterShapes(fn) {
1595
1581
  const params = field(fn, "parameters");
1596
1582
  const declared = [];
@@ -1605,24 +1591,52 @@ function parameterShapes(fn) {
1605
1591
  }
1606
1592
  return declared;
1607
1593
  }
1608
- function parameterNames(fn) {
1609
- return new Set(parameterList(fn));
1594
+ function boundParameterNames(fn) {
1595
+ const params = field(fn, "parameters");
1596
+ const names = [];
1597
+ for (const param of params === null ? [] : children(params)) {
1598
+ const name = parameterName2(param);
1599
+ if (name !== null) {
1600
+ names.push(name.text);
1601
+ }
1602
+ }
1603
+ return names;
1610
1604
  }
1611
1605
  function add(emitter, relation, ...tuple) {
1612
1606
  emitter.db.add(relation, tuple);
1613
1607
  }
1608
+ function nameKey(filePath, scope, name) {
1609
+ for (let current = scope; current !== null; current = current.parent) {
1610
+ if (current.globals.has(name)) {
1611
+ return nameId(filePath, name);
1612
+ }
1613
+ if (current.locals.has(name)) {
1614
+ return `${current.funcKey}#${name}`;
1615
+ }
1616
+ }
1617
+ return nameId(filePath, name);
1618
+ }
1619
+ function scopeChainOf(filePath, fn) {
1620
+ if (fn === null) {
1621
+ return null;
1622
+ }
1623
+ const reading = scopeReadingOf(fn);
1624
+ return {
1625
+ funcKey: nodeId(filePath, fn),
1626
+ locals: reading.locals,
1627
+ globals: reading.globals,
1628
+ parent: scopeChainOf(filePath, enclosingFunction(fn))
1629
+ };
1630
+ }
1614
1631
  function emitCall(emitter, call) {
1615
1632
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
1616
1633
  const callee = field(call, "function");
1617
- if (callee === null) {
1634
+ const args = field(call, "arguments");
1635
+ if (callee === null || args === null) {
1618
1636
  return;
1619
1637
  }
1620
1638
  const callKey = nodeId(emitter.filePath, call);
1621
1639
  add(emitter, "call", callKey, valueKey(emitter, callee));
1622
- const args = field(call, "arguments");
1623
- if (args === null) {
1624
- return;
1625
- }
1626
1640
  let position = 0;
1627
1641
  for (const argument of children(args)) {
1628
1642
  if (argument.type === "keyword_argument") {
@@ -1653,11 +1667,7 @@ function valueKey(emitter, value) {
1653
1667
  if (value.type !== "identifier") {
1654
1668
  return nodeId(emitter.filePath, value);
1655
1669
  }
1656
- const enclosing = emitter.enclosing;
1657
- if (enclosing !== null && enclosing.params.has(value.text)) {
1658
- return `${enclosing.funcKey}#${value.text}`;
1659
- }
1660
- return nameId(emitter.filePath, value.text);
1670
+ return nameKey(emitter.filePath, emitter.enclosing, value.text);
1661
1671
  }
1662
1672
  function emitSequence(emitter, sequence) {
1663
1673
  const objectKey = nodeId(emitter.filePath, sequence);
@@ -1747,7 +1757,6 @@ function emitFunctionFacts(emitter, fn, classKey) {
1747
1757
  const funcKey = nodeId(emitter.filePath, fn);
1748
1758
  add(emitter, "func", funcKey);
1749
1759
  const params = field(fn, "parameters");
1750
- const declared = parameterNames(fn);
1751
1760
  let position = classKey === void 0 ? 0 : -1;
1752
1761
  let byPosition = true;
1753
1762
  let receiver = null;
@@ -1770,24 +1779,22 @@ function emitFunctionFacts(emitter, fn, classKey) {
1770
1779
  }
1771
1780
  position += 1;
1772
1781
  }
1782
+ const reading = scopeReadingOf(fn);
1773
1783
  const inside = {
1774
1784
  ...emitter,
1775
- enclosing: { funcKey, params: declared }
1785
+ enclosing: {
1786
+ funcKey,
1787
+ locals: reading.locals,
1788
+ globals: reading.globals,
1789
+ parent: emitter.enclosing
1790
+ }
1776
1791
  };
1792
+ emitScopeWrites(inside, reading, false);
1777
1793
  const body = field(fn, "body");
1778
1794
  if (body === null) {
1779
1795
  return funcKey;
1780
1796
  }
1781
- const recordNested = (node) => {
1782
- for (const child of children(node)) {
1783
- if (isFunction(child)) {
1784
- add(emitter, "containsFn", funcKey, nodeId(emitter.filePath, child));
1785
- continue;
1786
- }
1787
- recordNested(child);
1788
- }
1789
- };
1790
- recordNested(body);
1797
+ emitNestedDefinitions(inside, body);
1791
1798
  walkExpressions(inside, body, (child) => {
1792
1799
  if (child.type === "return_statement") {
1793
1800
  const returned = child.namedChildren[0];
@@ -1798,29 +1805,17 @@ function emitFunctionFacts(emitter, fn, classKey) {
1798
1805
  if (child.type === "call") {
1799
1806
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
1800
1807
  }
1801
- if (child.type === "assignment") {
1802
- emitBinding(inside, child, receiver);
1808
+ if (child.type === "assignment" && receiver !== null) {
1809
+ emitReceiverProperty(inside, child, receiver);
1803
1810
  }
1804
1811
  emitExpressionFact(inside, child);
1805
1812
  });
1806
1813
  return funcKey;
1807
1814
  }
1808
- function emitBinding(emitter, assignment2, receiver) {
1815
+ function emitReceiverProperty(emitter, assignment2, receiver) {
1809
1816
  const left = field(assignment2, "left");
1810
1817
  const right = field(assignment2, "right");
1811
- if (left === null || right === null) {
1812
- return;
1813
- }
1814
- if (left.type === "identifier") {
1815
- add(
1816
- emitter,
1817
- "binds",
1818
- nameId(emitter.filePath, left.text),
1819
- valueKey(emitter, assignedValue(right))
1820
- );
1821
- return;
1822
- }
1823
- if (receiver === null || left.type !== "attribute") {
1818
+ if (left === null || right === null || left.type !== "attribute") {
1824
1819
  return;
1825
1820
  }
1826
1821
  const object = field(left, "object");
@@ -1836,21 +1831,6 @@ function emitBinding(emitter, assignment2, receiver) {
1836
1831
  valueKey(emitter, right)
1837
1832
  );
1838
1833
  }
1839
- function emitAssignment(emitter, assignment2) {
1840
- emitBinding(emitter, assignment2, null);
1841
- const left = field(assignment2, "left");
1842
- const right = field(assignment2, "right");
1843
- if (left === null || right === null || left.type !== "identifier") {
1844
- return;
1845
- }
1846
- add(
1847
- emitter,
1848
- "exportsAs",
1849
- emitter.filePath,
1850
- left.text,
1851
- valueKey(emitter, assignedValue(right))
1852
- );
1853
- }
1854
1834
  function assignedValue(right) {
1855
1835
  if (right.type !== "subscript") {
1856
1836
  return right;
@@ -1901,40 +1881,361 @@ function emitClassFacts(emitter, cls) {
1901
1881
  }
1902
1882
  continue;
1903
1883
  }
1904
- const memberKey = declaredMemberKey(emitter, member, classKey);
1884
+ const memberKey2 = declaredMemberKey(emitter, member, classKey);
1905
1885
  const name = field(member, "name");
1906
- if (memberKey !== null && name !== null) {
1907
- add(emitter, "holdsProperty", classKey, name.text, memberKey);
1886
+ if (memberKey2 !== null && name !== null) {
1887
+ add(emitter, "holdsProperty", classKey, name.text, memberKey2);
1908
1888
  }
1909
1889
  }
1910
1890
  return classKey;
1911
1891
  }
1912
- function emitValueFacts(db, filePath, root) {
1913
- const emitter = { db, filePath, enclosing: null };
1914
- const declaresName = (child, key) => {
1915
- const name = field(child, "name");
1916
- if (name === null) {
1892
+ function emitNestedDefinitions(emitter, node) {
1893
+ for (const child of children(node)) {
1894
+ if (isFunction(child)) {
1895
+ emitFunctionFacts(emitter, child);
1896
+ if (emitter.enclosing !== null) {
1897
+ add(
1898
+ emitter,
1899
+ "containsFn",
1900
+ emitter.enclosing.funcKey,
1901
+ nodeId(emitter.filePath, child)
1902
+ );
1903
+ }
1904
+ continue;
1905
+ }
1906
+ if (child.type === "class_definition") {
1907
+ emitClassFacts(emitter, child);
1908
+ continue;
1909
+ }
1910
+ emitNestedDefinitions(emitter, child);
1911
+ }
1912
+ }
1913
+ var readingsByTree = /* @__PURE__ */ new WeakMap();
1914
+ function scopeReadingOf(fn) {
1915
+ const tree = fn.tree;
1916
+ let byNode = readingsByTree.get(tree);
1917
+ if (byNode === void 0) {
1918
+ byNode = /* @__PURE__ */ new Map();
1919
+ readingsByTree.set(tree, byNode);
1920
+ }
1921
+ const remembered = byNode.get(fn.id);
1922
+ if (remembered !== void 0) {
1923
+ return remembered;
1924
+ }
1925
+ const body = field(fn, "body");
1926
+ const reading = readScope(
1927
+ body !== null && body.type === "block" ? body : null,
1928
+ boundParameterNames(fn)
1929
+ );
1930
+ byNode.set(fn.id, reading);
1931
+ return reading;
1932
+ }
1933
+ var TARGET_PATTERN_TYPES = /* @__PURE__ */ new Set([
1934
+ "pattern_list",
1935
+ "tuple_pattern",
1936
+ "list_pattern"
1937
+ ]);
1938
+ function targetNames(target, found = []) {
1939
+ if (target.type === "identifier") {
1940
+ found.push(target);
1941
+ return found;
1942
+ }
1943
+ if (TARGET_PATTERN_TYPES.has(target.type)) {
1944
+ for (const child of children(target)) {
1945
+ targetNames(child, found);
1946
+ }
1947
+ }
1948
+ return found;
1949
+ }
1950
+ function readAssignment(node, sink) {
1951
+ const left = field(node, "left");
1952
+ const right = field(node, "right");
1953
+ if (left === null) {
1954
+ return;
1955
+ }
1956
+ const written = right === null ? null : assignedValue(right);
1957
+ const names = targetNames(left);
1958
+ for (const name of names) {
1959
+ sink.record(name.text, {
1960
+ // An unpacked target takes one piece of the value, and which piece
1961
+ // is not something the assignment says.
1962
+ value: names.length === 1 ? written : null,
1963
+ given: null,
1964
+ at: node,
1965
+ direct: isDirectStatement(node, sink.bodyOwner)
1966
+ });
1967
+ }
1968
+ }
1969
+ function readAugmentedAssignment(node, sink) {
1970
+ const left = field(node, "left");
1971
+ if (left === null || left.type !== "identifier") {
1972
+ return;
1973
+ }
1974
+ sink.record(left.text, {
1975
+ value: null,
1976
+ given: null,
1977
+ at: node,
1978
+ direct: isDirectStatement(node, sink.bodyOwner)
1979
+ });
1980
+ }
1981
+ function readIterationTarget(node, sink) {
1982
+ const left = field(node, "left");
1983
+ if (left === null) {
1984
+ return;
1985
+ }
1986
+ for (const name of targetNames(left)) {
1987
+ sink.record(name.text, {
1988
+ value: null,
1989
+ given: null,
1990
+ at: node,
1991
+ direct: false
1992
+ });
1993
+ }
1994
+ }
1995
+ function identifierOf(node) {
1996
+ if (node.type === "identifier") {
1997
+ return node;
1998
+ }
1999
+ return children(node).find((child) => child.type === "identifier") ?? null;
2000
+ }
2001
+ function readAsPattern(node, sink) {
2002
+ const target = field(node, "alias") ?? children(node)[1];
2003
+ const name = target === void 0 ? null : identifierOf(target);
2004
+ if (name === null) {
2005
+ return;
2006
+ }
2007
+ sink.record(name.text, {
2008
+ value: null,
2009
+ given: null,
2010
+ at: node,
2011
+ direct: false
2012
+ });
2013
+ }
2014
+ function readWalrus(node, sink) {
2015
+ const name = field(node, "name");
2016
+ if (name === null || name.type !== "identifier") {
2017
+ return;
2018
+ }
2019
+ sink.record(name.text, {
2020
+ value: field(node, "value"),
2021
+ given: null,
2022
+ at: node,
2023
+ direct: false
2024
+ });
2025
+ }
2026
+ function readGlobalStatement(node, sink) {
2027
+ for (const child of children(node)) {
2028
+ if (child.type === "identifier") {
2029
+ sink.globals.add(child.text);
2030
+ }
2031
+ }
2032
+ }
2033
+ function readNonlocalStatement(node, sink) {
2034
+ for (const child of children(node)) {
2035
+ if (child.type === "identifier") {
2036
+ sink.nonlocals.add(child.text);
2037
+ }
2038
+ }
2039
+ }
2040
+ var WRITE_READERS = {
2041
+ assignment: readAssignment,
2042
+ augmented_assignment: readAugmentedAssignment,
2043
+ for_statement: readIterationTarget,
2044
+ for_in_clause: readIterationTarget,
2045
+ as_pattern: readAsPattern,
2046
+ named_expression: readWalrus,
2047
+ global_statement: readGlobalStatement,
2048
+ nonlocal_statement: readNonlocalStatement
2049
+ };
2050
+ function readScope(bodyOwner, parameterNames) {
2051
+ const writes = /* @__PURE__ */ new Map();
2052
+ const globals = /* @__PURE__ */ new Set();
2053
+ const nonlocals = /* @__PURE__ */ new Set();
2054
+ const record = (name, write) => {
2055
+ const existing = writes.get(name);
2056
+ if (existing === void 0) {
2057
+ writes.set(name, [write]);
1917
2058
  return;
1918
2059
  }
1919
- add(emitter, "binds", nameId(filePath, name.text), key);
1920
- add(emitter, "exportsAs", filePath, name.text, key);
2060
+ existing.push(write);
1921
2061
  };
1922
- const walk = (node) => {
1923
- for (const child of children(node)) {
1924
- if (child.type === "class_definition") {
1925
- declaresName(child, emitClassFacts(emitter, child));
1926
- continue;
1927
- }
1928
- if (isFunction(child)) {
1929
- declaresName(child, emitFunctionFacts(emitter, child));
1930
- }
1931
- if (child.type === "assignment") {
1932
- emitAssignment(emitter, child);
2062
+ const sink = { bodyOwner, globals, nonlocals, record };
2063
+ const visit = (node) => {
2064
+ if (isFunction(node) || node.type === "class_definition") {
2065
+ const name = field(node, "name");
2066
+ if (name !== null) {
2067
+ record(name.text, {
2068
+ value: node,
2069
+ given: null,
2070
+ at: node,
2071
+ direct: isDirectStatement(node, bodyOwner)
2072
+ });
1933
2073
  }
1934
- walk(child);
2074
+ return;
2075
+ }
2076
+ WRITE_READERS[node.type]?.(node, sink);
2077
+ for (const child of children(node)) {
2078
+ visit(child);
1935
2079
  }
1936
2080
  };
1937
- walk(root);
2081
+ for (const statement of bodyOwner === null ? [] : children(bodyOwner)) {
2082
+ visit(statement);
2083
+ }
2084
+ const locals = new Set(writes.keys());
2085
+ for (const name of [...globals, ...nonlocals]) {
2086
+ locals.delete(name);
2087
+ }
2088
+ for (const name of parameterNames) {
2089
+ locals.add(name);
2090
+ addParameterWrite(writes.get(name), name);
2091
+ }
2092
+ return { bodyOwner, writes, locals, globals };
2093
+ }
2094
+ function addParameterWrite(later, name) {
2095
+ const first = later?.[0];
2096
+ if (later === void 0 || first === void 0) {
2097
+ return;
2098
+ }
2099
+ later.unshift({ value: null, given: name, at: first.at, direct: false });
2100
+ }
2101
+ function statementOf(node) {
2102
+ let current = node;
2103
+ while (current !== null) {
2104
+ const parent = current.parent;
2105
+ if (parent === null) {
2106
+ return null;
2107
+ }
2108
+ if (parent.type === "block" || parent.type === "module") {
2109
+ return current;
2110
+ }
2111
+ current = parent;
2112
+ }
2113
+ return null;
2114
+ }
2115
+ function isDirectStatement(node, bodyOwner) {
2116
+ if (bodyOwner === null) {
2117
+ return false;
2118
+ }
2119
+ const statement = statementOf(node);
2120
+ return statement !== null && statement.parent?.id === bodyOwner.id;
2121
+ }
2122
+ var UNREAD_PARENTS = /* @__PURE__ */ new Set([
2123
+ "as_pattern_target",
2124
+ "global_statement",
2125
+ "nonlocal_statement",
2126
+ "pattern_list",
2127
+ "tuple_pattern",
2128
+ "list_pattern",
2129
+ "dotted_name"
2130
+ ]);
2131
+ var UNREAD_FIELDS = {
2132
+ assignment: "left",
2133
+ augmented_assignment: "left",
2134
+ for_statement: "left",
2135
+ for_in_clause: "left",
2136
+ named_expression: "name",
2137
+ function_definition: "name",
2138
+ class_definition: "name",
2139
+ attribute: "attribute",
2140
+ keyword_argument: "name"
2141
+ };
2142
+ function isNameRead(name) {
2143
+ const parent = name.parent;
2144
+ if (parent === null) {
2145
+ return true;
2146
+ }
2147
+ if (UNREAD_PARENTS.has(parent.type)) {
2148
+ return false;
2149
+ }
2150
+ const fieldName = UNREAD_FIELDS[parent.type];
2151
+ return fieldName === void 0 || field(parent, fieldName)?.id !== name.id;
2152
+ }
2153
+ var READ_FIRST = {
2154
+ call: "function",
2155
+ attribute: "object"
2156
+ };
2157
+ function readFirst(node) {
2158
+ if (node.type === "parenthesized_expression") {
2159
+ return children(node)[0] ?? null;
2160
+ }
2161
+ const fieldName = READ_FIRST[node.type];
2162
+ return fieldName === void 0 ? null : field(node, fieldName);
2163
+ }
2164
+ var NAME_READS = {
2165
+ nameTypes: /* @__PURE__ */ new Set(["identifier"]),
2166
+ laterBodies: LATER_BODY_TYPES,
2167
+ childrenOf: children,
2168
+ isRead: isNameRead,
2169
+ readFirst
2170
+ };
2171
+ var CONSTRUCTION_TYPES = /* @__PURE__ */ new Set([
2172
+ "call",
2173
+ "list",
2174
+ "dictionary",
2175
+ "set",
2176
+ "tuple",
2177
+ "list_comprehension",
2178
+ "dictionary_comprehension",
2179
+ "set_comprehension",
2180
+ "generator_expression"
2181
+ ]);
2182
+ function describeWrite(emitter, write, name) {
2183
+ if (write.value === null) {
2184
+ return {
2185
+ value: write.given,
2186
+ placeholder: false,
2187
+ construction: null,
2188
+ narrowsName: false
2189
+ };
2190
+ }
2191
+ return {
2192
+ value: valueKey(emitter, write.value),
2193
+ placeholder: write.value.type === "none",
2194
+ construction: CONSTRUCTION_TYPES.has(write.value.type) ? write.value.text.replace(/\s+/g, " ").trim() : null,
2195
+ narrowsName: write.value.type === "call" && startsAtName(write.value, name, NAME_READS)
2196
+ };
2197
+ }
2198
+ function settledValue(emitter, reading, name, writes) {
2199
+ const only = writes.length === 1 ? writes[0] : void 0;
2200
+ if (only !== void 0) {
2201
+ return only.value === null ? only.given : valueKey(emitter, only.value);
2202
+ }
2203
+ return valueLeftByWrites(
2204
+ writes.map((write) => describeWrite(emitter, write, name)),
2205
+ writesRunInOrder(reading.bodyOwner, name, writes, NAME_READS)
2206
+ );
2207
+ }
2208
+ function emitScopeWrites(emitter, reading, atModule) {
2209
+ for (const [name, writes] of reading.writes) {
2210
+ const settled = settledValue(emitter, reading, name, writes);
2211
+ const key = nameKey(emitter.filePath, emitter.enclosing, name);
2212
+ if (settled === null) {
2213
+ emitCandidates(emitter, key, name, writes);
2214
+ continue;
2215
+ }
2216
+ add(emitter, writes.length === 1 ? "binds" : "endsHolding", key, settled);
2217
+ if (atModule) {
2218
+ add(emitter, "exportsAs", emitter.filePath, name, settled);
2219
+ }
2220
+ }
2221
+ }
2222
+ function emitCandidates(emitter, key, name, writes) {
2223
+ for (const write of writes) {
2224
+ if (write.value === null) {
2225
+ if (write.given === null) {
2226
+ add(emitter, "writesUnstated", key);
2227
+ }
2228
+ continue;
2229
+ }
2230
+ if (!describeWrite(emitter, write, name).narrowsName) {
2231
+ add(emitter, "mayHold", key, valueKey(emitter, write.value));
2232
+ }
2233
+ }
2234
+ }
2235
+ function emitValueFacts(db, filePath, root) {
2236
+ const emitter = { db, filePath, enclosing: null };
2237
+ emitNestedDefinitions(emitter, root);
2238
+ emitScopeWrites(emitter, readScope(root, []), true);
1938
2239
  emitExpressionFacts(emitter, root);
1939
2240
  }
1940
2241
 
@@ -2047,7 +2348,7 @@ function pythonLowering(options) {
2047
2348
  return {
2048
2349
  idOf: (node) => node.id,
2049
2350
  expression: (node) => expressionOf(node, originOf2),
2050
- statement: statementOf,
2351
+ statement: statementOf2,
2051
2352
  siteOf,
2052
2353
  functionOf,
2053
2354
  writtenTo: (node) => context === null ? null : context.writtenTo(node),
@@ -2311,7 +2612,7 @@ var STATEMENT_TYPES = {
2311
2612
  body: blockStatements2(field(node, "body"))
2312
2613
  })
2313
2614
  };
2314
- function statementOf(node) {
2615
+ function statementOf2(node) {
2315
2616
  const lower = STATEMENT_TYPES[node.type];
2316
2617
  return lower === void 0 ? { kind: "opaque" } : lower(node);
2317
2618
  }
@@ -3381,9 +3682,13 @@ function constructionThroughFacts(name, scope, scan) {
3381
3682
  if (binding.kind !== "assignment" && binding.kind !== "importFrom") {
3382
3683
  return null;
3383
3684
  }
3384
- const nameKey = `${scan.bound.file}#${name}`;
3385
- const constructionKey2 = subjectConstructions(facts, [nameKey]).get(
3386
- nameKey
3685
+ const nameKey2 = nameKeyIn(
3686
+ scan.bound.file,
3687
+ scope.kind === "function" ? scope.node : enclosingFunction(scope.node),
3688
+ name
3689
+ );
3690
+ const constructionKey2 = subjectConstructions(facts, [nameKey2]).get(
3691
+ nameKey2
3387
3692
  )?.constructionKey;
3388
3693
  return constructionKey2 === void 0 ? null : scan.index.byValueKey.get(constructionKey2) ?? null;
3389
3694
  }
@@ -3474,11 +3779,11 @@ function walkStatements(statements, position, scan, visit) {
3474
3779
  visit(stmt, position, scan);
3475
3780
  }
3476
3781
  }
3477
- function targetNames(node) {
3782
+ function targetNames2(node) {
3478
3783
  if (node.type === "identifier") {
3479
3784
  return [node.text];
3480
3785
  }
3481
- return bodyStatements(node).flatMap(targetNames);
3786
+ return bodyStatements(node).flatMap(targetNames2);
3482
3787
  }
3483
3788
  function sequenceElementNames(node) {
3484
3789
  if (node === null || node.type !== "list" && node.type !== "tuple") {
@@ -3515,7 +3820,7 @@ function loopBindingsOf(stmt, position, scan) {
3515
3820
  });
3516
3821
  return bindings;
3517
3822
  }
3518
- for (const name of targetNames(left)) {
3823
+ for (const name of targetNames2(left)) {
3519
3824
  bindings.set(name, { kind: "unenumerable", location });
3520
3825
  }
3521
3826
  return bindings;
@@ -4991,14 +5296,9 @@ function matchedChains(calls, options) {
4991
5296
  if (chains.length === 0) {
4992
5297
  return [];
4993
5298
  }
4994
- const alreadyAsked = new Set(
4995
- options.facts.facts("wanted").map((row) => String(row[0]))
4996
- );
4997
5299
  resolveCalls(
4998
5300
  options.facts,
4999
- storageCallees(calls, options.filePath, options.couldMatch).filter(
5000
- (key) => !alreadyAsked.has(key)
5001
- )
5301
+ storageCallees(calls, options.filePath, options.couldMatch)
5002
5302
  );
5003
5303
  const matched = [];
5004
5304
  for (const chain of chains) {
@@ -6060,7 +6360,7 @@ function isInjectedParameter(param, injectedCallees, scope, ctx) {
6060
6360
  import fs4 from "fs";
6061
6361
  import path4 from "path";
6062
6362
  import { Database as Database3 } from "@suss/datalog";
6063
- import { explainResolvedKey, RESOLUTION_RULES as RESOLUTION_RULES2 } from "@suss/resolution";
6363
+ import { explainResolvedKey, RESOLUTION_RULES } from "@suss/resolution";
6064
6364
 
6065
6365
  // src/facts.ts
6066
6366
  function unitKey(filePath, range, name) {
@@ -6078,7 +6378,7 @@ function emitModuleImportFacts(db, filePath, module, resolverOptions) {
6078
6378
  if (binding.kind !== "import" && binding.kind !== "importFrom") {
6079
6379
  continue;
6080
6380
  }
6081
- const nameKey = `${filePath}#${localName}`;
6381
+ const nameKey2 = `${filePath}#${localName}`;
6082
6382
  const moduleText = importedModuleText(
6083
6383
  binding.module,
6084
6384
  binding.relativeLevel
@@ -6094,16 +6394,16 @@ function emitModuleImportFacts(db, filePath, module, resolverOptions) {
6094
6394
  resolution.status === "resolved" ? "resolved" : resolution.reason
6095
6395
  ]);
6096
6396
  const importedName = binding.kind === "import" ? binding.localName : binding.importedName;
6097
- db.add("pyImportedName", [nameKey, moduleText, importedName]);
6397
+ db.add("pyImportedName", [nameKey2, moduleText, importedName]);
6098
6398
  if (scope.kind === "module") {
6099
- db.add("exportsAs", [filePath, localName, nameKey]);
6399
+ db.add("exportsAs", [filePath, localName, nameKey2]);
6100
6400
  }
6101
6401
  if (resolution.status === "resolved") {
6102
6402
  db.add("pyImportResolved", [filePath, moduleText, resolution.file]);
6103
- db.add("imports", [nameKey, resolution.file, importedName]);
6403
+ db.add("imports", [nameKey2, resolution.file, importedName]);
6104
6404
  continue;
6105
6405
  }
6106
- db.add("imports", [nameKey, moduleText, importedName]);
6406
+ db.add("imports", [nameKey2, moduleText, importedName]);
6107
6407
  }
6108
6408
  }
6109
6409
  for (const openModule of module.openImports) {
@@ -6274,7 +6574,7 @@ import {
6274
6574
  unfollowedCallGap,
6275
6575
  worthRecording
6276
6576
  } from "@suss/behavioral-ir";
6277
- import { Database, evaluate as evaluate2, lit, rule, variable as v } from "@suss/datalog";
6577
+ import { Database, evaluate, lit, rule, variable as v } from "@suss/datalog";
6278
6578
  import {
6279
6579
  assembleSummary,
6280
6580
  SKIP_CHILDREN as SKIP_CHILDREN2,
@@ -6282,251 +6582,227 @@ import {
6282
6582
  } from "@suss/extractor";
6283
6583
 
6284
6584
  // src/reach/resolveCallee.ts
6285
- var stop = (reason) => ({ kind: "stop", reason });
6286
- var MODULE_STOP = {
6287
- ambiguous: "multipleSources",
6288
- outsideRoots: "outsideRun"
6585
+ import {
6586
+ calleeOutcomeOf,
6587
+ calleeOutcomes,
6588
+ writtenSourcesOf
6589
+ } from "@suss/resolution";
6590
+ var STOP_FOR = {
6591
+ severalSources: "multipleSources",
6592
+ outsideRun: "outsideRun",
6593
+ unsettled: "unsettledValue",
6594
+ undeclared: "noDeclaration"
6289
6595
  };
6290
- function resolveCallee2(call, site, ctx) {
6291
- const callee = field(call, "function");
6292
- if (callee === null) {
6293
- return { kind: "stopped", reason: "noDeclaration" };
6596
+ var NO_DECLARATION = {
6597
+ kind: "stopped",
6598
+ reason: "noDeclaration"
6599
+ };
6600
+ function calleeSpellings(calls, ctx) {
6601
+ const spellingOf = /* @__PURE__ */ new Map();
6602
+ const keys = /* @__PURE__ */ new Set();
6603
+ for (const { call, site } of calls) {
6604
+ const spelling = spellingFor(call, site, ctx);
6605
+ spellingOf.set(call.id, spelling);
6606
+ if (spelling.kind === "key") {
6607
+ keys.add(spelling.key);
6608
+ }
6294
6609
  }
6295
- return asCallee(resolveExpression(callee, site, ctx), ctx);
6610
+ return { spellingOf, outcomes: calleeOutcomes(ctx.facts, [...keys]) };
6296
6611
  }
6297
- function resolveNamedFunctionArgument(name, site, ctx) {
6298
- if (site.rebound.has(name)) {
6299
- return null;
6612
+ function resolveCallee2(call, site, ctx, read) {
6613
+ const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site, ctx);
6614
+ if (spelling.kind === "stopped") {
6615
+ return spelling;
6300
6616
  }
6301
- const binding = resolveName(site.scope, name);
6302
- if (binding !== null && binding.kind === "assignment") {
6617
+ const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key);
6618
+ return asCallee(outcome, site.owner, ctx);
6619
+ }
6620
+ function functionNamed(nameKey2, ctx) {
6621
+ if (assignedElsewhere(ctx.facts, nameKey2)) {
6303
6622
  return null;
6304
6623
  }
6305
- const resolved = binding === null ? throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set()) : resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
6306
- return resolved.kind === "function" ? resolved.target : null;
6624
+ const outcome = calleeOutcomeOf(ctx.facts, nameKey2);
6625
+ return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
6307
6626
  }
6308
- function asCallee(resolved, ctx) {
6309
- if (resolved.kind === "function") {
6310
- return { kind: "followed", target: resolved.target };
6311
- }
6312
- if (resolved.kind === "class") {
6313
- return asCallee(
6314
- memberOfClass(resolved.file, resolved.node, "__init__", ctx),
6315
- ctx
6316
- );
6317
- }
6318
- if (resolved.kind === "stop") {
6319
- return { kind: "stopped", reason: resolved.reason };
6320
- }
6321
- if (resolved.kind === "package") {
6322
- return { kind: "stopped", reason: "outsideRun" };
6323
- }
6324
- return { kind: "stopped", reason: "noDeclaration" };
6627
+ function assignedElsewhere(facts, nameKey2) {
6628
+ return writtenSourcesOf(facts, nameKey2).some(
6629
+ (source) => !facts.has("func", [source])
6630
+ );
6325
6631
  }
6326
- var EXPRESSION_RESOLVERS = {
6327
- identifier: (node, site, ctx) => resolveIdentifier(node.text, site, ctx),
6328
- attribute: (node, site, ctx) => {
6329
- const object = field(node, "object");
6330
- const attribute = field(node, "attribute");
6331
- if (object === null || attribute === null) {
6332
- return stop("noDeclaration");
6333
- }
6334
- return memberOf(resolveExpression(object, site, ctx), attribute.text, ctx);
6335
- },
6336
- call: (node, site, ctx) => {
6337
- const callee = field(node, "function");
6338
- return callee === null ? stop("noDeclaration") : instanceBuiltBy(resolveExpression(callee, site, ctx), "noDeclaration");
6339
- },
6340
- parenthesized_expression: (node, site, ctx) => {
6341
- const inner = node.namedChildren[0];
6342
- return inner === null || inner === void 0 ? stop("noDeclaration") : resolveExpression(inner, site, ctx);
6632
+ function readThrough(node) {
6633
+ if (node?.type !== "parenthesized_expression") {
6634
+ return node;
6343
6635
  }
6344
- };
6345
- function resolveExpression(node, site, ctx) {
6346
- const resolver = EXPRESSION_RESOLVERS[node.type];
6347
- return resolver === void 0 ? stop("noDeclaration") : resolver(node, site, ctx);
6636
+ return readThrough(children(node)[0] ?? null);
6348
6637
  }
6349
- function instanceBuiltBy(built, otherwise) {
6350
- if (built.kind === "class" || built.kind === "stop") {
6351
- return built;
6638
+ function spellingFor(call, site, ctx) {
6639
+ const callee = readThrough(field(call, "function"));
6640
+ if (callee === null) {
6641
+ return { kind: "stopped", reason: "noDeclaration" };
6352
6642
  }
6353
- return built.kind === "package" ? stop("outsideRun") : stop(otherwise);
6643
+ return throughModules(callee, site, ctx) ?? {
6644
+ kind: "key",
6645
+ key: readKey(site.file.file, callee, enclosingFunction(callee))
6646
+ };
6354
6647
  }
6355
- function resolveIdentifier(name, site, ctx) {
6356
- if (site.rebound.has(name)) {
6357
- return stop("unsettledValue");
6648
+ function asCallee(outcome, owner, ctx) {
6649
+ if (outcome.kind === "function") {
6650
+ return functionCallee(outcome.key, ctx);
6358
6651
  }
6359
- if (isReceiver(name, site.scope)) {
6360
- return receiverClass(site);
6652
+ if (outcome.kind === "object") {
6653
+ return constructorOf(outcome.key, ctx);
6361
6654
  }
6362
- const binding = resolveName(site.scope, name);
6363
- if (binding === null) {
6364
- return throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set());
6655
+ if (outcome.kind === "callerSupplied") {
6656
+ return outcome.key.startsWith(`${owner}#`) ? { kind: "stopped", reason: "callerSupplied" } : { kind: "stopped", reason: "unsettledValue" };
6365
6657
  }
6366
- return resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
6367
- }
6368
- function isReceiver(name, scope) {
6369
- return (name === "self" || name === "cls") && scope.kind === "function" && scope.parent?.kind === "class";
6658
+ return {
6659
+ kind: "stopped",
6660
+ reason: STOP_FOR[outcome.kind] ?? "noDeclaration"
6661
+ };
6370
6662
  }
6371
- function receiverClass(site) {
6372
- const classScope = site.scope.parent;
6373
- if (classScope === null || classScope === void 0) {
6374
- return stop("noDeclaration");
6663
+ function functionCallee(key, ctx) {
6664
+ if (ctx.definitions.get(key)?.type === "lambda") {
6665
+ return { kind: "stopped", reason: "unsettledValue" };
6375
6666
  }
6376
- return { kind: "class", file: site.file, node: classScope.node };
6667
+ const target = functionAt(key, ctx);
6668
+ return target === null ? NO_DECLARATION : { kind: "followed", target };
6377
6669
  }
6378
- var BINDING_RESOLVERS = {
6379
- functionDef: (binding, file) => ({
6380
- kind: "function",
6381
- target: reachedFunction(file, binding.node)
6382
- }),
6383
- classDef: (binding, file) => ({ kind: "class", file, node: binding.node }),
6384
- parameter: () => stop("callerSupplied"),
6385
- // `import a.b.c` binds `a`, and `import a.b.c as m` binds `m` to `a.b.c`.
6386
- import: (binding, file, _scope, ctx) => moduleNamed(
6387
- file,
6388
- binding.localName === binding.module.split(".")[0] ? binding.localName : binding.module,
6389
- 0,
6390
- ctx
6391
- ),
6392
- importFrom: (binding, file, _scope, ctx, visited) => memberOf(
6393
- moduleNamed(file, binding.module, binding.relativeLevel, ctx),
6394
- binding.importedName,
6395
- ctx,
6396
- visited
6397
- ),
6398
- // `run = load` is an alias worth one hop; anything else is a value.
6399
- assignment: (binding, file, scope, ctx, visited) => aliasedValue(binding.value, file, scope, ctx, visited),
6400
- global: () => stop("unsettledValue"),
6401
- nonlocal: () => stop("unsettledValue")
6402
- };
6403
- function resolveBinding(binding, file, scope, ctx, visited) {
6404
- const resolver = BINDING_RESOLVERS[binding.kind];
6405
- return resolver(binding, file, scope, ctx, visited);
6406
- }
6407
- function aliasedValue(value, file, scope, ctx, visited) {
6408
- if (value === null) {
6409
- return stop("unsettledValue");
6410
- }
6411
- if (value.type === "identifier") {
6412
- const binding = resolveName(scope, value.text);
6413
- if (binding === null || binding.kind === "assignment") {
6414
- return stop("unsettledValue");
6415
- }
6416
- return resolveBinding(binding, file, scope, ctx, visited);
6417
- }
6418
- if (value.type === "call") {
6419
- const callee = field(value, "function");
6420
- return callee === null ? stop("unsettledValue") : instanceBuiltBy(
6421
- aliasedValue(callee, file, scope, ctx, visited),
6422
- "unsettledValue"
6423
- );
6670
+ function constructorOf(classKey, ctx) {
6671
+ const declared = ctx.facts.lookup("holdsProperty", 0, classKey).find((row) => String(row[1]) === "__init__");
6672
+ if (declared === void 0) {
6673
+ return NO_DECLARATION;
6424
6674
  }
6425
- if (value.type === "attribute") {
6426
- const object = field(value, "object");
6427
- const attribute = field(value, "attribute");
6428
- if (object === null || attribute === null) {
6429
- return stop("unsettledValue");
6430
- }
6431
- return memberOf(
6432
- aliasedValue(object, file, scope, ctx, visited),
6433
- attribute.text,
6434
- ctx
6435
- );
6436
- }
6437
- return stop("unsettledValue");
6675
+ return functionCallee(String(declared[2]), ctx);
6438
6676
  }
6439
- function reachedFunction(file, node, owner) {
6677
+ function functionAt(key, ctx) {
6678
+ const node = ctx.definitions.get(key);
6679
+ const file = ctx.filesByPath.get(key.slice(0, key.lastIndexOf(":")));
6680
+ if (node === void 0 || file === void 0) {
6681
+ return null;
6682
+ }
6440
6683
  const name = field(node, "name")?.text ?? "<anon>";
6441
- const ownerName = owner === void 0 ? null : field(owner, "name")?.text;
6684
+ const owner = ownerClassOf(node);
6685
+ const ownerName = owner === null ? void 0 : field(owner, "name")?.text;
6442
6686
  return {
6443
6687
  file,
6444
6688
  node,
6445
6689
  name,
6446
- exportPath: ownerName === null || ownerName === void 0 ? [name] : [ownerName, name]
6690
+ exportPath: ownerName === void 0 ? [name] : [ownerName, name]
6447
6691
  };
6448
6692
  }
6449
- function moduleNamed(from, module, relativeLevel, ctx) {
6450
- const resolution = resolveModule(
6451
- from.file,
6452
- { module, relativeLevel },
6453
- { roots: ctx.roots }
6454
- );
6455
- if (resolution.status !== "resolved") {
6456
- return resolution.reason === "external" ? { kind: "package", dotted: module, relativeLevel, from } : stop(MODULE_STOP[resolution.reason]);
6693
+ function ownerClassOf(node) {
6694
+ for (let up = node.parent; up !== null; up = up.parent) {
6695
+ if (isFunction(up)) {
6696
+ return null;
6697
+ }
6698
+ if (up.type === "class_definition") {
6699
+ return up;
6700
+ }
6457
6701
  }
6458
- const file = ctx.filesByPath.get(resolution.file);
6459
- return file === void 0 ? stop("outsideRun") : { kind: "module", file };
6702
+ return null;
6460
6703
  }
6461
- function memberOf(base, name, ctx, visited = /* @__PURE__ */ new Set()) {
6462
- if (base.kind === "module") {
6463
- return memberOfModule(base.file, name, ctx, visited);
6704
+ function throughModules(callee, site, ctx) {
6705
+ if (callee.type === "identifier") {
6706
+ return resolveName(site.scope, callee.text) === null ? openImportMember(site.file, callee.text, ctx, /* @__PURE__ */ new Set()) : null;
6464
6707
  }
6465
- if (base.kind === "package") {
6466
- return moduleNamed(
6467
- base.from,
6468
- base.dotted === "" ? name : `${base.dotted}.${name}`,
6469
- base.relativeLevel,
6470
- ctx
6471
- );
6708
+ if (callee.type !== "attribute") {
6709
+ return null;
6472
6710
  }
6473
- if (base.kind === "class") {
6474
- return memberOfClass(base.file, base.node, name, ctx);
6711
+ const object = field(callee, "object");
6712
+ const attribute = field(callee, "attribute");
6713
+ if (object === null || attribute === null) {
6714
+ return null;
6475
6715
  }
6476
- if (base.kind === "stop") {
6477
- return base;
6716
+ const base = moduleAt(object, site, ctx);
6717
+ return base === null ? null : memberKey(base, attribute.text, ctx);
6718
+ }
6719
+ function openImportMember(file, name, ctx, visited) {
6720
+ if (visited.has(file.file)) {
6721
+ return null;
6478
6722
  }
6479
- return stop("noDeclaration");
6723
+ visited.add(file.file);
6724
+ const found = /* @__PURE__ */ new Set();
6725
+ for (const spec of file.module.openImports) {
6726
+ const dots = spec.length - spec.replace(/^\.+/, "").length;
6727
+ const opened = moduleNamed(file, spec.slice(dots), dots, ctx);
6728
+ if (opened === null || opened.kind !== "module") {
6729
+ continue;
6730
+ }
6731
+ const member = opened.file.module.moduleScope.bindings.has(name) ? { kind: "key", key: `${opened.file.file}#${name}` } : openImportMember(opened.file, name, ctx, visited);
6732
+ if (member?.kind === "key") {
6733
+ found.add(member.key);
6734
+ }
6735
+ }
6736
+ if (found.size > 1) {
6737
+ return { kind: "stopped", reason: "multipleSources" };
6738
+ }
6739
+ const only = [...found][0];
6740
+ return only === void 0 ? null : { kind: "key", key: only };
6480
6741
  }
6481
- function memberOfClass(file, classNode, name, ctx) {
6482
- const classScope = file.module.scopeFor.get(classNode.id);
6483
- const binding = classScope?.bindings.get(name);
6484
- if (binding === void 0) {
6485
- return stop("noDeclaration");
6742
+ function moduleAt(node, site, ctx) {
6743
+ if (node.type === "identifier") {
6744
+ return importedModule(node.text, site, ctx);
6486
6745
  }
6487
- if (binding.kind === "functionDef") {
6488
- return {
6489
- kind: "function",
6490
- target: reachedFunction(file, binding.node, classNode)
6491
- };
6746
+ if (node.type !== "attribute") {
6747
+ return null;
6748
+ }
6749
+ const object = field(node, "object");
6750
+ const attribute = field(node, "attribute");
6751
+ if (object === null || attribute === null) {
6752
+ return null;
6492
6753
  }
6493
- return resolveBinding(binding, file, classScope, ctx, /* @__PURE__ */ new Set());
6754
+ const base = moduleAt(object, site, ctx);
6755
+ return base === null ? null : submoduleOf(base, attribute.text, ctx);
6494
6756
  }
6495
- function memberOfModule(file, name, ctx, visited) {
6496
- const key = `${file.file}#${name}`;
6497
- if (visited.has(key)) {
6498
- return stop("unsettledValue");
6757
+ function importedModule(name, site, ctx) {
6758
+ const binding = resolveName(site.scope, name);
6759
+ if (binding?.kind === "import") {
6760
+ const head = binding.module.split(".")[0];
6761
+ const dotted = binding.localName === head ? binding.localName : binding.module;
6762
+ return moduleNamed(site.file, dotted, 0, ctx);
6499
6763
  }
6500
- visited.add(key);
6501
- const binding = file.module.moduleScope.bindings.get(name);
6502
- if (binding !== void 0) {
6503
- return resolveBinding(binding, file, file.module.moduleScope, ctx, visited);
6764
+ if (binding?.kind !== "importFrom") {
6765
+ return null;
6504
6766
  }
6505
- if (file.file.endsWith("__init__.py")) {
6506
- const submodule = moduleNamed(file, name, 1, ctx);
6507
- if (submodule.kind === "module") {
6508
- return submodule;
6509
- }
6767
+ const from = moduleNamed(
6768
+ site.file,
6769
+ binding.module,
6770
+ binding.relativeLevel,
6771
+ ctx
6772
+ );
6773
+ return from === null ? null : submoduleOf(from, binding.importedName, ctx);
6774
+ }
6775
+ function submoduleOf(base, name, ctx) {
6776
+ if (base.kind === "package") {
6777
+ return moduleNamed(
6778
+ base.from,
6779
+ base.dotted === "" ? name : `${base.dotted}.${name}`,
6780
+ base.relativeLevel,
6781
+ ctx
6782
+ );
6510
6783
  }
6511
- return throughOpenImports(file, name, ctx, visited);
6784
+ return base.file.file.endsWith("__init__.py") ? moduleNamed(base.file, name, 1, ctx) : null;
6512
6785
  }
6513
- function throughOpenImports(file, name, ctx, visited) {
6514
- const found = [];
6515
- for (const spec of file.module.openImports) {
6516
- const dots = spec.length - spec.replace(/^\.+/, "").length;
6517
- const opened = moduleNamed(file, spec.slice(dots), dots, ctx);
6518
- if (opened.kind !== "module") {
6519
- continue;
6520
- }
6521
- const member = memberOfModule(opened.file, name, ctx, visited);
6522
- if (member.kind !== "stop") {
6523
- found.push(member);
6524
- }
6786
+ function memberKey(base, name, ctx) {
6787
+ if (base.kind === "package") {
6788
+ return null;
6525
6789
  }
6526
- if (found.length > 1) {
6527
- return stop("multipleSources");
6790
+ if (base.file.module.moduleScope.bindings.has(name)) {
6791
+ return { kind: "key", key: `${base.file.file}#${name}` };
6792
+ }
6793
+ return openImportMember(base.file, name, ctx, /* @__PURE__ */ new Set());
6794
+ }
6795
+ function moduleNamed(from, module, relativeLevel, ctx) {
6796
+ const resolution = resolveModule(
6797
+ from.file,
6798
+ { module, relativeLevel },
6799
+ { roots: ctx.roots }
6800
+ );
6801
+ if (resolution.status !== "resolved") {
6802
+ return resolution.reason === "external" ? { kind: "package", dotted: module, relativeLevel, from } : null;
6528
6803
  }
6529
- return found[0] ?? stop("noDeclaration");
6804
+ const file = ctx.filesByPath.get(resolution.file);
6805
+ return file === void 0 ? null : { kind: "module", file };
6530
6806
  }
6531
6807
 
6532
6808
  // src/reach/closure.ts
@@ -6541,7 +6817,9 @@ var REACHABLE_RULES = [
6541
6817
  function reachedFunctions(seeds, options) {
6542
6818
  const ctx = {
6543
6819
  filesByPath: new Map(options.files.map((file) => [file.file, file])),
6544
- roots: options.roots
6820
+ roots: options.roots,
6821
+ facts: options.facts,
6822
+ definitions: options.definitions
6545
6823
  };
6546
6824
  const db = new Database();
6547
6825
  const functionByKey = /* @__PURE__ */ new Map();
@@ -6563,21 +6841,26 @@ function reachedFunctions(seeds, options) {
6563
6841
  db.add("entry", [seed.key]);
6564
6842
  }
6565
6843
  for (; ; ) {
6566
- evaluate2(db, REACHABLE_RULES);
6844
+ evaluate(db, REACHABLE_RULES);
6567
6845
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
6568
6846
  if (frontier.length === 0) {
6569
6847
  break;
6570
6848
  }
6571
- for (const key of frontier) {
6849
+ const bodies = frontier.flatMap((key) => {
6572
6850
  scanned.add(key);
6573
6851
  const source = functionByKey.get(key);
6574
- if (source === void 0) {
6575
- continue;
6576
- }
6852
+ return source === void 0 ? [] : [{ key, source, ...bodyOf(source) }];
6853
+ });
6854
+ const spellings = calleeSpellings(
6855
+ bodies.flatMap((body) => body.written),
6856
+ ctx
6857
+ );
6858
+ for (const { key, source, body, written } of bodies) {
6577
6859
  const scan = scanBody(
6578
6860
  source,
6579
6861
  ctx,
6580
- recognizedCallIds(source.node, options.storageFor(source.file))
6862
+ recognizedCallIds(source.node, options.storageFor(source.file)),
6863
+ { body, written, spellings }
6581
6864
  );
6582
6865
  if (scan.stops.length > 0) {
6583
6866
  stopsByKey.set(key, scan.stops);
@@ -6643,7 +6926,18 @@ var EMPTY_SCAN = {
6643
6926
  parameterCalls: [],
6644
6927
  passedPositions: /* @__PURE__ */ new Set()
6645
6928
  };
6646
- function scanBody(source, ctx, recognized) {
6929
+ function bodyOf(source) {
6930
+ const { file, node } = source;
6931
+ const body = field(node, "body");
6932
+ if (body === null) {
6933
+ return { body, written: [] };
6934
+ }
6935
+ return {
6936
+ body,
6937
+ written: callsWritten(file, body, scopeAt2(file, node), keyOf(source))
6938
+ };
6939
+ }
6940
+ function scanBody(source, ctx, recognized, read) {
6647
6941
  const followed = [];
6648
6942
  const stops = [];
6649
6943
  const placements = new TargetPlacements();
@@ -6652,12 +6946,12 @@ function scanBody(source, ctx, recognized) {
6652
6946
  const seen = /* @__PURE__ */ new Set();
6653
6947
  const parameterCallsSeen = /* @__PURE__ */ new Set();
6654
6948
  const { file, node } = source;
6655
- const body = field(node, "body");
6656
- if (body === null) {
6949
+ const { written, spellings } = read;
6950
+ if (read.body === null) {
6657
6951
  return EMPTY_SCAN;
6658
6952
  }
6659
6953
  const ownParameters = callParameterNames(node, source.exportPath.length > 1);
6660
- const recordPassedArgs = (call, callee, calleeKey, where) => {
6954
+ const recordPassedArgs = (call, callee, calleeKey) => {
6661
6955
  const args = field(call, "arguments");
6662
6956
  if (args === null) {
6663
6957
  return;
@@ -6666,9 +6960,8 @@ function scanBody(source, ctx, recognized) {
6666
6960
  if (arg === null || arg.type !== "identifier") {
6667
6961
  return;
6668
6962
  }
6669
- const resolved = resolveNamedFunctionArgument(
6670
- arg.text,
6671
- { file, scope: where.scope, rebound: where.rebound },
6963
+ const resolved = functionNamed(
6964
+ readKey(file.file, arg, enclosingFunction(arg)),
6672
6965
  ctx
6673
6966
  );
6674
6967
  if (resolved === null) {
@@ -6688,13 +6981,9 @@ function scanBody(source, ctx, recognized) {
6688
6981
  }
6689
6982
  });
6690
6983
  };
6691
- const record = (call, where) => {
6984
+ const record = (call, site) => {
6692
6985
  const callee = calleeText(call);
6693
- const outcome = resolveCallee2(
6694
- call,
6695
- { file, scope: where.scope, rebound: where.rebound },
6696
- ctx
6697
- );
6986
+ const outcome = resolveCallee2(call, site, ctx, spellings);
6698
6987
  const placed = outcome.kind === "followed" ? {
6699
6988
  file: outcome.target.file.displayPath,
6700
6989
  span: spanOf(outcome.target.node)
@@ -6703,8 +6992,7 @@ function scanBody(source, ctx, recognized) {
6703
6992
  recordPassedArgs(
6704
6993
  call,
6705
6994
  callee,
6706
- outcome.kind === "followed" ? keyOf(outcome.target) : null,
6707
- where
6995
+ outcome.kind === "followed" ? keyOf(outcome.target) : null
6708
6996
  );
6709
6997
  if (outcome.kind === "stopped") {
6710
6998
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6727,32 +7015,9 @@ function scanBody(source, ctx, recognized) {
6727
7015
  followed.push(outcome.target);
6728
7016
  }
6729
7017
  };
6730
- walkDescendants3(
6731
- body,
6732
- { scope: scopeAt2(file, node), rebound: reboundNames(body) },
6733
- {
6734
- at: (child, where) => {
6735
- if (child.type === "call") {
6736
- record(child, where);
6737
- }
6738
- },
6739
- into: (child, where) => {
6740
- if (child.type === "function_definition") {
6741
- return SKIP_CHILDREN2;
6742
- }
6743
- if (child.type === "lambda") {
6744
- return {
6745
- scope: where.scope,
6746
- rebound: /* @__PURE__ */ new Set([...where.rebound, ...lambdaParameters(child)])
6747
- };
6748
- }
6749
- return {
6750
- scope: file.module.scopeFor.get(child.id) ?? where.scope,
6751
- rebound: where.rebound
6752
- };
6753
- }
6754
- }
6755
- );
7018
+ for (const { call, site } of written) {
7019
+ record(call, site);
7020
+ }
6756
7021
  return {
6757
7022
  followed,
6758
7023
  stops,
@@ -6762,6 +7027,18 @@ function scanBody(source, ctx, recognized) {
6762
7027
  passedPositions
6763
7028
  };
6764
7029
  }
7030
+ function callsWritten(file, body, outer, owner) {
7031
+ const found = [];
7032
+ walkDescendants3(body, outer, {
7033
+ at: (child, scope) => {
7034
+ if (child.type === "call") {
7035
+ found.push({ call: child, site: { file, scope, owner } });
7036
+ }
7037
+ },
7038
+ into: (child, scope) => child.type === "function_definition" ? SKIP_CHILDREN2 : file.module.scopeFor.get(child.id) ?? scope
7039
+ });
7040
+ return found;
7041
+ }
6765
7042
  function scopeAt2(file, node) {
6766
7043
  let current = node;
6767
7044
  while (current !== null) {
@@ -6773,40 +7050,6 @@ function scopeAt2(file, node) {
6773
7050
  }
6774
7051
  return file.module.moduleScope;
6775
7052
  }
6776
- var REBINDING_FIELDS = {
6777
- assignment: "left",
6778
- augmented_assignment: "left",
6779
- named_expression: "name",
6780
- for_statement: "left",
6781
- for_in_clause: "left",
6782
- as_pattern: "alias"
6783
- };
6784
- function reboundNames(body) {
6785
- const names = /* @__PURE__ */ new Set();
6786
- walkDescendants3(body, null, {
6787
- at: (node) => {
6788
- const target = REBINDING_FIELDS[node.type];
6789
- if (target === void 0 || boundByBinder(node, body)) {
6790
- return;
6791
- }
6792
- const written = field(node, target);
6793
- if (written !== null) {
6794
- for (const name of identifiersUnder(written)) {
6795
- names.add(name);
6796
- }
6797
- }
6798
- },
6799
- into: (node) => node.type === "function_definition" ? SKIP_CHILDREN2 : null
6800
- });
6801
- return names;
6802
- }
6803
- function boundByBinder(node, body) {
6804
- if (node.type !== "assignment") {
6805
- return false;
6806
- }
6807
- const statement = node.parent;
6808
- return field(node, "left")?.type === "identifier" && statement?.type === "expression_statement" && statement.parent?.id === body.id;
6809
- }
6810
7053
  function identifiersUnder(node, found = []) {
6811
7054
  if (node.type === "identifier") {
6812
7055
  found.push(node.text);
@@ -6819,10 +7062,6 @@ function identifiersUnder(node, found = []) {
6819
7062
  }
6820
7063
  return found;
6821
7064
  }
6822
- function lambdaParameters(lambda) {
6823
- const parameters = field(lambda, "parameters");
6824
- return parameters === null ? [] : identifiersUnder(parameters);
6825
- }
6826
7065
  function libraryUnit(target, options) {
6827
7066
  const { file, node, name, exportPath } = target;
6828
7067
  const body = field(node, "body");
@@ -6973,6 +7212,18 @@ var PythonWrapperIndex = class {
6973
7212
  ownRoutes = /* @__PURE__ */ new Map();
6974
7213
  unitsByKey = /* @__PURE__ */ new Map();
6975
7214
  unitsByFile = /* @__PURE__ */ new Map();
7215
+ /** The project function a module-level name in this file refers to, or null for anything else. */
7216
+ functionCalled(file, name) {
7217
+ if (this.options.facts === void 0) {
7218
+ return null;
7219
+ }
7220
+ return functionNamed(nameKeyIn(file, null, name), {
7221
+ filesByPath: this.filesByPath,
7222
+ roots: this.options.roots,
7223
+ facts: this.options.facts,
7224
+ definitions: this.options.definitions
7225
+ });
7226
+ }
6976
7227
  register(covers, target, declared) {
6977
7228
  const registered = this.registered(target, declared);
6978
7229
  const table = covers.kind === "everyRoute" ? this.everyRoute : this.ownRoutes;
@@ -7019,20 +7270,12 @@ var PythonWrapperIndex = class {
7019
7270
  if (declared.form.type !== "dependency") {
7020
7271
  continue;
7021
7272
  }
7022
- const site = {
7023
- file,
7024
- scope: query.module.moduleScope,
7025
- rebound: /* @__PURE__ */ new Set()
7026
- };
7027
7273
  const targets = [
7028
7274
  ...decoratorDependencies(query.classification, declared.form),
7029
7275
  ...parameterDependencies(query.definitionNode, declared.form)
7030
7276
  ];
7031
7277
  for (const name of targets) {
7032
- const target = resolveNamedFunctionArgument(name, site, {
7033
- filesByPath: this.filesByPath,
7034
- roots: this.options.roots
7035
- });
7278
+ const target = this.functionCalled(file.file, name);
7036
7279
  if (target !== null) {
7037
7280
  found.push(this.registered(target, declared));
7038
7281
  }
@@ -7174,12 +7417,8 @@ function registerConstructorDependencies(file, declared, form, index) {
7174
7417
  if (listed === void 0) {
7175
7418
  continue;
7176
7419
  }
7177
- const site = { file, scope, rebound: /* @__PURE__ */ new Set() };
7178
7420
  for (const name of dependencyNamesIn(listed.node, form)) {
7179
- const target = resolveNamedFunctionArgument(name, site, {
7180
- filesByPath: index.filesByPath,
7181
- roots: index.options.roots
7182
- });
7421
+ const target = index.functionCalled(file.file, name);
7183
7422
  if (target !== null) {
7184
7423
  index.register(
7185
7424
  coverageOf(match, file, declared.pack),
@@ -7507,6 +7746,7 @@ async function extractPythonProject(options) {
7507
7746
  packs: options.packs,
7508
7747
  roots: options.roots,
7509
7748
  facts: needsValues ? db : void 0,
7749
+ definitions,
7510
7750
  storageFor
7511
7751
  })
7512
7752
  );
@@ -7590,7 +7830,9 @@ async function extractPythonProject(options) {
7590
7830
  files: bound,
7591
7831
  roots: options.roots,
7592
7832
  gapHandling,
7593
- storageFor
7833
+ storageFor,
7834
+ facts: db,
7835
+ definitions
7594
7836
  })
7595
7837
  );
7596
7838
  for (const [key, owners] of summariesBySeed) {
@@ -7852,7 +8094,7 @@ var PythonWhySession = class {
7852
8094
  explain(value, options = {}) {
7853
8095
  return explainResolvedKey({
7854
8096
  db: this.db,
7855
- rules: RESOLUTION_RULES2,
8097
+ rules: RESOLUTION_RULES,
7856
8098
  key: readKey(value.file, value.node, enclosingFunction(value.node)),
7857
8099
  locate: (key) => this.locate(key),
7858
8100
  displayPath: (key) => this.displayPath(key),
@@ -7982,6 +8224,7 @@ export {
7982
8224
  emitValueFacts,
7983
8225
  extractPythonProject,
7984
8226
  findPythonFiles,
8227
+ nameKeyIn,
7985
8228
  nodeId,
7986
8229
  objectReturnedBy,
7987
8230
  parsePython,