@suss/adapter-python 0.27.0 → 0.29.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;
@@ -1877,12 +1857,19 @@ function declaredMemberKey(emitter, member, classKey) {
1877
1857
  }
1878
1858
  return null;
1879
1859
  }
1860
+ function writtenBaseName(base) {
1861
+ return base.type === "identifier" || base.type === "attribute" ? base.text : null;
1862
+ }
1880
1863
  function emitClassFacts(emitter, cls) {
1881
1864
  const classKey = nodeId(emitter.filePath, cls);
1882
1865
  add(emitter, "objectValue", classKey);
1883
1866
  const bases = field(cls, "superclasses");
1884
1867
  for (const base of bases === null ? [] : children(bases)) {
1885
1868
  add(emitter, "extends", classKey, valueKey(emitter, base));
1869
+ const written = writtenBaseName(base);
1870
+ if (written !== null) {
1871
+ add(emitter, "extendsNamed", classKey, written);
1872
+ }
1886
1873
  }
1887
1874
  const body = field(cls, "body");
1888
1875
  for (const statement of body === null ? [] : children(body)) {
@@ -1901,40 +1888,361 @@ function emitClassFacts(emitter, cls) {
1901
1888
  }
1902
1889
  continue;
1903
1890
  }
1904
- const memberKey = declaredMemberKey(emitter, member, classKey);
1891
+ const memberKey2 = declaredMemberKey(emitter, member, classKey);
1905
1892
  const name = field(member, "name");
1906
- if (memberKey !== null && name !== null) {
1907
- add(emitter, "holdsProperty", classKey, name.text, memberKey);
1893
+ if (memberKey2 !== null && name !== null) {
1894
+ add(emitter, "holdsProperty", classKey, name.text, memberKey2);
1908
1895
  }
1909
1896
  }
1910
1897
  return classKey;
1911
1898
  }
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) {
1899
+ function emitNestedDefinitions(emitter, node) {
1900
+ for (const child of children(node)) {
1901
+ if (isFunction(child)) {
1902
+ emitFunctionFacts(emitter, child);
1903
+ if (emitter.enclosing !== null) {
1904
+ add(
1905
+ emitter,
1906
+ "containsFn",
1907
+ emitter.enclosing.funcKey,
1908
+ nodeId(emitter.filePath, child)
1909
+ );
1910
+ }
1911
+ continue;
1912
+ }
1913
+ if (child.type === "class_definition") {
1914
+ emitClassFacts(emitter, child);
1915
+ continue;
1916
+ }
1917
+ emitNestedDefinitions(emitter, child);
1918
+ }
1919
+ }
1920
+ var readingsByTree = /* @__PURE__ */ new WeakMap();
1921
+ function scopeReadingOf(fn) {
1922
+ const tree = fn.tree;
1923
+ let byNode = readingsByTree.get(tree);
1924
+ if (byNode === void 0) {
1925
+ byNode = /* @__PURE__ */ new Map();
1926
+ readingsByTree.set(tree, byNode);
1927
+ }
1928
+ const remembered = byNode.get(fn.id);
1929
+ if (remembered !== void 0) {
1930
+ return remembered;
1931
+ }
1932
+ const body = field(fn, "body");
1933
+ const reading = readScope(
1934
+ body !== null && body.type === "block" ? body : null,
1935
+ boundParameterNames(fn)
1936
+ );
1937
+ byNode.set(fn.id, reading);
1938
+ return reading;
1939
+ }
1940
+ var TARGET_PATTERN_TYPES = /* @__PURE__ */ new Set([
1941
+ "pattern_list",
1942
+ "tuple_pattern",
1943
+ "list_pattern"
1944
+ ]);
1945
+ function targetNames(target, found = []) {
1946
+ if (target.type === "identifier") {
1947
+ found.push(target);
1948
+ return found;
1949
+ }
1950
+ if (TARGET_PATTERN_TYPES.has(target.type)) {
1951
+ for (const child of children(target)) {
1952
+ targetNames(child, found);
1953
+ }
1954
+ }
1955
+ return found;
1956
+ }
1957
+ function readAssignment(node, sink) {
1958
+ const left = field(node, "left");
1959
+ const right = field(node, "right");
1960
+ if (left === null) {
1961
+ return;
1962
+ }
1963
+ const written = right === null ? null : assignedValue(right);
1964
+ const names = targetNames(left);
1965
+ for (const name of names) {
1966
+ sink.record(name.text, {
1967
+ // An unpacked target takes one piece of the value, and which piece
1968
+ // is not something the assignment says.
1969
+ value: names.length === 1 ? written : null,
1970
+ given: null,
1971
+ at: node,
1972
+ direct: isDirectStatement(node, sink.bodyOwner)
1973
+ });
1974
+ }
1975
+ }
1976
+ function readAugmentedAssignment(node, sink) {
1977
+ const left = field(node, "left");
1978
+ if (left === null || left.type !== "identifier") {
1979
+ return;
1980
+ }
1981
+ sink.record(left.text, {
1982
+ value: null,
1983
+ given: null,
1984
+ at: node,
1985
+ direct: isDirectStatement(node, sink.bodyOwner)
1986
+ });
1987
+ }
1988
+ function readIterationTarget(node, sink) {
1989
+ const left = field(node, "left");
1990
+ if (left === null) {
1991
+ return;
1992
+ }
1993
+ for (const name of targetNames(left)) {
1994
+ sink.record(name.text, {
1995
+ value: null,
1996
+ given: null,
1997
+ at: node,
1998
+ direct: false
1999
+ });
2000
+ }
2001
+ }
2002
+ function identifierOf(node) {
2003
+ if (node.type === "identifier") {
2004
+ return node;
2005
+ }
2006
+ return children(node).find((child) => child.type === "identifier") ?? null;
2007
+ }
2008
+ function readAsPattern(node, sink) {
2009
+ const target = field(node, "alias") ?? children(node)[1];
2010
+ const name = target === void 0 ? null : identifierOf(target);
2011
+ if (name === null) {
2012
+ return;
2013
+ }
2014
+ sink.record(name.text, {
2015
+ value: null,
2016
+ given: null,
2017
+ at: node,
2018
+ direct: false
2019
+ });
2020
+ }
2021
+ function readWalrus(node, sink) {
2022
+ const name = field(node, "name");
2023
+ if (name === null || name.type !== "identifier") {
2024
+ return;
2025
+ }
2026
+ sink.record(name.text, {
2027
+ value: field(node, "value"),
2028
+ given: null,
2029
+ at: node,
2030
+ direct: false
2031
+ });
2032
+ }
2033
+ function readGlobalStatement(node, sink) {
2034
+ for (const child of children(node)) {
2035
+ if (child.type === "identifier") {
2036
+ sink.globals.add(child.text);
2037
+ }
2038
+ }
2039
+ }
2040
+ function readNonlocalStatement(node, sink) {
2041
+ for (const child of children(node)) {
2042
+ if (child.type === "identifier") {
2043
+ sink.nonlocals.add(child.text);
2044
+ }
2045
+ }
2046
+ }
2047
+ var WRITE_READERS = {
2048
+ assignment: readAssignment,
2049
+ augmented_assignment: readAugmentedAssignment,
2050
+ for_statement: readIterationTarget,
2051
+ for_in_clause: readIterationTarget,
2052
+ as_pattern: readAsPattern,
2053
+ named_expression: readWalrus,
2054
+ global_statement: readGlobalStatement,
2055
+ nonlocal_statement: readNonlocalStatement
2056
+ };
2057
+ function readScope(bodyOwner, parameterNames) {
2058
+ const writes = /* @__PURE__ */ new Map();
2059
+ const globals = /* @__PURE__ */ new Set();
2060
+ const nonlocals = /* @__PURE__ */ new Set();
2061
+ const record = (name, write) => {
2062
+ const existing = writes.get(name);
2063
+ if (existing === void 0) {
2064
+ writes.set(name, [write]);
1917
2065
  return;
1918
2066
  }
1919
- add(emitter, "binds", nameId(filePath, name.text), key);
1920
- add(emitter, "exportsAs", filePath, name.text, key);
2067
+ existing.push(write);
1921
2068
  };
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);
2069
+ const sink = { bodyOwner, globals, nonlocals, record };
2070
+ const visit = (node) => {
2071
+ if (isFunction(node) || node.type === "class_definition") {
2072
+ const name = field(node, "name");
2073
+ if (name !== null) {
2074
+ record(name.text, {
2075
+ value: node,
2076
+ given: null,
2077
+ at: node,
2078
+ direct: isDirectStatement(node, bodyOwner)
2079
+ });
1933
2080
  }
1934
- walk(child);
2081
+ return;
2082
+ }
2083
+ WRITE_READERS[node.type]?.(node, sink);
2084
+ for (const child of children(node)) {
2085
+ visit(child);
1935
2086
  }
1936
2087
  };
1937
- walk(root);
2088
+ for (const statement of bodyOwner === null ? [] : children(bodyOwner)) {
2089
+ visit(statement);
2090
+ }
2091
+ const locals = new Set(writes.keys());
2092
+ for (const name of [...globals, ...nonlocals]) {
2093
+ locals.delete(name);
2094
+ }
2095
+ for (const name of parameterNames) {
2096
+ locals.add(name);
2097
+ addParameterWrite(writes.get(name), name);
2098
+ }
2099
+ return { bodyOwner, writes, locals, globals };
2100
+ }
2101
+ function addParameterWrite(later, name) {
2102
+ const first = later?.[0];
2103
+ if (later === void 0 || first === void 0) {
2104
+ return;
2105
+ }
2106
+ later.unshift({ value: null, given: name, at: first.at, direct: false });
2107
+ }
2108
+ function statementOf(node) {
2109
+ let current = node;
2110
+ while (current !== null) {
2111
+ const parent = current.parent;
2112
+ if (parent === null) {
2113
+ return null;
2114
+ }
2115
+ if (parent.type === "block" || parent.type === "module") {
2116
+ return current;
2117
+ }
2118
+ current = parent;
2119
+ }
2120
+ return null;
2121
+ }
2122
+ function isDirectStatement(node, bodyOwner) {
2123
+ if (bodyOwner === null) {
2124
+ return false;
2125
+ }
2126
+ const statement = statementOf(node);
2127
+ return statement !== null && statement.parent?.id === bodyOwner.id;
2128
+ }
2129
+ var UNREAD_PARENTS = /* @__PURE__ */ new Set([
2130
+ "as_pattern_target",
2131
+ "global_statement",
2132
+ "nonlocal_statement",
2133
+ "pattern_list",
2134
+ "tuple_pattern",
2135
+ "list_pattern",
2136
+ "dotted_name"
2137
+ ]);
2138
+ var UNREAD_FIELDS = {
2139
+ assignment: "left",
2140
+ augmented_assignment: "left",
2141
+ for_statement: "left",
2142
+ for_in_clause: "left",
2143
+ named_expression: "name",
2144
+ function_definition: "name",
2145
+ class_definition: "name",
2146
+ attribute: "attribute",
2147
+ keyword_argument: "name"
2148
+ };
2149
+ function isNameRead(name) {
2150
+ const parent = name.parent;
2151
+ if (parent === null) {
2152
+ return true;
2153
+ }
2154
+ if (UNREAD_PARENTS.has(parent.type)) {
2155
+ return false;
2156
+ }
2157
+ const fieldName = UNREAD_FIELDS[parent.type];
2158
+ return fieldName === void 0 || field(parent, fieldName)?.id !== name.id;
2159
+ }
2160
+ var READ_FIRST = {
2161
+ call: "function",
2162
+ attribute: "object"
2163
+ };
2164
+ function readFirst(node) {
2165
+ if (node.type === "parenthesized_expression") {
2166
+ return children(node)[0] ?? null;
2167
+ }
2168
+ const fieldName = READ_FIRST[node.type];
2169
+ return fieldName === void 0 ? null : field(node, fieldName);
2170
+ }
2171
+ var NAME_READS = {
2172
+ nameTypes: /* @__PURE__ */ new Set(["identifier"]),
2173
+ laterBodies: LATER_BODY_TYPES,
2174
+ childrenOf: children,
2175
+ isRead: isNameRead,
2176
+ readFirst
2177
+ };
2178
+ var CONSTRUCTION_TYPES = /* @__PURE__ */ new Set([
2179
+ "call",
2180
+ "list",
2181
+ "dictionary",
2182
+ "set",
2183
+ "tuple",
2184
+ "list_comprehension",
2185
+ "dictionary_comprehension",
2186
+ "set_comprehension",
2187
+ "generator_expression"
2188
+ ]);
2189
+ function describeWrite(emitter, write, name) {
2190
+ if (write.value === null) {
2191
+ return {
2192
+ value: write.given,
2193
+ placeholder: false,
2194
+ construction: null,
2195
+ narrowsName: false
2196
+ };
2197
+ }
2198
+ return {
2199
+ value: valueKey(emitter, write.value),
2200
+ placeholder: write.value.type === "none",
2201
+ construction: CONSTRUCTION_TYPES.has(write.value.type) ? write.value.text.replace(/\s+/g, " ").trim() : null,
2202
+ narrowsName: write.value.type === "call" && startsAtName(write.value, name, NAME_READS)
2203
+ };
2204
+ }
2205
+ function settledValue(emitter, reading, name, writes) {
2206
+ const only = writes.length === 1 ? writes[0] : void 0;
2207
+ if (only !== void 0) {
2208
+ return only.value === null ? only.given : valueKey(emitter, only.value);
2209
+ }
2210
+ return valueLeftByWrites(
2211
+ writes.map((write) => describeWrite(emitter, write, name)),
2212
+ writesRunInOrder(reading.bodyOwner, name, writes, NAME_READS)
2213
+ );
2214
+ }
2215
+ function emitScopeWrites(emitter, reading, atModule) {
2216
+ for (const [name, writes] of reading.writes) {
2217
+ const settled = settledValue(emitter, reading, name, writes);
2218
+ const key = nameKey(emitter.filePath, emitter.enclosing, name);
2219
+ if (settled === null) {
2220
+ emitCandidates(emitter, key, name, writes);
2221
+ continue;
2222
+ }
2223
+ add(emitter, writes.length === 1 ? "binds" : "endsHolding", key, settled);
2224
+ if (atModule) {
2225
+ add(emitter, "exportsAs", emitter.filePath, name, settled);
2226
+ }
2227
+ }
2228
+ }
2229
+ function emitCandidates(emitter, key, name, writes) {
2230
+ for (const write of writes) {
2231
+ if (write.value === null) {
2232
+ if (write.given === null) {
2233
+ add(emitter, "writesUnstated", key);
2234
+ }
2235
+ continue;
2236
+ }
2237
+ if (!describeWrite(emitter, write, name).narrowsName) {
2238
+ add(emitter, "mayHold", key, valueKey(emitter, write.value));
2239
+ }
2240
+ }
2241
+ }
2242
+ function emitValueFacts(db, filePath, root) {
2243
+ const emitter = { db, filePath, enclosing: null };
2244
+ emitNestedDefinitions(emitter, root);
2245
+ emitScopeWrites(emitter, readScope(root, []), true);
1938
2246
  emitExpressionFacts(emitter, root);
1939
2247
  }
