@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.cjs CHANGED
@@ -43,6 +43,7 @@ __export(index_exports, {
43
43
  emitValueFacts: () => emitValueFacts,
44
44
  extractPythonProject: () => extractPythonProject,
45
45
  findPythonFiles: () => findPythonFiles,
46
+ nameKeyIn: () => nameKeyIn,
46
47
  nodeId: () => nodeId,
47
48
  objectReturnedBy: () => objectReturnedBy,
48
49
  parsePython: () => parsePython,
@@ -90,6 +91,11 @@ function isType(node, ...types) {
90
91
  function isFunction(node) {
91
92
  return node.type === "function_definition" || node.type === "lambda";
92
93
  }
94
+ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([
95
+ "function_definition",
96
+ "lambda",
97
+ "class_definition"
98
+ ]);
93
99
  function enclosingFunction(node) {
94
100
  let current = node.parent;
95
101
  while (current !== null) {
@@ -1464,14 +1470,14 @@ function invocationEffects(definitionNode) {
1464
1470
  if (calls.length === 0) {
1465
1471
  return [];
1466
1472
  }
1467
- const statementOf2 = /* @__PURE__ */ new Map();
1473
+ const statementOf3 = /* @__PURE__ */ new Map();
1468
1474
  const statements = [];
1469
1475
  for (const call of calls) {
1470
1476
  const statement = enclosingStatement(call, body);
1471
1477
  if (statement === null) {
1472
1478
  continue;
1473
1479
  }
1474
- statementOf2.set(call.id, statement);
1480
+ statementOf3.set(call.id, statement);
1475
1481
  statements.push(statement);
1476
1482
  }
1477
1483
  const lowered = lowerPythonBody(body, statements);
@@ -1483,7 +1489,7 @@ function invocationEffects(definitionNode) {
1483
1489
  statements
1484
1490
  );
1485
1491
  return calls.map((call) => {
1486
- const statement = statementOf2.get(call.id);
1492
+ const statement = statementOf3.get(call.id);
1487
1493
  const conditions = (0, import_extractor3.sharedGatingConditions)(
1488
1494
  statement === void 0 ? void 0 : enumerated.byTerminal.get(statement),
1489
1495
  predicateOf
@@ -1499,36 +1505,20 @@ function invocationEffects(definitionNode) {
1499
1505
  }
1500
1506
 
1501
1507
  // src/routers.ts
1502
- var import_resolution3 = require("@suss/resolution");
1508
+ var import_resolution4 = require("@suss/resolution");
1503
1509
 
1504
1510
  // src/facts/resolve.ts
1505
- var import_datalog = require("@suss/datalog");
1506
1511
  var import_resolution = require("@suss/resolution");
1507
- var RESOLUTION_PROGRAM = (0, import_datalog.deriveOnDemand)(
1508
- [...import_resolution.RESOLUTION_RULES, ...import_resolution.RESOLUTION_QUESTIONS],
1509
- import_resolution.ANSWER_RELATIONS
1510
- );
1511
1512
  function resolveCalls(db, callKeys) {
1512
- const asked = new Set(db.facts("wanted").map((row) => String(row[0])));
1513
- const fresh = callKeys.filter((key) => !asked.has(key));
1514
- if (fresh.length === 0) {
1515
- return;
1516
- }
1517
- for (const callKey of fresh) {
1518
- db.add("wanted", [callKey]);
1519
- }
1520
- (0, import_datalog.evaluate)(db, RESOLUTION_PROGRAM.rules);
1513
+ (0, import_resolution.askResolution)(db, callKeys);
1521
1514
  }
1522
1515
  function writtenValueOf(db, key) {
1523
1516
  resolveCalls(db, [key]);
1524
1517
  return (0, import_resolution.writtenValueOf)(db, key, (keys) => resolveCalls(db, keys));
1525
1518
  }
1526
- function originsOf(db, nameKey) {
1527
- if (!db.facts("wantedOrigin").some((row) => String(row[0]) === nameKey)) {
1528
- db.add("wantedOrigin", [nameKey]);
1529
- (0, import_datalog.evaluate)(db, RESOLUTION_PROGRAM.rules);
1530
- }
1531
- return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
1519
+ function originsOf(db, nameKey2) {
1520
+ (0, import_resolution.askResolution)(db, [nameKey2], "wantedOrigin");
1521
+ return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey2).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
1532
1522
  }
1533
1523
  function subjectConstructions(db, valueKeys) {
1534
1524
  const found = /* @__PURE__ */ new Map();
@@ -1536,10 +1526,7 @@ function subjectConstructions(db, valueKeys) {
1536
1526
  return found;
1537
1527
  }
1538
1528
  const askSubjects = (keys) => {
1539
- for (const key of keys) {
1540
- db.add("wantedSubject", [key]);
1541
- }
1542
- (0, import_datalog.evaluate)(db, RESOLUTION_PROGRAM.rules);
1529
+ (0, import_resolution.askResolution)(db, keys, "wantedSubject");
1543
1530
  };
1544
1531
  askSubjects(valueKeys);
1545
1532
  const placeholders = (0, import_resolution.placeholderValues)(db);
@@ -1586,6 +1573,7 @@ function objectReturnedBy(db, callKey) {
1586
1573
  }
1587
1574
 
1588
1575
  // src/facts/values.ts
1576
+ var import_resolution2 = require("@suss/resolution");
1589
1577
  function nodeId(filePath, node) {
1590
1578
  return `${filePath}:${node.startIndex}-${node.endIndex}`;
1591
1579
  }
@@ -1596,10 +1584,10 @@ function readKey(filePath, node, enclosing) {
1596
1584
  if (node.type !== "identifier") {
1597
1585
  return nodeId(filePath, node);
1598
1586
  }
1599
- if (enclosing !== null && parameterNames(enclosing).has(node.text)) {
1600
- return `${nodeId(filePath, enclosing)}#${node.text}`;
1601
- }
1602
- return nameId(filePath, node.text);
1587
+ return nameKeyIn(filePath, enclosing, node.text);
1588
+ }
1589
+ function nameKeyIn(filePath, enclosing, name) {
1590
+ return nameKey(filePath, scopeChainOf(filePath, enclosing), name);
1603
1591
  }
1604
1592
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
1605
1593
  "string",
@@ -1637,9 +1625,6 @@ function parameterName2(param) {
1637
1625
  }
1638
1626
  return children(param).find((child) => child.type === "identifier") ?? null;
1639
1627
  }
1640
- function parameterList(fn) {
1641
- return parameterShapes(fn).map((parameter) => parameter.name);
1642
- }
1643
1628
  function parameterShapes(fn) {
1644
1629
  const params = field(fn, "parameters");
1645
1630
  const declared = [];
@@ -1654,24 +1639,52 @@ function parameterShapes(fn) {
1654
1639
  }
1655
1640
  return declared;
1656
1641
  }
1657
- function parameterNames(fn) {
1658
- return new Set(parameterList(fn));
1642
+ function boundParameterNames(fn) {
1643
+ const params = field(fn, "parameters");
1644
+ const names = [];
1645
+ for (const param of params === null ? [] : children(params)) {
1646
+ const name = parameterName2(param);
1647
+ if (name !== null) {
1648
+ names.push(name.text);
1649
+ }
1650
+ }
1651
+ return names;
1659
1652
  }
1660
1653
  function add(emitter, relation, ...tuple) {
1661
1654
  emitter.db.add(relation, tuple);
1662
1655
  }
1656
+ function nameKey(filePath, scope, name) {
1657
+ for (let current = scope; current !== null; current = current.parent) {
1658
+ if (current.globals.has(name)) {
1659
+ return nameId(filePath, name);
1660
+ }
1661
+ if (current.locals.has(name)) {
1662
+ return `${current.funcKey}#${name}`;
1663
+ }
1664
+ }
1665
+ return nameId(filePath, name);
1666
+ }
1667
+ function scopeChainOf(filePath, fn) {
1668
+ if (fn === null) {
1669
+ return null;
1670
+ }
1671
+ const reading = scopeReadingOf(fn);
1672
+ return {
1673
+ funcKey: nodeId(filePath, fn),
1674
+ locals: reading.locals,
1675
+ globals: reading.globals,
1676
+ parent: scopeChainOf(filePath, enclosingFunction(fn))
1677
+ };
1678
+ }
1663
1679
  function emitCall(emitter, call) {
1664
1680
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
1665
1681
  const callee = field(call, "function");
1666
- if (callee === null) {
1682
+ const args = field(call, "arguments");
1683
+ if (callee === null || args === null) {
1667
1684
  return;
1668
1685
  }
1669
1686
  const callKey = nodeId(emitter.filePath, call);
1670
1687
  add(emitter, "call", callKey, valueKey(emitter, callee));
1671
- const args = field(call, "arguments");
1672
- if (args === null) {
1673
- return;
1674
- }
1675
1688
  let position = 0;
1676
1689
  for (const argument of children(args)) {
1677
1690
  if (argument.type === "keyword_argument") {
@@ -1702,11 +1715,7 @@ function valueKey(emitter, value) {
1702
1715
  if (value.type !== "identifier") {
1703
1716
  return nodeId(emitter.filePath, value);
1704
1717
  }
1705
- const enclosing = emitter.enclosing;
1706
- if (enclosing !== null && enclosing.params.has(value.text)) {
1707
- return `${enclosing.funcKey}#${value.text}`;
1708
- }
1709
- return nameId(emitter.filePath, value.text);
1718
+ return nameKey(emitter.filePath, emitter.enclosing, value.text);
1710
1719
  }
1711
1720
  function emitSequence(emitter, sequence) {
1712
1721
  const objectKey = nodeId(emitter.filePath, sequence);
@@ -1796,7 +1805,6 @@ function emitFunctionFacts(emitter, fn, classKey) {
1796
1805
  const funcKey = nodeId(emitter.filePath, fn);
1797
1806
  add(emitter, "func", funcKey);
1798
1807
  const params = field(fn, "parameters");
1799
- const declared = parameterNames(fn);
1800
1808
  let position = classKey === void 0 ? 0 : -1;
1801
1809
  let byPosition = true;
1802
1810
  let receiver = null;
@@ -1819,24 +1827,22 @@ function emitFunctionFacts(emitter, fn, classKey) {
1819
1827
  }
1820
1828
  position += 1;
1821
1829
  }
1830
+ const reading = scopeReadingOf(fn);
1822
1831
  const inside = {
1823
1832
  ...emitter,
1824
- enclosing: { funcKey, params: declared }
1833
+ enclosing: {
1834
+ funcKey,
1835
+ locals: reading.locals,
1836
+ globals: reading.globals,
1837
+ parent: emitter.enclosing
1838
+ }
1825
1839
  };
1840
+ emitScopeWrites(inside, reading, false);
1826
1841
  const body = field(fn, "body");
1827
1842
  if (body === null) {
1828
1843
  return funcKey;
1829
1844
  }
1830
- const recordNested = (node) => {
1831
- for (const child of children(node)) {
1832
- if (isFunction(child)) {
1833
- add(emitter, "containsFn", funcKey, nodeId(emitter.filePath, child));
1834
- continue;
1835
- }
1836
- recordNested(child);
1837
- }
1838
- };
1839
- recordNested(body);
1845
+ emitNestedDefinitions(inside, body);
1840
1846
  walkExpressions(inside, body, (child) => {
1841
1847
  if (child.type === "return_statement") {
1842
1848
  const returned = child.namedChildren[0];
@@ -1847,29 +1853,17 @@ function emitFunctionFacts(emitter, fn, classKey) {
1847
1853
  if (child.type === "call") {
1848
1854
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
1849
1855
  }
1850
- if (child.type === "assignment") {
1851
- emitBinding(inside, child, receiver);
1856
+ if (child.type === "assignment" && receiver !== null) {
1857
+ emitReceiverProperty(inside, child, receiver);
1852
1858
  }
1853
1859
  emitExpressionFact(inside, child);
1854
1860
  });
1855
1861
  return funcKey;
1856
1862
  }
1857
- function emitBinding(emitter, assignment2, receiver) {
1863
+ function emitReceiverProperty(emitter, assignment2, receiver) {
1858
1864
  const left = field(assignment2, "left");
1859
1865
  const right = field(assignment2, "right");
1860
- if (left === null || right === null) {
1861
- return;
1862
- }
1863
- if (left.type === "identifier") {
1864
- add(
1865
- emitter,
1866
- "binds",
1867
- nameId(emitter.filePath, left.text),
1868
- valueKey(emitter, assignedValue(right))
1869
- );
1870
- return;
1871
- }
1872
- if (receiver === null || left.type !== "attribute") {
1866
+ if (left === null || right === null || left.type !== "attribute") {
1873
1867
  return;
1874
1868
  }
1875
1869
  const object = field(left, "object");
@@ -1885,21 +1879,6 @@ function emitBinding(emitter, assignment2, receiver) {
1885
1879
  valueKey(emitter, right)
1886
1880
  );
1887
1881
  }
1888
- function emitAssignment(emitter, assignment2) {
1889
- emitBinding(emitter, assignment2, null);
1890
- const left = field(assignment2, "left");
1891
- const right = field(assignment2, "right");
1892
- if (left === null || right === null || left.type !== "identifier") {
1893
- return;
1894
- }
1895
- add(
1896
- emitter,
1897
- "exportsAs",
1898
- emitter.filePath,
1899
- left.text,
1900
- valueKey(emitter, assignedValue(right))
1901
- );
1902
- }
1903
1882
  function assignedValue(right) {
1904
1883
  if (right.type !== "subscript") {
1905
1884
  return right;
@@ -1950,40 +1929,361 @@ function emitClassFacts(emitter, cls) {
1950
1929
  }
1951
1930
  continue;
1952
1931
  }
1953
- const memberKey = declaredMemberKey(emitter, member, classKey);
1932
+ const memberKey2 = declaredMemberKey(emitter, member, classKey);
1954
1933
  const name = field(member, "name");
1955
- if (memberKey !== null && name !== null) {
1956
- add(emitter, "holdsProperty", classKey, name.text, memberKey);
1934
+ if (memberKey2 !== null && name !== null) {
1935
+ add(emitter, "holdsProperty", classKey, name.text, memberKey2);
1957
1936
  }
1958
1937
  }
1959
1938
  return classKey;
1960
1939
  }
1961
- function emitValueFacts(db, filePath, root) {
1962
- const emitter = { db, filePath, enclosing: null };
1963
- const declaresName = (child, key) => {
1964
- const name = field(child, "name");
1965
- if (name === null) {
1940
+ function emitNestedDefinitions(emitter, node) {
1941
+ for (const child of children(node)) {
1942
+ if (isFunction(child)) {
1943
+ emitFunctionFacts(emitter, child);
1944
+ if (emitter.enclosing !== null) {
1945
+ add(
1946
+ emitter,
1947
+ "containsFn",
1948
+ emitter.enclosing.funcKey,
1949
+ nodeId(emitter.filePath, child)
1950
+ );
1951
+ }
1952
+ continue;
1953
+ }
1954
+ if (child.type === "class_definition") {
1955
+ emitClassFacts(emitter, child);
1956
+ continue;
1957
+ }
1958
+ emitNestedDefinitions(emitter, child);
1959
+ }
1960
+ }
1961
+ var readingsByTree = /* @__PURE__ */ new WeakMap();
1962
+ function scopeReadingOf(fn) {
1963
+ const tree = fn.tree;
1964
+ let byNode = readingsByTree.get(tree);
1965
+ if (byNode === void 0) {
1966
+ byNode = /* @__PURE__ */ new Map();
1967
+ readingsByTree.set(tree, byNode);
1968
+ }
1969
+ const remembered = byNode.get(fn.id);
1970
+ if (remembered !== void 0) {
1971
+ return remembered;
1972
+ }
1973
+ const body = field(fn, "body");
1974
+ const reading = readScope(
1975
+ body !== null && body.type === "block" ? body : null,
1976
+ boundParameterNames(fn)
1977
+ );
1978
+ byNode.set(fn.id, reading);
1979
+ return reading;
1980
+ }
1981
+ var TARGET_PATTERN_TYPES = /* @__PURE__ */ new Set([
1982
+ "pattern_list",
1983
+ "tuple_pattern",
1984
+ "list_pattern"
1985
+ ]);
1986
+ function targetNames(target, found = []) {
1987
+ if (target.type === "identifier") {
1988
+ found.push(target);
1989
+ return found;
1990
+ }
1991
+ if (TARGET_PATTERN_TYPES.has(target.type)) {
1992
+ for (const child of children(target)) {
1993
+ targetNames(child, found);
1994
+ }
1995
+ }
1996
+ return found;
1997
+ }
1998
+ function readAssignment(node, sink) {
1999
+ const left = field(node, "left");
2000
+ const right = field(node, "right");
2001
+ if (left === null) {
2002
+ return;
2003
+ }
2004
+ const written = right === null ? null : assignedValue(right);
2005
+ const names = targetNames(left);
2006
+ for (const name of names) {
2007
+ sink.record(name.text, {
2008
+ // An unpacked target takes one piece of the value, and which piece
2009
+ // is not something the assignment says.
2010
+ value: names.length === 1 ? written : null,
2011
+ given: null,
2012
+ at: node,
2013
+ direct: isDirectStatement(node, sink.bodyOwner)
2014
+ });
2015
+ }
2016
+ }
2017
+ function readAugmentedAssignment(node, sink) {
2018
+ const left = field(node, "left");
2019
+ if (left === null || left.type !== "identifier") {
2020
+ return;
2021
+ }
2022
+ sink.record(left.text, {
2023
+ value: null,
2024
+ given: null,
2025
+ at: node,
2026
+ direct: isDirectStatement(node, sink.bodyOwner)
2027
+ });
2028
+ }
2029
+ function readIterationTarget(node, sink) {
2030
+ const left = field(node, "left");
2031
+ if (left === null) {
2032
+ return;
2033
+ }
2034
+ for (const name of targetNames(left)) {
2035
+ sink.record(name.text, {
2036
+ value: null,
2037
+ given: null,
2038
+ at: node,
2039
+ direct: false
2040
+ });
2041
+ }
2042
+ }
2043
+ function identifierOf(node) {
2044
+ if (node.type === "identifier") {
2045
+ return node;
2046
+ }
2047
+ return children(node).find((child) => child.type === "identifier") ?? null;
2048
+ }
2049
+ function readAsPattern(node, sink) {
2050
+ const target = field(node, "alias") ?? children(node)[1];
2051
+ const name = target === void 0 ? null : identifierOf(target);
2052
+ if (name === null) {
2053
+ return;
2054
+ }
2055
+ sink.record(name.text, {
2056
+ value: null,
2057
+ given: null,
2058
+ at: node,
2059
+ direct: false
2060
+ });
2061
+ }
2062
+ function readWalrus(node, sink) {
2063
+ const name = field(node, "name");
2064
+ if (name === null || name.type !== "identifier") {
2065
+ return;
2066
+ }
2067
+ sink.record(name.text, {
2068
+ value: field(node, "value"),
2069
+ given: null,
2070
+ at: node,
2071
+ direct: false
2072
+ });
2073
+ }
2074
+ function readGlobalStatement(node, sink) {
2075
+ for (const child of children(node)) {
2076
+ if (child.type === "identifier") {
2077
+ sink.globals.add(child.text);
2078
+ }
2079
+ }
2080
+ }
2081
+ function readNonlocalStatement(node, sink) {
2082
+ for (const child of children(node)) {
2083
+ if (child.type === "identifier") {
2084
+ sink.nonlocals.add(child.text);
2085
+ }
2086
+ }
2087
+ }
2088
+ var WRITE_READERS = {
2089
+ assignment: readAssignment,
2090
+ augmented_assignment: readAugmentedAssignment,
2091
+ for_statement: readIterationTarget,
2092
+ for_in_clause: readIterationTarget,
2093
+ as_pattern: readAsPattern,
2094
+ named_expression: readWalrus,
2095
+ global_statement: readGlobalStatement,
2096
+ nonlocal_statement: readNonlocalStatement
2097
+ };
2098
+ function readScope(bodyOwner, parameterNames) {
2099
+ const writes = /* @__PURE__ */ new Map();
2100
+ const globals = /* @__PURE__ */ new Set();
2101
+ const nonlocals = /* @__PURE__ */ new Set();
2102
+ const record = (name, write) => {
2103
+ const existing = writes.get(name);
2104
+ if (existing === void 0) {
2105
+ writes.set(name, [write]);
1966
2106
  return;
1967
2107
  }
1968
- add(emitter, "binds", nameId(filePath, name.text), key);
1969
- add(emitter, "exportsAs", filePath, name.text, key);
2108
+ existing.push(write);
1970
2109
  };
1971
- const walk = (node) => {
1972
- for (const child of children(node)) {
1973
- if (child.type === "class_definition") {
1974
- declaresName(child, emitClassFacts(emitter, child));
1975
- continue;
1976
- }
1977
- if (isFunction(child)) {
1978
- declaresName(child, emitFunctionFacts(emitter, child));
1979
- }
1980
- if (child.type === "assignment") {
1981
- emitAssignment(emitter, child);
2110
+ const sink = { bodyOwner, globals, nonlocals, record };
2111
+ const visit = (node) => {
2112
+ if (isFunction(node) || node.type === "class_definition") {
2113
+ const name = field(node, "name");
2114
+ if (name !== null) {
2115
+ record(name.text, {
2116
+ value: node,
2117
+ given: null,
2118
+ at: node,
2119
+ direct: isDirectStatement(node, bodyOwner)
2120
+ });
1982
2121
  }
1983
- walk(child);
2122
+ return;
2123
+ }
2124
+ WRITE_READERS[node.type]?.(node, sink);
2125
+ for (const child of children(node)) {
2126
+ visit(child);
1984
2127
  }
1985
2128
  };
1986
- walk(root);
2129
+ for (const statement of bodyOwner === null ? [] : children(bodyOwner)) {
2130
+ visit(statement);
2131
+ }
2132
+ const locals = new Set(writes.keys());
2133
+ for (const name of [...globals, ...nonlocals]) {
2134
+ locals.delete(name);
2135
+ }
2136
+ for (const name of parameterNames) {
2137
+ locals.add(name);
2138
+ addParameterWrite(writes.get(name), name);
2139
+ }
2140
+ return { bodyOwner, writes, locals, globals };
2141
+ }
2142
+ function addParameterWrite(later, name) {
2143
+ const first = later?.[0];
2144
+ if (later === void 0 || first === void 0) {
2145
+ return;
2146
+ }
2147
+ later.unshift({ value: null, given: name, at: first.at, direct: false });
2148
+ }
2149
+ function statementOf(node) {
2150
+ let current = node;
2151
+ while (current !== null) {
2152
+ const parent = current.parent;
2153
+ if (parent === null) {
2154
+ return null;
2155
+ }
2156
+ if (parent.type === "block" || parent.type === "module") {
2157
+ return current;
2158
+ }
2159
+ current = parent;
2160
+ }
2161
+ return null;
2162
+ }
2163
+ function isDirectStatement(node, bodyOwner) {
2164
+ if (bodyOwner === null) {
2165
+ return false;
2166
+ }
2167
+ const statement = statementOf(node);
2168
+ return statement !== null && statement.parent?.id === bodyOwner.id;
2169
+ }
2170
+ var UNREAD_PARENTS = /* @__PURE__ */ new Set([
2171
+ "as_pattern_target",
2172
+ "global_statement",
2173
+ "nonlocal_statement",
2174
+ "pattern_list",
2175
+ "tuple_pattern",
2176
+ "list_pattern",
2177
+ "dotted_name"
2178
+ ]);
2179
+ var UNREAD_FIELDS = {
2180
+ assignment: "left",
2181
+ augmented_assignment: "left",
2182
+ for_statement: "left",
2183
+ for_in_clause: "left",
2184
+ named_expression: "name",
2185
+ function_definition: "name",
2186
+ class_definition: "name",
2187
+ attribute: "attribute",
2188
+ keyword_argument: "name"
2189
+ };
2190
+ function isNameRead(name) {
2191
+ const parent = name.parent;
2192
+ if (parent === null) {
2193
+ return true;
2194
+ }
2195
+ if (UNREAD_PARENTS.has(parent.type)) {
2196
+ return false;
2197
+ }
2198
+ const fieldName = UNREAD_FIELDS[parent.type];
2199
+ return fieldName === void 0 || field(parent, fieldName)?.id !== name.id;
2200
+ }
2201
+ var READ_FIRST = {
2202
+ call: "function",
2203
+ attribute: "object"
2204
+ };
2205
+ function readFirst(node) {
2206
+ if (node.type === "parenthesized_expression") {
2207
+ return children(node)[0] ?? null;
2208
+ }
2209
+ const fieldName = READ_FIRST[node.type];
2210
+ return fieldName === void 0 ? null : field(node, fieldName);
2211
+ }
2212
+ var NAME_READS = {
2213
+ nameTypes: /* @__PURE__ */ new Set(["identifier"]),
2214
+ laterBodies: LATER_BODY_TYPES,
2215
+ childrenOf: children,
2216
+ isRead: isNameRead,
2217
+ readFirst
2218
+ };
2219
+ var CONSTRUCTION_TYPES = /* @__PURE__ */ new Set([
2220
+ "call",
2221
+ "list",
2222
+ "dictionary",
2223
+ "set",
2224
+ "tuple",
2225
+ "list_comprehension",
2226
+ "dictionary_comprehension",
2227
+ "set_comprehension",
2228
+ "generator_expression"
2229
+ ]);
2230
+ function describeWrite(emitter, write, name) {
2231
+ if (write.value === null) {
2232
+ return {
2233
+ value: write.given,
2234
+ placeholder: false,
2235
+ construction: null,
2236
+ narrowsName: false
2237
+ };
2238
+ }
2239
+ return {
2240
+ value: valueKey(emitter, write.value),
2241
+ placeholder: write.value.type === "none",
2242
+ construction: CONSTRUCTION_TYPES.has(write.value.type) ? write.value.text.replace(/\s+/g, " ").trim() : null,
2243
+ narrowsName: write.value.type === "call" && (0, import_resolution2.startsAtName)(write.value, name, NAME_READS)
2244
+ };
2245
+ }
2246
+ function settledValue(emitter, reading, name, writes) {
2247
+ const only = writes.length === 1 ? writes[0] : void 0;
2248
+ if (only !== void 0) {
2249
+ return only.value === null ? only.given : valueKey(emitter, only.value);
2250
+ }
2251
+ return (0, import_resolution2.valueLeftByWrites)(
2252
+ writes.map((write) => describeWrite(emitter, write, name)),
2253
+ (0, import_resolution2.writesRunInOrder)(reading.bodyOwner, name, writes, NAME_READS)
2254
+ );
2255
+ }
2256
+ function emitScopeWrites(emitter, reading, atModule) {
2257
+ for (const [name, writes] of reading.writes) {
2258
+ const settled = settledValue(emitter, reading, name, writes);
2259
+ const key = nameKey(emitter.filePath, emitter.enclosing, name);
2260
+ if (settled === null) {
2261
+ emitCandidates(emitter, key, name, writes);
2262
+ continue;
2263
+ }
2264
+ add(emitter, writes.length === 1 ? "binds" : "endsHolding", key, settled);
2265
+ if (atModule) {
2266
+ add(emitter, "exportsAs", emitter.filePath, name, settled);
2267
+ }
2268
+ }
2269
+ }
2270
+ function emitCandidates(emitter, key, name, writes) {
2271
+ for (const write of writes) {
2272
+ if (write.value === null) {
2273
+ if (write.given === null) {
2274
+ add(emitter, "writesUnstated", key);
2275
+ }
2276
+ continue;
2277
+ }
2278
+ if (!describeWrite(emitter, write, name).narrowsName) {
2279
+ add(emitter, "mayHold", key, valueKey(emitter, write.value));
2280
+ }
2281
+ }
2282
+ }
2283
+ function emitValueFacts(db, filePath, root) {
2284
+ const emitter = { db, filePath, enclosing: null };
2285
+ emitNestedDefinitions(emitter, root);
2286
+ emitScopeWrites(emitter, readScope(root, []), true);
1987
2287
  emitExpressionFacts(emitter, root);
1988
2288
  }
1989
2289
 
@@ -2069,7 +2369,7 @@ function resolveModule(importingFile, spec, options) {
2069
2369
  }
2070
2370
 
2071
2371
  // src/values/evaluator.ts
2072
- var import_resolution2 = require("@suss/resolution");
2372
+ var import_resolution3 = require("@suss/resolution");
2073
2373
  var import_values3 = require("@suss/values");
2074
2374
 
2075
2375
  // src/values/lowering.ts
@@ -2096,7 +2396,7 @@ function pythonLowering(options) {
2096
2396
  return {
2097
2397
  idOf: (node) => node.id,
2098
2398
  expression: (node) => expressionOf(node, originOf2),
2099
- statement: statementOf,
2399
+ statement: statementOf2,
2100
2400
  siteOf,
2101
2401
  functionOf,
2102
2402
  writtenTo: (node) => context === null ? null : context.writtenTo(node),
@@ -2360,7 +2660,7 @@ var STATEMENT_TYPES = {
2360
2660
  body: blockStatements2(field(node, "body"))
2361
2661
  })
2362
2662
  };
2363
- function statementOf(node) {
2663
+ function statementOf2(node) {
2364
2664
  const lower = STATEMENT_TYPES[node.type];
2365
2665
  return lower === void 0 ? { kind: "opaque" } : lower(node);
2366
2666
  }
@@ -2857,7 +3157,7 @@ function contextOver(db, nodes) {
2857
3157
  return null;
2858
3158
  }
2859
3159
  const answer = writtenValueOf(db, key);
2860
- return answer === null ? null : (0, import_resolution2.nodeOfKey)(rootsByFile, answer);
3160
+ return answer === null ? null : (0, import_resolution3.nodeOfKey)(rootsByFile, answer);
2861
3161
  },
2862
3162
  callable: (call) => {
2863
3163
  const callee = field(call, "function");
@@ -2986,7 +3286,7 @@ function composedPrefixOf(index, construction, routerPath) {
2986
3286
  return { kind: "composed", value: composed.value + ownPrefix.value };
2987
3287
  }
2988
3288
  function composedMountPrefix(index, construction) {
2989
- const paths = (0, import_resolution3.mountPathsOf)(mountEdgesOf(index), construction.valueKey);
3289
+ const paths = (0, import_resolution4.mountPathsOf)(mountEdgesOf(index), construction.valueKey);
2990
3290
  if (paths === null) {
2991
3291
  return {
2992
3292
  kind: "abstain",
@@ -3409,9 +3709,13 @@ function constructionThroughFacts(name, scope, scan) {
3409
3709
  if (binding.kind !== "assignment" && binding.kind !== "importFrom") {
3410
3710
  return null;
3411
3711
  }
3412
- const nameKey = `${scan.bound.file}#${name}`;
3413
- const constructionKey2 = subjectConstructions(facts, [nameKey]).get(
3414
- nameKey
3712
+ const nameKey2 = nameKeyIn(
3713
+ scan.bound.file,
3714
+ scope.kind === "function" ? scope.node : enclosingFunction(scope.node),
3715
+ name
3716
+ );
3717
+ const constructionKey2 = subjectConstructions(facts, [nameKey2]).get(
3718
+ nameKey2
3415
3719
  )?.constructionKey;
3416
3720
  return constructionKey2 === void 0 ? null : scan.index.byValueKey.get(constructionKey2) ?? null;
3417
3721
  }
@@ -3502,11 +3806,11 @@ function walkStatements(statements, position, scan, visit) {
3502
3806
  visit(stmt, position, scan);
3503
3807
  }
3504
3808
  }
3505
- function targetNames(node) {
3809
+ function targetNames2(node) {
3506
3810
  if (node.type === "identifier") {
3507
3811
  return [node.text];
3508
3812
  }
3509
- return bodyStatements(node).flatMap(targetNames);
3813
+ return bodyStatements(node).flatMap(targetNames2);
3510
3814
  }
3511
3815
  function sequenceElementNames(node) {
3512
3816
  if (node === null || node.type !== "list" && node.type !== "tuple") {
@@ -3543,7 +3847,7 @@ function loopBindingsOf(stmt, position, scan) {
3543
3847
  });
3544
3848
  return bindings;
3545
3849
  }
3546
- for (const name of targetNames(left)) {
3850
+ for (const name of targetNames2(left)) {
3547
3851
  bindings.set(name, { kind: "unenumerable", location });
3548
3852
  }
3549
3853
  return bindings;
@@ -5019,14 +5323,9 @@ function matchedChains(calls, options) {
5019
5323
  if (chains.length === 0) {
5020
5324
  return [];
5021
5325
  }
5022
- const alreadyAsked = new Set(
5023
- options.facts.facts("wanted").map((row) => String(row[0]))
5024
- );
5025
5326
  resolveCalls(
5026
5327
  options.facts,
5027
- storageCallees(calls, options.filePath, options.couldMatch).filter(
5028
- (key) => !alreadyAsked.has(key)
5029
- )
5328
+ storageCallees(calls, options.filePath, options.couldMatch)
5030
5329
  );
5031
5330
  const matched = [];
5032
5331
  for (const chain of chains) {
@@ -6087,8 +6386,8 @@ function isInjectedParameter(param, injectedCallees, scope, ctx) {
6087
6386
  // src/facts/why.ts
6088
6387
  var import_node_fs4 = __toESM(require("fs"), 1);
6089
6388
  var import_node_path4 = __toESM(require("path"), 1);
6090
- var import_datalog4 = require("@suss/datalog");
6091
- var import_resolution4 = require("@suss/resolution");
6389
+ var import_datalog3 = require("@suss/datalog");
6390
+ var import_resolution6 = require("@suss/resolution");
6092
6391
 
6093
6392
  // src/facts.ts
6094
6393
  function unitKey(filePath, range, name) {
@@ -6106,7 +6405,7 @@ function emitModuleImportFacts(db, filePath, module2, resolverOptions) {
6106
6405
  if (binding.kind !== "import" && binding.kind !== "importFrom") {
6107
6406
  continue;
6108
6407
  }
6109
- const nameKey = `${filePath}#${localName}`;
6408
+ const nameKey2 = `${filePath}#${localName}`;
6110
6409
  const moduleText = importedModuleText(
6111
6410
  binding.module,
6112
6411
  binding.relativeLevel
@@ -6122,16 +6421,16 @@ function emitModuleImportFacts(db, filePath, module2, resolverOptions) {
6122
6421
  resolution.status === "resolved" ? "resolved" : resolution.reason
6123
6422
  ]);
6124
6423
  const importedName = binding.kind === "import" ? binding.localName : binding.importedName;
6125
- db.add("pyImportedName", [nameKey, moduleText, importedName]);
6424
+ db.add("pyImportedName", [nameKey2, moduleText, importedName]);
6126
6425
  if (scope.kind === "module") {
6127
- db.add("exportsAs", [filePath, localName, nameKey]);
6426
+ db.add("exportsAs", [filePath, localName, nameKey2]);
6128
6427
  }
6129
6428
  if (resolution.status === "resolved") {
6130
6429
  db.add("pyImportResolved", [filePath, moduleText, resolution.file]);
6131
- db.add("imports", [nameKey, resolution.file, importedName]);
6430
+ db.add("imports", [nameKey2, resolution.file, importedName]);
6132
6431
  continue;
6133
6432
  }
6134
- db.add("imports", [nameKey, moduleText, importedName]);
6433
+ db.add("imports", [nameKey2, moduleText, importedName]);
6135
6434
  }
6136
6435
  }
6137
6436
  for (const openModule of module2.openImports) {
@@ -6199,7 +6498,7 @@ function parseWith(language, source) {
6199
6498
  var import_node_fs3 = __toESM(require("fs"), 1);
6200
6499
  var import_node_path3 = __toESM(require("path"), 1);
6201
6500
  var import_behavioral_ir5 = require("@suss/behavioral-ir");
6202
- var import_datalog3 = require("@suss/datalog");
6501
+ var import_datalog2 = require("@suss/datalog");
6203
6502
  var import_extractor10 = require("@suss/extractor");
6204
6503
 
6205
6504
  // src/diagnostics.ts
@@ -6271,272 +6570,246 @@ function definedNodeOf(binding) {
6271
6570
 
6272
6571
  // src/reach/closure.ts
6273
6572
  var import_behavioral_ir4 = require("@suss/behavioral-ir");
6274
- var import_datalog2 = require("@suss/datalog");
6573
+ var import_datalog = require("@suss/datalog");
6275
6574
  var import_extractor7 = require("@suss/extractor");
6276
6575
 
6277
6576
  // src/reach/resolveCallee.ts
6278
- var stop = (reason) => ({ kind: "stop", reason });
6279
- var MODULE_STOP = {
6280
- ambiguous: "multipleSources",
6281
- outsideRoots: "outsideRun"
6577
+ var import_resolution5 = require("@suss/resolution");
6578
+ var STOP_FOR = {
6579
+ severalSources: "multipleSources",
6580
+ outsideRun: "outsideRun",
6581
+ unsettled: "unsettledValue",
6582
+ undeclared: "noDeclaration"
6282
6583
  };
6283
- function resolveCallee2(call, site, ctx) {
6284
- const callee = field(call, "function");
6285
- if (callee === null) {
6286
- return { kind: "stopped", reason: "noDeclaration" };
6584
+ var NO_DECLARATION = {
6585
+ kind: "stopped",
6586
+ reason: "noDeclaration"
6587
+ };
6588
+ function calleeSpellings(calls, ctx) {
6589
+ const spellingOf = /* @__PURE__ */ new Map();
6590
+ const keys = /* @__PURE__ */ new Set();
6591
+ for (const { call, site } of calls) {
6592
+ const spelling = spellingFor(call, site, ctx);
6593
+ spellingOf.set(call.id, spelling);
6594
+ if (spelling.kind === "key") {
6595
+ keys.add(spelling.key);
6596
+ }
6287
6597
  }
6288
- return asCallee(resolveExpression(callee, site, ctx), ctx);
6598
+ return { spellingOf, outcomes: (0, import_resolution5.calleeOutcomes)(ctx.facts, [...keys]) };
6289
6599
  }
6290
- function resolveNamedFunctionArgument(name, site, ctx) {
6291
- if (site.rebound.has(name)) {
6292
- return null;
6600
+ function resolveCallee2(call, site, ctx, read) {
6601
+ const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site, ctx);
6602
+ if (spelling.kind === "stopped") {
6603
+ return spelling;
6293
6604
  }
6294
- const binding = resolveName(site.scope, name);
6295
- if (binding !== null && binding.kind === "assignment") {
6605
+ const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution5.calleeOutcomeOf)(ctx.facts, spelling.key);
6606
+ return asCallee(outcome, site.owner, ctx);
6607
+ }
6608
+ function functionNamed(nameKey2, ctx) {
6609
+ if (assignedElsewhere(ctx.facts, nameKey2)) {
6296
6610
  return null;
6297
6611
  }
6298
- const resolved = binding === null ? throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set()) : resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
6299
- return resolved.kind === "function" ? resolved.target : null;
6612
+ const outcome = (0, import_resolution5.calleeOutcomeOf)(ctx.facts, nameKey2);
6613
+ return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
6300
6614
  }
6301
- function asCallee(resolved, ctx) {
6302
- if (resolved.kind === "function") {
6303
- return { kind: "followed", target: resolved.target };
6304
- }
6305
- if (resolved.kind === "class") {
6306
- return asCallee(
6307
- memberOfClass(resolved.file, resolved.node, "__init__", ctx),
6308
- ctx
6309
- );
6310
- }
6311
- if (resolved.kind === "stop") {
6312
- return { kind: "stopped", reason: resolved.reason };
6313
- }
6314
- if (resolved.kind === "package") {
6315
- return { kind: "stopped", reason: "outsideRun" };
6316
- }
6317
- return { kind: "stopped", reason: "noDeclaration" };
6615
+ function assignedElsewhere(facts, nameKey2) {
6616
+ return (0, import_resolution5.writtenSourcesOf)(facts, nameKey2).some(
6617
+ (source) => !facts.has("func", [source])
6618
+ );
6318
6619
  }
6319
- var EXPRESSION_RESOLVERS = {
6320
- identifier: (node, site, ctx) => resolveIdentifier(node.text, site, ctx),
6321
- attribute: (node, site, ctx) => {
6322
- const object = field(node, "object");
6323
- const attribute = field(node, "attribute");
6324
- if (object === null || attribute === null) {
6325
- return stop("noDeclaration");
6326
- }
6327
- return memberOf(resolveExpression(object, site, ctx), attribute.text, ctx);
6328
- },
6329
- call: (node, site, ctx) => {
6330
- const callee = field(node, "function");
6331
- return callee === null ? stop("noDeclaration") : instanceBuiltBy(resolveExpression(callee, site, ctx), "noDeclaration");
6332
- },
6333
- parenthesized_expression: (node, site, ctx) => {
6334
- const inner = node.namedChildren[0];
6335
- return inner === null || inner === void 0 ? stop("noDeclaration") : resolveExpression(inner, site, ctx);
6620
+ function readThrough(node) {
6621
+ if (node?.type !== "parenthesized_expression") {
6622
+ return node;
6336
6623
  }
6337
- };
6338
- function resolveExpression(node, site, ctx) {
6339
- const resolver = EXPRESSION_RESOLVERS[node.type];
6340
- return resolver === void 0 ? stop("noDeclaration") : resolver(node, site, ctx);
6624
+ return readThrough(children(node)[0] ?? null);
6341
6625
  }
6342
- function instanceBuiltBy(built, otherwise) {
6343
- if (built.kind === "class" || built.kind === "stop") {
6344
- return built;
6626
+ function spellingFor(call, site, ctx) {
6627
+ const callee = readThrough(field(call, "function"));
6628
+ if (callee === null) {
6629
+ return { kind: "stopped", reason: "noDeclaration" };
6345
6630
  }
6346
- return built.kind === "package" ? stop("outsideRun") : stop(otherwise);
6631
+ return throughModules(callee, site, ctx) ?? {
6632
+ kind: "key",
6633
+ key: readKey(site.file.file, callee, enclosingFunction(callee))
6634
+ };
6347
6635
  }
6348
- function resolveIdentifier(name, site, ctx) {
6349
- if (site.rebound.has(name)) {
6350
- return stop("unsettledValue");
6636
+ function asCallee(outcome, owner, ctx) {
6637
+ if (outcome.kind === "function") {
6638
+ return functionCallee(outcome.key, ctx);
6351
6639
  }
6352
- if (isReceiver(name, site.scope)) {
6353
- return receiverClass(site);
6640
+ if (outcome.kind === "object") {
6641
+ return constructorOf(outcome.key, ctx);
6354
6642
  }
6355
- const binding = resolveName(site.scope, name);
6356
- if (binding === null) {
6357
- return throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set());
6643
+ if (outcome.kind === "callerSupplied") {
6644
+ return outcome.key.startsWith(`${owner}#`) ? { kind: "stopped", reason: "callerSupplied" } : { kind: "stopped", reason: "unsettledValue" };
6358
6645
  }
6359
- return resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
6360
- }
6361
- function isReceiver(name, scope) {
6362
- return (name === "self" || name === "cls") && scope.kind === "function" && scope.parent?.kind === "class";
6646
+ return {
6647
+ kind: "stopped",
6648
+ reason: STOP_FOR[outcome.kind] ?? "noDeclaration"
6649
+ };
6363
6650
  }
6364
- function receiverClass(site) {
6365
- const classScope = site.scope.parent;
6366
- if (classScope === null || classScope === void 0) {
6367
- return stop("noDeclaration");
6651
+ function functionCallee(key, ctx) {
6652
+ if (ctx.definitions.get(key)?.type === "lambda") {
6653
+ return { kind: "stopped", reason: "unsettledValue" };
6368
6654
  }
6369
- return { kind: "class", file: site.file, node: classScope.node };
6655
+ const target = functionAt(key, ctx);
6656
+ return target === null ? NO_DECLARATION : { kind: "followed", target };
6370
6657
  }
6371
- var BINDING_RESOLVERS = {
6372
- functionDef: (binding, file) => ({
6373
- kind: "function",
6374
- target: reachedFunction(file, binding.node)
6375
- }),
6376
- classDef: (binding, file) => ({ kind: "class", file, node: binding.node }),
6377
- parameter: () => stop("callerSupplied"),
6378
- // `import a.b.c` binds `a`, and `import a.b.c as m` binds `m` to `a.b.c`.
6379
- import: (binding, file, _scope, ctx) => moduleNamed(
6380
- file,
6381
- binding.localName === binding.module.split(".")[0] ? binding.localName : binding.module,
6382
- 0,
6383
- ctx
6384
- ),
6385
- importFrom: (binding, file, _scope, ctx, visited) => memberOf(
6386
- moduleNamed(file, binding.module, binding.relativeLevel, ctx),
6387
- binding.importedName,
6388
- ctx,
6389
- visited
6390
- ),
6391
- // `run = load` is an alias worth one hop; anything else is a value.
6392
- assignment: (binding, file, scope, ctx, visited) => aliasedValue(binding.value, file, scope, ctx, visited),
6393
- global: () => stop("unsettledValue"),
6394
- nonlocal: () => stop("unsettledValue")
6395
- };
6396
- function resolveBinding(binding, file, scope, ctx, visited) {
6397
- const resolver = BINDING_RESOLVERS[binding.kind];
6398
- return resolver(binding, file, scope, ctx, visited);
6399
- }
6400
- function aliasedValue(value, file, scope, ctx, visited) {
6401
- if (value === null) {
6402
- return stop("unsettledValue");
6403
- }
6404
- if (value.type === "identifier") {
6405
- const binding = resolveName(scope, value.text);
6406
- if (binding === null || binding.kind === "assignment") {
6407
- return stop("unsettledValue");
6408
- }
6409
- return resolveBinding(binding, file, scope, ctx, visited);
6410
- }
6411
- if (value.type === "call") {
6412
- const callee = field(value, "function");
6413
- return callee === null ? stop("unsettledValue") : instanceBuiltBy(
6414
- aliasedValue(callee, file, scope, ctx, visited),
6415
- "unsettledValue"
6416
- );
6417
- }
6418
- if (value.type === "attribute") {
6419
- const object = field(value, "object");
6420
- const attribute = field(value, "attribute");
6421
- if (object === null || attribute === null) {
6422
- return stop("unsettledValue");
6423
- }
6424
- return memberOf(
6425
- aliasedValue(object, file, scope, ctx, visited),
6426
- attribute.text,
6427
- ctx
6428
- );
6658
+ function constructorOf(classKey, ctx) {
6659
+ const declared = ctx.facts.lookup("holdsProperty", 0, classKey).find((row) => String(row[1]) === "__init__");
6660
+ if (declared === void 0) {
6661
+ return NO_DECLARATION;
6429
6662
  }
6430
- return stop("unsettledValue");
6663
+ return functionCallee(String(declared[2]), ctx);
6431
6664
  }
6432
- function reachedFunction(file, node, owner) {
6665
+ function functionAt(key, ctx) {
6666
+ const node = ctx.definitions.get(key);
6667
+ const file = ctx.filesByPath.get(key.slice(0, key.lastIndexOf(":")));
6668
+ if (node === void 0 || file === void 0) {
6669
+ return null;
6670
+ }
6433
6671
  const name = field(node, "name")?.text ?? "<anon>";
6434
- const ownerName = owner === void 0 ? null : field(owner, "name")?.text;
6672
+ const owner = ownerClassOf(node);
6673
+ const ownerName = owner === null ? void 0 : field(owner, "name")?.text;
6435
6674
  return {
6436
6675
  file,
6437
6676
  node,
6438
6677
  name,
6439
- exportPath: ownerName === null || ownerName === void 0 ? [name] : [ownerName, name]
6678
+ exportPath: ownerName === void 0 ? [name] : [ownerName, name]
6440
6679
  };
6441
6680
  }
6442
- function moduleNamed(from, module2, relativeLevel, ctx) {
6443
- const resolution = resolveModule(
6444
- from.file,
6445
- { module: module2, relativeLevel },
6446
- { roots: ctx.roots }
6447
- );
6448
- if (resolution.status !== "resolved") {
6449
- return resolution.reason === "external" ? { kind: "package", dotted: module2, relativeLevel, from } : stop(MODULE_STOP[resolution.reason]);
6681
+ function ownerClassOf(node) {
6682
+ for (let up = node.parent; up !== null; up = up.parent) {
6683
+ if (isFunction(up)) {
6684
+ return null;
6685
+ }
6686
+ if (up.type === "class_definition") {
6687
+ return up;
6688
+ }
6450
6689
  }
6451
- const file = ctx.filesByPath.get(resolution.file);
6452
- return file === void 0 ? stop("outsideRun") : { kind: "module", file };
6690
+ return null;
6453
6691
  }
6454
- function memberOf(base, name, ctx, visited = /* @__PURE__ */ new Set()) {
6455
- if (base.kind === "module") {
6456
- return memberOfModule(base.file, name, ctx, visited);
6692
+ function throughModules(callee, site, ctx) {
6693
+ if (callee.type === "identifier") {
6694
+ return resolveName(site.scope, callee.text) === null ? openImportMember(site.file, callee.text, ctx, /* @__PURE__ */ new Set()) : null;
6457
6695
  }
6458
- if (base.kind === "package") {
6459
- return moduleNamed(
6460
- base.from,
6461
- base.dotted === "" ? name : `${base.dotted}.${name}`,
6462
- base.relativeLevel,
6463
- ctx
6464
- );
6696
+ if (callee.type !== "attribute") {
6697
+ return null;
6465
6698
  }
6466
- if (base.kind === "class") {
6467
- return memberOfClass(base.file, base.node, name, ctx);
6699
+ const object = field(callee, "object");
6700
+ const attribute = field(callee, "attribute");
6701
+ if (object === null || attribute === null) {
6702
+ return null;
6468
6703
  }
6469
- if (base.kind === "stop") {
6470
- return base;
6704
+ const base = moduleAt(object, site, ctx);
6705
+ return base === null ? null : memberKey(base, attribute.text, ctx);
6706
+ }
6707
+ function openImportMember(file, name, ctx, visited) {
6708
+ if (visited.has(file.file)) {
6709
+ return null;
6471
6710
  }
6472
- return stop("noDeclaration");
6711
+ visited.add(file.file);
6712
+ const found = /* @__PURE__ */ new Set();
6713
+ for (const spec of file.module.openImports) {
6714
+ const dots = spec.length - spec.replace(/^\.+/, "").length;
6715
+ const opened = moduleNamed(file, spec.slice(dots), dots, ctx);
6716
+ if (opened === null || opened.kind !== "module") {
6717
+ continue;
6718
+ }
6719
+ const member = opened.file.module.moduleScope.bindings.has(name) ? { kind: "key", key: `${opened.file.file}#${name}` } : openImportMember(opened.file, name, ctx, visited);
6720
+ if (member?.kind === "key") {
6721
+ found.add(member.key);
6722
+ }
6723
+ }
6724
+ if (found.size > 1) {
6725
+ return { kind: "stopped", reason: "multipleSources" };
6726
+ }
6727
+ const only = [...found][0];
6728
+ return only === void 0 ? null : { kind: "key", key: only };
6473
6729
  }
6474
- function memberOfClass(file, classNode, name, ctx) {
6475
- const classScope = file.module.scopeFor.get(classNode.id);
6476
- const binding = classScope?.bindings.get(name);
6477
- if (binding === void 0) {
6478
- return stop("noDeclaration");
6730
+ function moduleAt(node, site, ctx) {
6731
+ if (node.type === "identifier") {
6732
+ return importedModule(node.text, site, ctx);
6479
6733
  }
6480
- if (binding.kind === "functionDef") {
6481
- return {
6482
- kind: "function",
6483
- target: reachedFunction(file, binding.node, classNode)
6484
- };
6734
+ if (node.type !== "attribute") {
6735
+ return null;
6736
+ }
6737
+ const object = field(node, "object");
6738
+ const attribute = field(node, "attribute");
6739
+ if (object === null || attribute === null) {
6740
+ return null;
6485
6741
  }
6486
- return resolveBinding(binding, file, classScope, ctx, /* @__PURE__ */ new Set());
6742
+ const base = moduleAt(object, site, ctx);
6743
+ return base === null ? null : submoduleOf(base, attribute.text, ctx);
6487
6744
  }
6488
- function memberOfModule(file, name, ctx, visited) {
6489
- const key = `${file.file}#${name}`;
6490
- if (visited.has(key)) {
6491
- return stop("unsettledValue");
6745
+ function importedModule(name, site, ctx) {
6746
+ const binding = resolveName(site.scope, name);
6747
+ if (binding?.kind === "import") {
6748
+ const head = binding.module.split(".")[0];
6749
+ const dotted = binding.localName === head ? binding.localName : binding.module;
6750
+ return moduleNamed(site.file, dotted, 0, ctx);
6492
6751
  }
6493
- visited.add(key);
6494
- const binding = file.module.moduleScope.bindings.get(name);
6495
- if (binding !== void 0) {
6496
- return resolveBinding(binding, file, file.module.moduleScope, ctx, visited);
6752
+ if (binding?.kind !== "importFrom") {
6753
+ return null;
6497
6754
  }
6498
- if (file.file.endsWith("__init__.py")) {
6499
- const submodule = moduleNamed(file, name, 1, ctx);
6500
- if (submodule.kind === "module") {
6501
- return submodule;
6502
- }
6755
+ const from = moduleNamed(
6756
+ site.file,
6757
+ binding.module,
6758
+ binding.relativeLevel,
6759
+ ctx
6760
+ );
6761
+ return from === null ? null : submoduleOf(from, binding.importedName, ctx);
6762
+ }
6763
+ function submoduleOf(base, name, ctx) {
6764
+ if (base.kind === "package") {
6765
+ return moduleNamed(
6766
+ base.from,
6767
+ base.dotted === "" ? name : `${base.dotted}.${name}`,
6768
+ base.relativeLevel,
6769
+ ctx
6770
+ );
6503
6771
  }
6504
- return throughOpenImports(file, name, ctx, visited);
6772
+ return base.file.file.endsWith("__init__.py") ? moduleNamed(base.file, name, 1, ctx) : null;
6505
6773
  }
6506
- function throughOpenImports(file, name, ctx, visited) {
6507
- const found = [];
6508
- for (const spec of file.module.openImports) {
6509
- const dots = spec.length - spec.replace(/^\.+/, "").length;
6510
- const opened = moduleNamed(file, spec.slice(dots), dots, ctx);
6511
- if (opened.kind !== "module") {
6512
- continue;
6513
- }
6514
- const member = memberOfModule(opened.file, name, ctx, visited);
6515
- if (member.kind !== "stop") {
6516
- found.push(member);
6517
- }
6774
+ function memberKey(base, name, ctx) {
6775
+ if (base.kind === "package") {
6776
+ return null;
6518
6777
  }
6519
- if (found.length > 1) {
6520
- return stop("multipleSources");
6778
+ if (base.file.module.moduleScope.bindings.has(name)) {
6779
+ return { kind: "key", key: `${base.file.file}#${name}` };
6521
6780
  }
6522
- return found[0] ?? stop("noDeclaration");
6781
+ return openImportMember(base.file, name, ctx, /* @__PURE__ */ new Set());
6782
+ }
6783
+ function moduleNamed(from, module2, relativeLevel, ctx) {
6784
+ const resolution = resolveModule(
6785
+ from.file,
6786
+ { module: module2, relativeLevel },
6787
+ { roots: ctx.roots }
6788
+ );
6789
+ if (resolution.status !== "resolved") {
6790
+ return resolution.reason === "external" ? { kind: "package", dotted: module2, relativeLevel, from } : null;
6791
+ }
6792
+ const file = ctx.filesByPath.get(resolution.file);
6793
+ return file === void 0 ? null : { kind: "module", file };
6523
6794
  }
6524
6795
 
6525
6796
  // src/reach/closure.ts
6526
6797
  var REACHABLE_RULES = [
6527
- (0, import_datalog2.rule)("reachable", [(0, import_datalog2.variable)("f")], [(0, import_datalog2.lit)("entry", (0, import_datalog2.variable)("f"))]),
6528
- (0, import_datalog2.rule)(
6798
+ (0, import_datalog.rule)("reachable", [(0, import_datalog.variable)("f")], [(0, import_datalog.lit)("entry", (0, import_datalog.variable)("f"))]),
6799
+ (0, import_datalog.rule)(
6529
6800
  "reachable",
6530
- [(0, import_datalog2.variable)("g")],
6531
- [(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"))]
6801
+ [(0, import_datalog.variable)("g")],
6802
+ [(0, import_datalog.lit)("reachable", (0, import_datalog.variable)("f")), (0, import_datalog.lit)("calls", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("g"))]
6532
6803
  )
6533
6804
  ];
6534
6805
  function reachedFunctions(seeds, options) {
6535
6806
  const ctx = {
6536
6807
  filesByPath: new Map(options.files.map((file) => [file.file, file])),
6537
- roots: options.roots
6808
+ roots: options.roots,
6809
+ facts: options.facts,
6810
+ definitions: options.definitions
6538
6811
  };
6539
- const db = new import_datalog2.Database();
6812
+ const db = new import_datalog.Database();
6540
6813
  const functionByKey = /* @__PURE__ */ new Map();
6541
6814
  const seedKeys = /* @__PURE__ */ new Set();
6542
6815
  const scanned = /* @__PURE__ */ new Set();
@@ -6556,21 +6829,26 @@ function reachedFunctions(seeds, options) {
6556
6829
  db.add("entry", [seed.key]);
6557
6830
  }
6558
6831
  for (; ; ) {
6559
- (0, import_datalog2.evaluate)(db, REACHABLE_RULES);
6832
+ (0, import_datalog.evaluate)(db, REACHABLE_RULES);
6560
6833
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
6561
6834
  if (frontier.length === 0) {
6562
6835
  break;
6563
6836
  }
6564
- for (const key of frontier) {
6837
+ const bodies = frontier.flatMap((key) => {
6565
6838
  scanned.add(key);
6566
6839
  const source = functionByKey.get(key);
6567
- if (source === void 0) {
6568
- continue;
6569
- }
6840
+ return source === void 0 ? [] : [{ key, source, ...bodyOf(source) }];
6841
+ });
6842
+ const spellings = calleeSpellings(
6843
+ bodies.flatMap((body) => body.written),
6844
+ ctx
6845
+ );
6846
+ for (const { key, source, body, written } of bodies) {
6570
6847
  const scan = scanBody(
6571
6848
  source,
6572
6849
  ctx,
6573
- recognizedCallIds(source.node, options.storageFor(source.file))
6850
+ recognizedCallIds(source.node, options.storageFor(source.file)),
6851
+ { body, written, spellings }
6574
6852
  );
6575
6853
  if (scan.stops.length > 0) {
6576
6854
  stopsByKey.set(key, scan.stops);
@@ -6636,7 +6914,18 @@ var EMPTY_SCAN = {
6636
6914
  parameterCalls: [],
6637
6915
  passedPositions: /* @__PURE__ */ new Set()
6638
6916
  };
6639
- function scanBody(source, ctx, recognized) {
6917
+ function bodyOf(source) {
6918
+ const { file, node } = source;
6919
+ const body = field(node, "body");
6920
+ if (body === null) {
6921
+ return { body, written: [] };
6922
+ }
6923
+ return {
6924
+ body,
6925
+ written: callsWritten(file, body, scopeAt2(file, node), keyOf(source))
6926
+ };
6927
+ }
6928
+ function scanBody(source, ctx, recognized, read) {
6640
6929
  const followed = [];
6641
6930
  const stops = [];
6642
6931
  const placements = new import_behavioral_ir4.TargetPlacements();
@@ -6645,12 +6934,12 @@ function scanBody(source, ctx, recognized) {
6645
6934
  const seen = /* @__PURE__ */ new Set();
6646
6935
  const parameterCallsSeen = /* @__PURE__ */ new Set();
6647
6936
  const { file, node } = source;
6648
- const body = field(node, "body");
6649
- if (body === null) {
6937
+ const { written, spellings } = read;
6938
+ if (read.body === null) {
6650
6939
  return EMPTY_SCAN;
6651
6940
  }
6652
6941
  const ownParameters = callParameterNames(node, source.exportPath.length > 1);
6653
- const recordPassedArgs = (call, callee, calleeKey, where) => {
6942
+ const recordPassedArgs = (call, callee, calleeKey) => {
6654
6943
  const args = field(call, "arguments");
6655
6944
  if (args === null) {
6656
6945
  return;
@@ -6659,9 +6948,8 @@ function scanBody(source, ctx, recognized) {
6659
6948
  if (arg === null || arg.type !== "identifier") {
6660
6949
  return;
6661
6950
  }
6662
- const resolved = resolveNamedFunctionArgument(
6663
- arg.text,
6664
- { file, scope: where.scope, rebound: where.rebound },
6951
+ const resolved = functionNamed(
6952
+ readKey(file.file, arg, enclosingFunction(arg)),
6665
6953
  ctx
6666
6954
  );
6667
6955
  if (resolved === null) {
@@ -6681,13 +6969,9 @@ function scanBody(source, ctx, recognized) {
6681
6969
  }
6682
6970
  });
6683
6971
  };
6684
- const record = (call, where) => {
6972
+ const record = (call, site) => {
6685
6973
  const callee = calleeText(call);
6686
- const outcome = resolveCallee2(
6687
- call,
6688
- { file, scope: where.scope, rebound: where.rebound },
6689
- ctx
6690
- );
6974
+ const outcome = resolveCallee2(call, site, ctx, spellings);
6691
6975
  const placed = outcome.kind === "followed" ? {
6692
6976
  file: outcome.target.file.displayPath,
6693
6977
  span: spanOf(outcome.target.node)
@@ -6696,8 +6980,7 @@ function scanBody(source, ctx, recognized) {
6696
6980
  recordPassedArgs(
6697
6981
  call,
6698
6982
  callee,
6699
- outcome.kind === "followed" ? keyOf(outcome.target) : null,
6700
- where
6983
+ outcome.kind === "followed" ? keyOf(outcome.target) : null
6701
6984
  );
6702
6985
  if (outcome.kind === "stopped") {
6703
6986
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6720,32 +7003,9 @@ function scanBody(source, ctx, recognized) {
6720
7003
  followed.push(outcome.target);
6721
7004
  }
6722
7005
  };
6723
- (0, import_extractor7.walkDescendants)(
6724
- body,
6725
- { scope: scopeAt2(file, node), rebound: reboundNames(body) },
6726
- {
6727
- at: (child, where) => {
6728
- if (child.type === "call") {
6729
- record(child, where);
6730
- }
6731
- },
6732
- into: (child, where) => {
6733
- if (child.type === "function_definition") {
6734
- return import_extractor7.SKIP_CHILDREN;
6735
- }
6736
- if (child.type === "lambda") {
6737
- return {
6738
- scope: where.scope,
6739
- rebound: /* @__PURE__ */ new Set([...where.rebound, ...lambdaParameters(child)])
6740
- };
6741
- }
6742
- return {
6743
- scope: file.module.scopeFor.get(child.id) ?? where.scope,
6744
- rebound: where.rebound
6745
- };
6746
- }
6747
- }
6748
- );
7006
+ for (const { call, site } of written) {
7007
+ record(call, site);
7008
+ }
6749
7009
  return {
6750
7010
  followed,
6751
7011
  stops,
@@ -6755,6 +7015,18 @@ function scanBody(source, ctx, recognized) {
6755
7015
  passedPositions
6756
7016
  };
6757
7017
  }
7018
+ function callsWritten(file, body, outer, owner) {
7019
+ const found = [];
7020
+ (0, import_extractor7.walkDescendants)(body, outer, {
7021
+ at: (child, scope) => {
7022
+ if (child.type === "call") {
7023
+ found.push({ call: child, site: { file, scope, owner } });
7024
+ }
7025
+ },
7026
+ into: (child, scope) => child.type === "function_definition" ? import_extractor7.SKIP_CHILDREN : file.module.scopeFor.get(child.id) ?? scope
7027
+ });
7028
+ return found;
7029
+ }
6758
7030
  function scopeAt2(file, node) {
6759
7031
  let current = node;
6760
7032
  while (current !== null) {
@@ -6766,40 +7038,6 @@ function scopeAt2(file, node) {
6766
7038
  }
6767
7039
  return file.module.moduleScope;
6768
7040
  }
6769
- var REBINDING_FIELDS = {
6770
- assignment: "left",
6771
- augmented_assignment: "left",
6772
- named_expression: "name",
6773
- for_statement: "left",
6774
- for_in_clause: "left",
6775
- as_pattern: "alias"
6776
- };
6777
- function reboundNames(body) {
6778
- const names = /* @__PURE__ */ new Set();
6779
- (0, import_extractor7.walkDescendants)(body, null, {
6780
- at: (node) => {
6781
- const target = REBINDING_FIELDS[node.type];
6782
- if (target === void 0 || boundByBinder(node, body)) {
6783
- return;
6784
- }
6785
- const written = field(node, target);
6786
- if (written !== null) {
6787
- for (const name of identifiersUnder(written)) {
6788
- names.add(name);
6789
- }
6790
- }
6791
- },
6792
- into: (node) => node.type === "function_definition" ? import_extractor7.SKIP_CHILDREN : null
6793
- });
6794
- return names;
6795
- }
6796
- function boundByBinder(node, body) {
6797
- if (node.type !== "assignment") {
6798
- return false;
6799
- }
6800
- const statement = node.parent;
6801
- return field(node, "left")?.type === "identifier" && statement?.type === "expression_statement" && statement.parent?.id === body.id;
6802
- }
6803
7041
  function identifiersUnder(node, found = []) {
6804
7042
  if (node.type === "identifier") {
6805
7043
  found.push(node.text);
@@ -6812,10 +7050,6 @@ function identifiersUnder(node, found = []) {
6812
7050
  }
6813
7051
  return found;
6814
7052
  }
6815
- function lambdaParameters(lambda) {
6816
- const parameters = field(lambda, "parameters");
6817
- return parameters === null ? [] : identifiersUnder(parameters);
6818
- }
6819
7053
  function libraryUnit(target, options) {
6820
7054
  const { file, node, name, exportPath } = target;
6821
7055
  const body = field(node, "body");
@@ -6966,6 +7200,18 @@ var PythonWrapperIndex = class {
6966
7200
  ownRoutes = /* @__PURE__ */ new Map();
6967
7201
  unitsByKey = /* @__PURE__ */ new Map();
6968
7202
  unitsByFile = /* @__PURE__ */ new Map();
7203
+ /** The project function a module-level name in this file refers to, or null for anything else. */
7204
+ functionCalled(file, name) {
7205
+ if (this.options.facts === void 0) {
7206
+ return null;
7207
+ }
7208
+ return functionNamed(nameKeyIn(file, null, name), {
7209
+ filesByPath: this.filesByPath,
7210
+ roots: this.options.roots,
7211
+ facts: this.options.facts,
7212
+ definitions: this.options.definitions
7213
+ });
7214
+ }
6969
7215
  register(covers, target, declared) {
6970
7216
  const registered = this.registered(target, declared);
6971
7217
  const table = covers.kind === "everyRoute" ? this.everyRoute : this.ownRoutes;
@@ -7012,20 +7258,12 @@ var PythonWrapperIndex = class {
7012
7258
  if (declared.form.type !== "dependency") {
7013
7259
  continue;
7014
7260
  }
7015
- const site = {
7016
- file,
7017
- scope: query.module.moduleScope,
7018
- rebound: /* @__PURE__ */ new Set()
7019
- };
7020
7261
  const targets = [
7021
7262
  ...decoratorDependencies(query.classification, declared.form),
7022
7263
  ...parameterDependencies(query.definitionNode, declared.form)
7023
7264
  ];
7024
7265
  for (const name of targets) {
7025
- const target = resolveNamedFunctionArgument(name, site, {
7026
- filesByPath: this.filesByPath,
7027
- roots: this.options.roots
7028
- });
7266
+ const target = this.functionCalled(file.file, name);
7029
7267
  if (target !== null) {
7030
7268
  found.push(this.registered(target, declared));
7031
7269
  }
@@ -7167,12 +7405,8 @@ function registerConstructorDependencies(file, declared, form, index) {
7167
7405
  if (listed === void 0) {
7168
7406
  continue;
7169
7407
  }
7170
- const site = { file, scope, rebound: /* @__PURE__ */ new Set() };
7171
7408
  for (const name of dependencyNamesIn(listed.node, form)) {
7172
- const target = resolveNamedFunctionArgument(name, site, {
7173
- filesByPath: index.filesByPath,
7174
- roots: index.options.roots
7175
- });
7409
+ const target = index.functionCalled(file.file, name);
7176
7410
  if (target !== null) {
7177
7411
  index.register(
7178
7412
  coverageOf(match, file, declared.pack),
@@ -7434,9 +7668,9 @@ async function extractPythonProject(options) {
7434
7668
  options.onCacheDiagnostic?.(lookup.diagnostic);
7435
7669
  if (lookup.kind === "hit") {
7436
7670
  options.onTiming?.(timer.report());
7437
- return { summaries: lookup.summaries, facts: new import_datalog3.Database() };
7671
+ return { summaries: lookup.summaries, facts: new import_datalog2.Database() };
7438
7672
  }
7439
- const db = new import_datalog3.Database();
7673
+ const db = new import_datalog2.Database();
7440
7674
  const summaries = [];
7441
7675
  const gapHandling = options.gapHandling ?? "permissive";
7442
7676
  const tallies = (0, import_extractor6.createPackTallies)(options.packs);
@@ -7500,6 +7734,7 @@ async function extractPythonProject(options) {
7500
7734
  packs: options.packs,
7501
7735
  roots: options.roots,
7502
7736
  facts: needsValues ? db : void 0,
7737
+ definitions,
7503
7738
  storageFor
7504
7739
  })
7505
7740
  );
@@ -7583,7 +7818,9 @@ async function extractPythonProject(options) {
7583
7818
  files: bound,
7584
7819
  roots: options.roots,
7585
7820
  gapHandling,
7586
- storageFor
7821
+ storageFor,
7822
+ facts: db,
7823
+ definitions
7587
7824
  })
7588
7825
  );
7589
7826
  for (const [key, owners] of summariesBySeed) {
@@ -7774,7 +8011,7 @@ function width(node) {
7774
8011
  }
7775
8012
  var PythonWhySession = class {
7776
8013
  root;
7777
- db = new import_datalog4.Database();
8014
+ db = new import_datalog3.Database();
7778
8015
  locations = /* @__PURE__ */ new Map();
7779
8016
  trees = /* @__PURE__ */ new Map();
7780
8017
  constructor(options) {
@@ -7843,9 +8080,9 @@ var PythonWhySession = class {
7843
8080
  * which the caller says in its own words.
7844
8081
  */
7845
8082
  explain(value, options = {}) {
7846
- return (0, import_resolution4.explainResolvedKey)({
8083
+ return (0, import_resolution6.explainResolvedKey)({
7847
8084
  db: this.db,
7848
- rules: import_resolution4.RESOLUTION_RULES,
8085
+ rules: import_resolution6.RESOLUTION_RULES,
7849
8086
  key: readKey(value.file, value.node, enclosingFunction(value.node)),
7850
8087
  locate: (key) => this.locate(key),
7851
8088
  displayPath: (key) => this.displayPath(key),
@@ -7976,6 +8213,7 @@ async function pythonImportEvidence(options) {
7976
8213
  emitValueFacts,
7977
8214
  extractPythonProject,
7978
8215
  findPythonFiles,
8216
+ nameKeyIn,
7979
8217
  nodeId,
7980
8218
  objectReturnedBy,
7981
8219
  parsePython,