1940
2248
 
@@ -2047,7 +2355,7 @@ function pythonLowering(options) {
2047
2355
  return {
2048
2356
  idOf: (node) => node.id,
2049
2357
  expression: (node) => expressionOf(node, originOf2),
2050
- statement: statementOf,
2358
+ statement: statementOf2,
2051
2359
  siteOf,
2052
2360
  functionOf,
2053
2361
  writtenTo: (node) => context === null ? null : context.writtenTo(node),
@@ -2311,7 +2619,7 @@ var STATEMENT_TYPES = {
2311
2619
  body: blockStatements2(field(node, "body"))
2312
2620
  })
2313
2621
  };
2314
- function statementOf(node) {
2622
+ function statementOf2(node) {
2315
2623
  const lower = STATEMENT_TYPES[node.type];
2316
2624
  return lower === void 0 ? { kind: "opaque" } : lower(node);
2317
2625
  }
@@ -3381,9 +3689,13 @@ function constructionThroughFacts(name, scope, scan) {
3381
3689
  if (binding.kind !== "assignment" && binding.kind !== "importFrom") {
3382
3690
  return null;
3383
3691
  }
3384
- const nameKey = `${scan.bound.file}#${name}`;
3385
- const constructionKey2 = subjectConstructions(facts, [nameKey]).get(
3386
- nameKey
3692
+ const nameKey2 = nameKeyIn(
3693
+ scan.bound.file,
3694
+ scope.kind === "function" ? scope.node : enclosingFunction(scope.node),
3695
+ name
3696
+ );
3697
+ const constructionKey2 = subjectConstructions(facts, [nameKey2]).get(
3698
+ nameKey2
3387
3699
  )?.constructionKey;
3388
3700
  return constructionKey2 === void 0 ? null : scan.index.byValueKey.get(constructionKey2) ?? null;
3389
3701
  }
@@ -3474,11 +3786,11 @@ function walkStatements(statements, position, scan, visit) {
3474
3786
  visit(stmt, position, scan);
3475
3787
  }
3476
3788
  }
3477
- function targetNames(node) {
3789
+ function targetNames2(node) {
3478
3790
  if (node.type === "identifier") {
3479
3791
  return [node.text];
3480
3792
  }
3481
- return bodyStatements(node).flatMap(targetNames);
3793
+ return bodyStatements(node).flatMap(targetNames2);
3482
3794
  }
3483
3795
  function sequenceElementNames(node) {
3484
3796
  if (node === null || node.type !== "list" && node.type !== "tuple") {
@@ -3515,7 +3827,7 @@ function loopBindingsOf(stmt, position, scan) {
3515
3827
  });
3516
3828
  return bindings;
3517
3829
  }
3518
- for (const name of targetNames(left)) {
3830
+ for (const name of targetNames2(left)) {
3519
3831
  bindings.set(name, { kind: "unenumerable", location });
3520
3832
  }
3521
3833
  return bindings;
@@ -4991,14 +5303,9 @@ function matchedChains(calls, options) {
4991
5303
  if (chains.length === 0) {
4992
5304
  return [];
4993
5305
  }
4994
- const alreadyAsked = new Set(
4995
- options.facts.facts("wanted").map((row) => String(row[0]))
4996
- );
4997
5306
  resolveCalls(
4998
5307
  options.facts,
4999
- storageCallees(calls, options.filePath, options.couldMatch).filter(
5000
- (key) => !alreadyAsked.has(key)
5001
- )
5308
+ storageCallees(calls, options.filePath, options.couldMatch)
5002
5309
  );
5003
5310
  const matched = [];
5004
5311
  for (const chain of chains) {
@@ -6060,7 +6367,7 @@ function isInjectedParameter(param, injectedCallees, scope, ctx) {
6060
6367
  import fs4 from "fs";
6061
6368
  import path4 from "path";
6062
6369
  import { Database as Database3 } from "@suss/datalog";
6063
- import { explainResolvedKey, RESOLUTION_RULES as RESOLUTION_RULES2 } from "@suss/resolution";
6370
+ import { explainResolvedKey, RESOLUTION_RULES } from "@suss/resolution";
6064
6371
 
6065
6372
  // src/facts.ts
6066
6373
  function unitKey(filePath, range, name) {
@@ -6078,7 +6385,7 @@ function emitModuleImportFacts(db, filePath, module, resolverOptions) {
6078
6385
  if (binding.kind !== "import" && binding.kind !== "importFrom") {
6079
6386
  continue;
6080
6387
  }
6081
- const nameKey = `${filePath}#${localName}`;
6388
+ const nameKey2 = `${filePath}#${localName}`;
6082
6389
  const moduleText = importedModuleText(
6083
6390
  binding.module,
6084
6391
  binding.relativeLevel
@@ -6094,16 +6401,16 @@ function emitModuleImportFacts(db, filePath, module, resolverOptions) {
6094
6401
  resolution.status === "resolved" ? "resolved" : resolution.reason
6095
6402
  ]);
6096
6403
  const importedName = binding.kind === "import" ? binding.localName : binding.importedName;
6097
- db.add("pyImportedName", [nameKey, moduleText, importedName]);
6404
+ db.add("pyImportedName", [nameKey2, moduleText, importedName]);
6098
6405
  if (scope.kind === "module") {
6099
- db.add("exportsAs", [filePath, localName, nameKey]);
6406
+ db.add("exportsAs", [filePath, localName, nameKey2]);
6100
6407
  }
6101
6408
  if (resolution.status === "resolved") {
6102
6409
  db.add("pyImportResolved", [filePath, moduleText, resolution.file]);
6103
- db.add("imports", [nameKey, resolution.file, importedName]);
6410
+ db.add("imports", [nameKey2, resolution.file, importedName]);
6104
6411
  continue;
6105
6412
  }
6106
- db.add("imports", [nameKey, moduleText, importedName]);
6413
+ db.add("imports", [nameKey2, moduleText, importedName]);
6107
6414
  }
6108
6415
  }
6109
6416
  for (const openModule of module.openImports) {
@@ -6274,7 +6581,7 @@ import {
6274
6581
  unfollowedCallGap,
6275
6582
  worthRecording
6276
6583
  } from "@suss/behavioral-ir";
6277
- import { Database, evaluate as evaluate2, lit, rule, variable as v } from "@suss/datalog";
6584
+ import { Database, evaluate, lit, rule, variable as v } from "@suss/datalog";
6278
6585
  import {
6279
6586
  assembleSummary,
6280
6587
  SKIP_CHILDREN as SKIP_CHILDREN2,
@@ -6282,251 +6589,227 @@ import {
6282
6589
  } from "@suss/extractor";
6283
6590
 
6284
6591
  // src/reach/resolveCallee.ts
6285
- var stop = (reason) => ({ kind: "stop", reason });
6286
- var MODULE_STOP = {
6287
- ambiguous: "multipleSources",
6288
- outsideRoots: "outsideRun"
6592
+ import {
6593
+ calleeOutcomeOf,
6594
+ calleeOutcomes,
6595
+ writtenSourcesOf
6596
+ } from "@suss/resolution";
6597
+ var STOP_FOR = {
6598
+ severalSources: "multipleSources",
6599
+ outsideRun: "outsideRun",
6600
+ unsettled: "unsettledValue",
6601
+ undeclared: "noDeclaration"
6289
6602
  };
6290
- function resolveCallee2(call, site, ctx) {
6291
- const callee = field(call, "function");
6292
- if (callee === null) {
6293
- return { kind: "stopped", reason: "noDeclaration" };
6603
+ var NO_DECLARATION = {
6604
+ kind: "stopped",
6605
+ reason: "noDeclaration"
6606
+ };
6607
+ function calleeSpellings(calls, ctx) {
6608
+ const spellingOf = /* @__PURE__ */ new Map();
6609
+ const keys = /* @__PURE__ */ new Set();
6610
+ for (const { call, site } of calls) {
6611
+ const spelling = spellingFor(call, site, ctx);
6612
+ spellingOf.set(call.id, spelling);
6613
+ if (spelling.kind === "key") {
6614
+ keys.add(spelling.key);
6615
+ }
6294
6616
  }
6295
- return asCallee(resolveExpression(callee, site, ctx), ctx);
6617
+ return { spellingOf, outcomes: calleeOutcomes(ctx.facts, [...keys]) };
6296
6618
  }
6297
- function resolveNamedFunctionArgument(name, site, ctx) {
6298
- if (site.rebound.has(name)) {
6299
- return null;
6619
+ function resolveCallee2(call, site, ctx, read) {
6620
+ const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site, ctx);
6621
+ if (spelling.kind === "stopped") {
6622
+ return spelling;
6300
6623
  }
6301
- const binding = resolveName(site.scope, name);
6302
- if (binding !== null && binding.kind === "assignment") {
6624
+ const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key);
6625
+ return asCallee(outcome, site.owner, ctx);
6626
+ }
6627
+ function functionNamed(nameKey2, ctx) {
6628
+ if (assignedElsewhere(ctx.facts, nameKey2)) {
6303
6629
  return null;
6304
6630
  }
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;
6631
+ const outcome = calleeOutcomeOf(ctx.facts, nameKey2);
6632
+ return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
6307
6633
  }
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" };
6634
+ function assignedElsewhere(facts, nameKey2) {
6635
+ return writtenSourcesOf(facts, nameKey2).some(
6636
+ (source) => !facts.has("func", [source])
6637
+ );
6325
6638
  }
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);
6639
+ function readThrough(node) {
6640
+ if (node?.type !== "parenthesized_expression") {
6641
+ return node;
6343
6642
  }
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);
6643
+ return readThrough(children(node)[0] ?? null);
6348
6644
  }
6349
- function instanceBuiltBy(built, otherwise) {
6350
- if (built.kind === "class" || built.kind === "stop") {
6351
- return built;
6645
+ function spellingFor(call, site, ctx) {
6646
+ const callee = readThrough(field(call, "function"));
6647
+ if (callee === null) {
6648
+ return { kind: "stopped", reason: "noDeclaration" };
6352
6649
  }
6353
- return built.kind === "package" ? stop("outsideRun") : stop(otherwise);
6650
+ return throughModules(callee, site, ctx) ?? {
6651
+ kind: "key",
6652
+ key: readKey(site.file.file, callee, enclosingFunction(callee))
6653
+ };
6354
6654
  }
6355
- function resolveIdentifier(name, site, ctx) {
6356
- if (site.rebound.has(name)) {
6357
- return stop("unsettledValue");
6655
+ function asCallee(outcome, owner, ctx) {
6656
+ if (outcome.kind === "function") {
6657
+ return functionCallee(outcome.key, ctx);
6358
6658
  }
6359
- if (isReceiver(name, site.scope)) {
6360
- return receiverClass(site);
6659
+ if (outcome.kind === "object") {
6660
+ return constructorOf(outcome.key, ctx);
6361
6661
  }
6362
- const binding = resolveName(site.scope, name);
6363
- if (binding === null) {
6364
- return throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set());
6662
+ if (outcome.kind === "callerSupplied") {
6663
+ return outcome.key.startsWith(`${owner}#`) ? { kind: "stopped", reason: "callerSupplied" } : { kind: "stopped", reason: "unsettledValue" };
6365
6664
  }
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";
6665
+ return {
6666
+ kind: "stopped",
6667
+ reason: STOP_FOR[outcome.kind] ?? "noDeclaration"
6668
+ };
6370
6669
  }
6371
- function receiverClass(site) {
6372
- const classScope = site.scope.parent;
6373
- if (classScope === null || classScope === void 0) {
6374
- return stop("noDeclaration");
6670
+ function functionCallee(key, ctx) {
6671
+ if (ctx.definitions.get(key)?.type === "lambda") {
6672
+ return { kind: "stopped", reason: "unsettledValue" };
6375
6673
  }
6376
- return { kind: "class", file: site.file, node: classScope.node };
6674
+ const target = functionAt(key, ctx);
6675
+ return target === null ? NO_DECLARATION : { kind: "followed", target };
6377
6676
  }
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
- );
6677
+ function constructorOf(classKey, ctx) {
6678
+ const declared = ctx.facts.lookup("holdsProperty", 0, classKey).find((row) => String(row[1]) === "__init__");
6679
+ if (declared === void 0) {
6680
+ return NO_DECLARATION;
6424
6681
  }
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");
6682
+ return functionCallee(String(declared[2]), ctx);
6438
6683
  }
6439
- function reachedFunction(file, node, owner) {
6684
+ function functionAt(key, ctx) {
6685
+ const node = ctx.definitions.get(key);
6686
+ const file = ctx.filesByPath.get(key.slice(0, key.lastIndexOf(":")));
6687
+ if (node === void 0 || file === void 0) {
6688
+ return null;
6689
+ }
6440
6690
  const name = field(node, "name")?.text ?? "<anon>";
6441
- const ownerName = owner === void 0 ? null : field(owner, "name")?.text;
6691
+ const owner = ownerClassOf(node);
6692
+ const ownerName = owner === null ? void 0 : field(owner, "name")?.text;
6442
6693
  return {
6443
6694
  file,
6444
6695
  node,
6445
6696
  name,
6446
- exportPath: ownerName === null || ownerName === void 0 ? [name] : [ownerName, name]
6697
+ exportPath: ownerName === void 0 ? [name] : [ownerName, name]
6447
6698
  };
6448
6699
  }
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]);
6700
+ function ownerClassOf(node) {
6701
+ for (let up = node.parent; up !== null; up = up.parent) {
6702
+ if (isFunction(up)) {
6703
+ return null;
6704
+ }
6705
+ if (up.type === "class_definition") {
6706
+ return up;
6707
+ }
6457
6708
  }
6458
- const file = ctx.filesByPath.get(resolution.file);
6459
- return file === void 0 ? stop("outsideRun") : { kind: "module", file };
6709
+ return null;
6460
6710
  }
6461
- function memberOf(base, name, ctx, visited = /* @__PURE__ */ new Set()) {
6462
- if (base.kind === "module") {
6463
- return memberOfModule(base.file, name, ctx, visited);
6711
+ function throughModules(callee, site, ctx) {
6712
+ if (callee.type === "identifier") {
6713
+ return resolveName(site.scope, callee.text) === null ? openImportMember(site.file, callee.text, ctx, /* @__PURE__ */ new Set()) : null;
6464
6714
  }
6465
- if (base.kind === "package") {
6466
- return moduleNamed(
6467
- base.from,
6468
- base.dotted === "" ? name : `${base.dotted}.${name}`,
6469
- base.relativeLevel,
6470
- ctx
6471
- );
6715
+ if (callee.type !== "attribute") {
6716
+ return null;
6472
6717
  }
6473
- if (base.kind === "class") {
6474
- return memberOfClass(base.file, base.node, name, ctx);
6718
+ const object = field(callee, "object");
6719
+ const attribute = field(callee, "attribute");
6720
+ if (object === null || attribute === null) {
6721
+ return null;
6475
6722
  }
6476
- if (base.kind === "stop") {
6477
- return base;
6723
+ const base = moduleAt(object, site, ctx);
6724
+ return base === null ? null : memberKey(base, attribute.text, ctx);
6725
+ }
6726
+ function openImportMember(file, name, ctx, visited) {
6727
+ if (visited.has(file.file)) {
6728
+ return null;
6729
+ }
6730
+ visited.add(file.file);
6731
+ const found = /* @__PURE__ */ new Set();
6732
+ for (const spec of file.module.openImports) {
6733
+ const dots = spec.length - spec.replace(/^\.+/, "").length;
6734
+ const opened = moduleNamed(file, spec.slice(dots), dots, ctx);
6735
+ if (opened === null || opened.kind !== "module") {
6736
+ continue;
6737
+ }
6738
+ const member = opened.file.module.moduleScope.bindings.has(name) ? { kind: "key", key: `${opened.file.file}#${name}` } : openImportMember(opened.file, name, ctx, visited);
6739
+ if (member?.kind === "key") {
6740
+ found.add(member.key);
6741
+ }
6742
+ }
6743
+ if (found.size > 1) {
6744
+ return { kind: "stopped", reason: "multipleSources" };
6478
6745
  }
6479
- return stop("noDeclaration");
6746
+ const only = [...found][0];
6747
+ return only === void 0 ? null : { kind: "key", key: only };
6480
6748
  }
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");
6749
+ function moduleAt(node, site, ctx) {
6750
+ if (node.type === "identifier") {
6751
+ return importedModule(node.text, site, ctx);
6486
6752
  }
6487
- if (binding.kind === "functionDef") {
6488
- return {
6489
- kind: "function",
6490
- target: reachedFunction(file, binding.node, classNode)
6491
- };
6753
+ if (node.type !== "attribute") {
6754
+ return null;
6492
6755
  }
6493
- return resolveBinding(binding, file, classScope, ctx, /* @__PURE__ */ new Set());
6756
+ const object = field(node, "object");
6757
+ const attribute = field(node, "attribute");
6758
+ if (object === null || attribute === null) {
6759
+ return null;
6760
+ }
6761
+ const base = moduleAt(object, site, ctx);
6762
+ return base === null ? null : submoduleOf(base, attribute.text, ctx);
6494
6763
  }
6495
- function memberOfModule(file, name, ctx, visited) {
6496
- const key = `${file.file}#${name}`;
6497
- if (visited.has(key)) {
6498
- return stop("unsettledValue");
6764
+ function importedModule(name, site, ctx) {
6765
+ const binding = resolveName(site.scope, name);
6766
+ if (binding?.kind === "import") {
6767
+ const head = binding.module.split(".")[0];
6768
+ const dotted = binding.localName === head ? binding.localName : binding.module;
6769
+ return moduleNamed(site.file, dotted, 0, ctx);
6499
6770
  }
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);
6771
+ if (binding?.kind !== "importFrom") {
6772
+ return null;
6504
6773
  }
6505
- if (file.file.endsWith("__init__.py")) {
6506
- const submodule = moduleNamed(file, name, 1, ctx);
6507
- if (submodule.kind === "module") {
6508
- return submodule;
6509
- }
6774
+ const from = moduleNamed(
6775
+ site.file,
6776
+ binding.module,
6777
+ binding.relativeLevel,
6778
+ ctx
6779
+ );
6780
+ return from === null ? null : submoduleOf(from, binding.importedName, ctx);
6781
+ }
6782
+ function submoduleOf(base, name, ctx) {
6783
+ if (base.kind === "package") {
6784
+ return moduleNamed(
6785
+ base.from,
6786
+ base.dotted === "" ? name : `${base.dotted}.${name}`,
6787
+ base.relativeLevel,
6788
+ ctx
6789
+ );
6510
6790
  }
6511
- return throughOpenImports(file, name, ctx, visited);
6791
+ return base.file.file.endsWith("__init__.py") ? moduleNamed(base.file, name, 1, ctx) : null;
6512
6792
  }
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
- }
6793
+ function memberKey(base, name, ctx) {
6794
+ if (base.kind === "package") {
6795
+ return null;
6525
6796
  }
6526
- if (found.length > 1) {
6527
- return stop("multipleSources");
6797
+ if (base.file.module.moduleScope.bindings.has(name)) {
6798
+ return { kind: "key", key: `${base.file.file}#${name}` };
6528
6799
  }
6529
- return found[0] ?? stop("noDeclaration");
6800
+ return openImportMember(base.file, name, ctx, /* @__PURE__ */ new Set());
6801
+ }
6802
+ function moduleNamed(from, module, relativeLevel, ctx) {
6803
+ const resolution = resolveModule(
6804
+ from.file,
6805
+ { module, relativeLevel },
6806
+ { roots: ctx.roots }
6807
+ );
6808
+ if (resolution.status !== "resolved") {
6809
+ return resolution.reason === "external" ? { kind: "package", dotted: module, relativeLevel, from } : null;
6810
+ }
6811
+ const file = ctx.filesByPath.get(resolution.file);
6812
+ return file === void 0 ? null : { kind: "module", file };
6530
6813
  }
6531
6814
 
6532
6815
  // src/reach/closure.ts
@@ -6541,7 +6824,9 @@ var REACHABLE_RULES = [
6541
6824
  function reachedFunctions(seeds, options) {
6542
6825
  const ctx = {
6543
6826
  filesByPath: new Map(options.files.map((file) => [file.file, file])),
6544
- roots: options.roots
6827
+ roots: options.roots,
6828
+ facts: options.facts,
6829
+ definitions: options.definitions
6545
6830
  };
6546
6831
  const db = new Database();
6547
6832
  const functionByKey = /* @__PURE__ */ new Map();
@@ -6563,21 +6848,26 @@ function reachedFunctions(seeds, options) {
6563
6848
  db.add("entry", [seed.key]);
6564
6849
  }
6565
6850
  for (; ; ) {
6566
- evaluate2(db, REACHABLE_RULES);
6851
+ evaluate(db, REACHABLE_RULES);
6567
6852
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
6568
6853
  if (frontier.length === 0) {
6569
6854
  break;
6570
6855
  }
6571
- for (const key of frontier) {
6856
+ const bodies = frontier.flatMap((key) => {
6572
6857
  scanned.add(key);
6573
6858
  const source = functionByKey.get(key);
6574
- if (source === void 0) {
6575
- continue;
6576
- }
6859
+ return source === void 0 ? [] : [{ key, source, ...bodyOf(source) }];
6860
+ });
6861
+ const spellings = calleeSpellings(
6862
+ bodies.flatMap((body) => body.written),
6863
+ ctx
6864
+ );
6865
+ for (const { key, source, body, written } of bodies) {
6577
6866
  const scan = scanBody(
6578
6867
  source,
6579
6868
  ctx,
6580
- recognizedCallIds(source.node, options.storageFor(source.file))
6869
+ recognizedCallIds(source.node, options.storageFor(source.file)),
6870
+ { body, written, spellings }
6581
6871
  );
6582
6872
  if (scan.stops.length > 0) {
6583
6873
  stopsByKey.set(key, scan.stops);
@@ -6643,7 +6933,18 @@ var EMPTY_SCAN = {
6643
6933
  parameterCalls: [],
6644
6934
  passedPositions: /* @__PURE__ */ new Set()
6645
6935
  };
6646
- function scanBody(source, ctx, recognized) {
6936
+ function bodyOf(source) {
6937
+ const { file, node } = source;
6938
+ const body = field(node, "body");
6939
+ if (body === null) {
6940
+ return { body, written: [] };
6941
+ }
6942
+ return {
6943
+ body,
6944
+ written: callsWritten(file, body, scopeAt2(file, node), keyOf(source))
6945
+ };
6946
+ }
6947
+ function scanBody(source, ctx, recognized, read) {
6647
6948
  const followed = [];
6648
6949
  const stops = [];
6649
6950
  const placements = new TargetPlacements();
@@ -6652,12 +6953,12 @@ function scanBody(source, ctx, recognized) {
6652
6953
  const seen = /* @__PURE__ */ new Set();
6653
6954
  const parameterCallsSeen = /* @__PURE__ */ new Set();
6654
6955
  const { file, node } = source;
6655
- const body = field(node, "body");
6656
- if (body === null) {
6956
+ const { written, spellings } = read;
6957
+ if (read.body === null) {
6657
6958
  return EMPTY_SCAN;
6658
6959
  }
6659
6960
  const ownParameters = callParameterNames(node, source.exportPath.length > 1);
6660
- const recordPassedArgs = (call, callee, calleeKey, where) => {
6961
+ const recordPassedArgs = (call, callee, calleeKey) => {
6661
6962
  const args = field(call, "arguments");
6662
6963
  if (args === null) {
6663
6964
  return;
@@ -6666,9 +6967,8 @@ function scanBody(source, ctx, recognized) {
6666
6967
  if (arg === null || arg.type !== "identifier") {
6667
6968
  return;
6668
6969
  }
6669
- const resolved = resolveNamedFunctionArgument(
6670
- arg.text,
6671
- { file, scope: where.scope, rebound: where.rebound },
6970
+ const resolved = functionNamed(
6971
+ readKey(file.file, arg, enclosingFunction(arg)),
6672
6972
  ctx
6673
6973
  );
6674
6974
  if (resolved === null) {
@@ -6688,13 +6988,9 @@ function scanBody(source, ctx, recognized) {
6688
6988
  }
6689
6989
  });
6690
6990
  };
6691
- const record = (call, where) => {
6991
+ const record = (call, site) => {
6692
6992
  const callee = calleeText(call);
6693
- const outcome = resolveCallee2(
6694
- call,
6695
- { file, scope: where.scope, rebound: where.rebound },
6696
- ctx
6697
- );
6993
+ const outcome = resolveCallee2(call, site, ctx, spellings);
6698
6994
  const placed = outcome.kind === "followed" ? {
6699
6995
  file: outcome.target.file.displayPath,
6700
6996
  span: spanOf(outcome.target.node)
@@ -6703,8 +6999,7 @@ function scanBody(source, ctx, recognized) {
6703
6999
  recordPassedArgs(
6704
7000
  call,
6705
7001
  callee,
6706
- outcome.kind === "followed" ? keyOf(outcome.target) : null,
6707
- where
7002
+ outcome.kind === "followed" ? keyOf(outcome.target) : null
6708
7003
  );
6709
7004
  if (outcome.kind === "stopped") {
6710
7005
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6727,32 +7022,9 @@ function scanBody(source, ctx, recognized) {
6727
7022
  followed.push(outcome.target);
6728
7023
  }
6729
7024
  };
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
- );
7025
+ for (const { call, site } of written) {
7026
+ record(call, site);
7027
+ }
6756
7028
  return {
6757
7029
  followed,
6758
7030
  stops,
@@ -6762,6 +7034,18 @@ function scanBody(source, ctx, recognized) {
6762
7034
  passedPositions
6763
7035
  };
6764
7036
  }
7037
+ function callsWritten(file, body, outer, owner) {
7038
+ const found = [];
7039
+ walkDescendants3(body, outer, {
7040
+ at: (child, scope) => {
7041
+ if (child.type === "call") {
7042
+ found.push({ call: child, site: { file, scope, owner } });
7043
+ }
7044
+ },
7045
+ into: (child, scope) => child.type === "function_definition" ? SKIP_CHILDREN2 : file.module.scopeFor.get(child.id) ?? scope
7046
+ });
7047
+ return found;
7048
+ }
6765
7049
  function scopeAt2(file, node) {
6766
7050
  let current = node;
6767
7051
  while (current !== null) {
@@ -6773,40 +7057,6 @@ function scopeAt2(file, node) {
6773
7057
  }
6774
7058
  return file.module.moduleScope;
6775
7059
  }
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
7060
  function identifiersUnder(node, found = []) {
6811
7061
  if (node.type === "identifier") {
6812
7062
  found.push(node.text);
@@ -6819,10 +7069,6 @@ function identifiersUnder(node, found = []) {
6819
7069
  }
6820
7070
  return found;
6821
7071
  }
6822
- function lambdaParameters(lambda) {
6823
- const parameters = field(lambda, "parameters");
6824
- return parameters === null ? [] : identifiersUnder(parameters);
6825
- }
6826
7072
  function libraryUnit(target, options) {
6827
7073
  const { file, node, name, exportPath } = target;
6828
7074
  const body = field(node, "body");
@@ -6973,6 +7219,18 @@ var PythonWrapperIndex = class {
6973
7219
  ownRoutes = /* @__PURE__ */ new Map();
6974
7220
  unitsByKey = /* @__PURE__ */ new Map();
6975
7221
  unitsByFile = /* @__PURE__ */ new Map();
7222
+ /** The project function a module-level name in this file refers to, or null for anything else. */
7223
+ functionCalled(file, name) {
7224
+ if (this.options.facts === void 0) {
7225
+ return null;
7226
+ }
7227
+ return functionNamed(nameKeyIn(file, null, name), {
7228
+ filesByPath: this.filesByPath,
7229
+ roots: this.options.roots,
7230
+ facts: this.options.facts,
7231
+ definitions: this.options.definitions
7232
+ });
7233
+ }
6976
7234
  register(covers, target, declared) {
6977
7235
  const registered = this.registered(target, declared);
6978
7236
  const table = covers.kind === "everyRoute" ? this.everyRoute : this.ownRoutes;
@@ -7019,20 +7277,12 @@ var PythonWrapperIndex = class {
7019
7277
  if (declared.form.type !== "dependency") {
7020
7278
  continue;
7021
7279
  }
7022
- const site = {
7023
- file,
7024
- scope: query.module.moduleScope,
7025
- rebound: /* @__PURE__ */ new Set()
7026
- };
7027
7280
  const targets = [
7028
7281
  ...decoratorDependencies(query.classification, declared.form),
7029
7282
  ...parameterDependencies(query.definitionNode, declared.form)
7030
7283
  ];
7031
7284
  for (const name of targets) {
7032
- const target = resolveNamedFunctionArgument(name, site, {
7033
- filesByPath: this.filesByPath,
7034
- roots: this.options.roots
7035
- });
7285
+ const target = this.functionCalled(file.file, name);
7036
7286
  if (target !== null) {
7037
7287
  found.push(this.registered(target, declared));
7038
7288
  }
@@ -7174,12 +7424,8 @@ function registerConstructorDependencies(file, declared, form, index) {
7174
7424
  if (listed === void 0) {
7175
7425
  continue;
7176
7426
  }
7177
- const site = { file, scope, rebound: /* @__PURE__ */ new Set() };
7178
7427
  for (const name of dependencyNamesIn(listed.node, form)) {
7179
- const target = resolveNamedFunctionArgument(name, site, {
7180
- filesByPath: index.filesByPath,
7181
- roots: index.options.roots
7182
- });
7428
+ const target = index.functionCalled(file.file, name);
7183
7429
  if (target !== null) {
7184
7430
  index.register(
7185
7431
  coverageOf(match, file, declared.pack),
@@ -7419,6 +7665,31 @@ function reportUnresolvedProjectModules(options, db) {
7419
7665
  }
7420
7666
  }
7421
7667
  }
7668
+ function emitModelQueryFacts(db, packs) {
7669
+ for (const pack of packs) {
7670
+ for (const model of pack.models ?? []) {
7671
+ for (const base of model.baseNames) {
7672
+ for (const method of model.givesBack) {
7673
+ db.add("givesBackOne", [base, method]);
7674
+ }
7675
+ for (const entry of model.entryMethods) {
7676
+ db.add("givesBackOneOfArgument", [
7677
+ base,
7678
+ entry.method,
7679
+ String(entry.argument)
7680
+ ]);
7681
+ }
7682
+ }
7683
+ for (const entry of model.entryFunctions) {
7684
+ db.add("givesBackOneOfImport", [
7685
+ entry.module,
7686
+ entry.name,
7687
+ String(entry.argument)
7688
+ ]);
7689
+ }
7690
+ }
7691
+ }
7692
+ }
7422
7693
  async function extractPythonProject(options) {
7423
7694
  const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
7424
7695
  const cacheDir = adapterStamp.declineWhenRunFromSource(
@@ -7466,8 +7737,9 @@ async function extractPythonProject(options) {
7466
7737
  );
7467
7738
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
7468
7739
  const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
7740
+ const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
7469
7741
  const discovers = options.packs.some((pack) => pack.discovery.length > 0);
7470
- const needsValues = discovers || mountsRouters || storagePatterns.length > 0;
7742
+ const needsValues = discovers || mountsRouters || storagePatterns.length > 0 || modelQueries.length > 0;
7471
7743
  const definitions = /* @__PURE__ */ new Map();
7472
7744
  timer.time("discover", () => {
7473
7745
  for (const { file, root, module: moduleBinding } of bound) {
@@ -7480,6 +7752,7 @@ async function extractPythonProject(options) {
7480
7752
  if (needsValues) {
7481
7753
  bindEvaluator(db, { files: bound, definitions });
7482
7754
  }
7755
+ emitModelQueryFacts(db, options.packs);
7483
7756
  });
7484
7757
  reportUnresolvedProjectModules(options, db);
7485
7758
  const couldMatch = timer.time(
@@ -7507,6 +7780,7 @@ async function extractPythonProject(options) {
7507
7780
  packs: options.packs,
7508
7781
  roots: options.roots,
7509
7782
  facts: needsValues ? db : void 0,
7783
+ definitions,
7510
7784
  storageFor
7511
7785
  })
7512
7786
  );
@@ -7590,7 +7864,9 @@ async function extractPythonProject(options) {
7590
7864
  files: bound,
7591
7865
  roots: options.roots,
7592
7866
  gapHandling,
7593
- storageFor
7867
+ storageFor,
7868
+ facts: db,
7869
+ definitions
7594
7870
  })
7595
7871
  );
7596
7872
  for (const [key, owners] of summariesBySeed) {
@@ -7852,7 +8128,7 @@ var PythonWhySession = class {
7852
8128
  explain(value, options = {}) {
7853
8129
  return explainResolvedKey({
7854
8130
  db: this.db,
7855
- rules: RESOLUTION_RULES2,
8131
+ rules: RESOLUTION_RULES,
7856
8132
  key: readKey(value.file, value.node, enclosingFunction(value.node)),
7857
8133
  locate: (key) => this.locate(key),
7858
8134
  displayPath: (key) => this.displayPath(key),
@@ -7982,6 +8258,7 @@ export {
7982
8258
  emitValueFacts,
7983
8259
  extractPythonProject,
7984
8260
  findPythonFiles,
8261
+ nameKeyIn,
7985
8262
  nodeId,
7986
8263
  objectReturnedBy,
7987
8264
  parsePython,