@suss/adapter-python 0.22.2 → 0.22.3

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
@@ -34,6 +34,40 @@ function enclosingFunction(node) {
34
34
  }
35
35
  return null;
36
36
  }
37
+ function stringContentValue(content) {
38
+ const base = content.startIndex;
39
+ let text2 = "";
40
+ let cursor = 0;
41
+ for (const child of children(content)) {
42
+ if (child.type !== "escape_interpolation") {
43
+ continue;
44
+ }
45
+ text2 += content.text.slice(cursor, child.startIndex - base);
46
+ text2 += child.text.slice(0, 1);
47
+ cursor = child.endIndex - base;
48
+ }
49
+ return text2 + content.text.slice(cursor);
50
+ }
51
+ function parameterNameAndType(param) {
52
+ if (param.type === "identifier") {
53
+ return { name: param.text, typeNode: null };
54
+ }
55
+ if (param.type === "typed_parameter") {
56
+ const inner = param.namedChildren.find(
57
+ (child) => child !== null && child.type === "identifier"
58
+ );
59
+ return inner !== void 0 ? { name: inner.text, typeNode: field(param, "type") } : null;
60
+ }
61
+ if (param.type === "default_parameter") {
62
+ const nameNode = field(param, "name");
63
+ return nameNode?.type === "identifier" ? { name: nameNode.text, typeNode: null } : null;
64
+ }
65
+ if (param.type === "typed_default_parameter") {
66
+ const nameNode = field(param, "name");
67
+ return nameNode !== null ? { name: nameNode.text, typeNode: field(param, "type") } : null;
68
+ }
69
+ return null;
70
+ }
37
71
  function stringLiteralValue(node) {
38
72
  if (node.type !== "string") {
39
73
  return null;
@@ -44,7 +78,7 @@ function stringLiteralValue(node) {
44
78
  continue;
45
79
  }
46
80
  if (child.type === "string_content") {
47
- content += child.text;
81
+ content += stringContentValue(child);
48
82
  continue;
49
83
  }
50
84
  if (child.type === "interpolation") {
@@ -72,6 +106,8 @@ function stripDecorators(node) {
72
106
  );
73
107
  return { definition, decorators };
74
108
  }
109
+ var NodeSet = class extends IdSet {
110
+ };
75
111
  var NodeMap = class extends IdMap {
76
112
  };
77
113
 
@@ -1418,14 +1454,14 @@ var EXIT_KINDS = {
1418
1454
  continue_statement: "continue"
1419
1455
  };
1420
1456
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
1421
- function exitKindOf(node) {
1457
+ function exitKindOf(node, thrown) {
1422
1458
  let sawReturn = false;
1423
1459
  const raises = (current) => {
1424
1460
  for (const child of current.namedChildren) {
1425
1461
  if (child === null || NESTED_FUNCTION_TYPES.has(child.type)) {
1426
1462
  continue;
1427
1463
  }
1428
- if (child.type === "raise_statement") {
1464
+ if (child.type === "raise_statement" || thrown.has(child)) {
1429
1465
  return true;
1430
1466
  }
1431
1467
  if (child.type === "return_statement") {
@@ -1459,10 +1495,12 @@ function childBlock(node) {
1459
1495
  var Lowerer = class {
1460
1496
  // tree-sitter hands back a fresh wrapper object each time a child is
1461
1497
  // read, so a terminal is matched by its stable node id.
1462
- constructor(terminals) {
1498
+ constructor(terminals, thrown) {
1463
1499
  this.terminals = terminals;
1500
+ this.thrown = thrown;
1464
1501
  }
1465
1502
  terminals;
1503
+ thrown;
1466
1504
  terminalsByStmt = /* @__PURE__ */ new Map();
1467
1505
  terminalHome = new NodeMap();
1468
1506
  attach(statement, candidates) {
@@ -1495,7 +1533,7 @@ var Lowerer = class {
1495
1533
  condition: handleOf(field(clause, "condition")),
1496
1534
  thenBody: this.lowerBlock(field(clause, "consequence")),
1497
1535
  elseBody: this.lowerIfTail(clauses, index + 1),
1498
- exitKind: exitKindOf(clause)
1536
+ exitKind: exitKindOf(clause, this.thrown)
1499
1537
  };
1500
1538
  this.attach(nested, [field(clause, "condition")]);
1501
1539
  return [nested];
@@ -1509,7 +1547,7 @@ var Lowerer = class {
1509
1547
  condition: handleOf(field(node, "condition")),
1510
1548
  thenBody: this.lowerBlock(field(node, "consequence")),
1511
1549
  elseBody: this.lowerIfTail(clauses, 0),
1512
- exitKind: exitKindOf(node)
1550
+ exitKind: exitKindOf(node, this.thrown)
1513
1551
  };
1514
1552
  }
1515
1553
  lowerTry(node) {
@@ -1524,7 +1562,7 @@ var Lowerer = class {
1524
1562
  tryBody: this.lowerBlock(field(node, "body")),
1525
1563
  catchBody: excepts.length > 0 ? excepts.flatMap((clause) => this.lowerBlock(childBlock(clause))) : null,
1526
1564
  finallyBody: finallyClause === null ? null : this.lowerBlock(childBlock(finallyClause)),
1527
- exitKind: exitKindOf(node)
1565
+ exitKind: exitKindOf(node, this.thrown)
1528
1566
  };
1529
1567
  }
1530
1568
  lowerMatch(node) {
@@ -1541,15 +1579,21 @@ var Lowerer = class {
1541
1579
  body: this.lowerBlock(field(clause, "consequence"))
1542
1580
  };
1543
1581
  });
1544
- return { kind: "switch", groups, exitKind: exitKindOf(node) };
1582
+ return { kind: "switch", groups, exitKind: exitKindOf(node, this.thrown) };
1545
1583
  }
1546
1584
  lower(node) {
1547
- const exit = EXIT_KINDS[node.type];
1548
- if (exit !== void 0) {
1549
- return this.attach({ kind: "exit", exit, exitKind: exitKindOf(node) }, [
1585
+ if (this.thrown.has(node)) {
1586
+ return this.attach({ kind: "exit", exit: "throw", exitKind: "throw" }, [
1550
1587
  node
1551
1588
  ]);
1552
1589
  }
1590
+ const exit = EXIT_KINDS[node.type];
1591
+ if (exit !== void 0) {
1592
+ return this.attach(
1593
+ { kind: "exit", exit, exitKind: exitKindOf(node, this.thrown) },
1594
+ [node]
1595
+ );
1596
+ }
1553
1597
  if (node.type === "if_statement") {
1554
1598
  return this.attach(this.lowerIf(node), [field(node, "condition")]);
1555
1599
  }
@@ -1560,7 +1604,7 @@ var Lowerer = class {
1560
1604
  kind: "loop",
1561
1605
  condition: handleOf(header),
1562
1606
  body: this.lowerBlock(field(node, "body")),
1563
- exitKind: exitKindOf(node)
1607
+ exitKind: exitKindOf(node, this.thrown)
1564
1608
  },
1565
1609
  [header]
1566
1610
  );
@@ -1571,12 +1615,16 @@ var Lowerer = class {
1571
1615
  if (node.type === "match_statement") {
1572
1616
  return this.attach(this.lowerMatch(node), [field(node, "subject")]);
1573
1617
  }
1574
- return this.attach({ kind: "opaque", exitKind: exitKindOf(node) }, [node]);
1618
+ return this.attach(
1619
+ { kind: "opaque", exitKind: exitKindOf(node, this.thrown) },
1620
+ [node]
1621
+ );
1575
1622
  }
1576
1623
  };
1577
- function lowerPythonBody(body, terminals) {
1624
+ function lowerPythonBody(body, terminals, thrown = new NodeSet()) {
1578
1625
  const lowerer = new Lowerer(
1579
- new Map(terminals.map((terminal) => [terminal.id, terminal]))
1626
+ new Map(terminals.map((terminal) => [terminal.id, terminal])),
1627
+ thrown
1580
1628
  );
1581
1629
  return {
1582
1630
  statements: lowerer.lowerBlock(body),
@@ -1773,1192 +1821,1464 @@ function invocationEffects(definitionNode) {
1773
1821
  });
1774
1822
  }
1775
1823
 
1776
- // src/paths/returnedShape.ts
1777
- var MAX_DEPTH = 6;
1778
- var SCALARS = {
1779
- none: { type: "null" },
1780
- true: { type: "boolean" },
1781
- false: { type: "boolean" }
1824
+ // src/paths/raisedResponses.ts
1825
+ import { constantOf } from "@suss/values";
1826
+
1827
+ // src/values/evaluator.ts
1828
+ import { nodeOfKey } from "@suss/resolution";
1829
+ import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
1830
+
1831
+ // src/values/lowering.ts
1832
+ var ROOT_TYPES = /* @__PURE__ */ new Set(["module", "function_definition", "lambda"]);
1833
+ var LITERAL_TYPES = {
1834
+ integer: (node) => ({ kind: "literal", value: Number(node.text) }),
1835
+ float: (node) => ({ kind: "literal", value: Number(node.text) }),
1836
+ true: () => ({ kind: "literal", value: true }),
1837
+ false: () => ({ kind: "literal", value: false }),
1838
+ none: () => ({ kind: "literal", value: null })
1782
1839
  };
1783
- function children2(node) {
1784
- return node.namedChildren.filter((child) => child !== null);
1840
+ var WRITING_ROWS = /* @__PURE__ */ new Set(["append", "extend"]);
1841
+ function readingMethodsOf(rows) {
1842
+ return new Set(
1843
+ rows.flatMap(
1844
+ (row) => row.kind === "method" && !WRITING_ROWS.has(row.method) ? [row.method] : []
1845
+ )
1846
+ );
1785
1847
  }
1786
- function recordShape(node, depth) {
1787
- const properties = {};
1788
- let everyKeyWritten = true;
1789
- for (const pair of children2(node)) {
1790
- if (pair.type !== "pair") {
1791
- everyKeyWritten = false;
1792
- continue;
1793
- }
1794
- const key = field(pair, "key");
1795
- const value = field(pair, "value");
1796
- if (key === null || value === null || key.type !== "string") {
1797
- everyKeyWritten = false;
1798
- continue;
1799
- }
1800
- properties[key.text.slice(1, -1)] = shapeOfReturned(value, depth + 1);
1801
- }
1802
- return everyKeyWritten && Object.keys(properties).length > 0 ? { type: "record", properties } : { type: "dictionary", values: { type: "unknown" } };
1848
+ function pythonLowering(options) {
1849
+ const { context, originOf: originOf2, rows } = options;
1850
+ const readingMethods = readingMethodsOf(rows);
1851
+ const mutatedByRoot = /* @__PURE__ */ new Map();
1852
+ return {
1853
+ idOf: (node) => node.id,
1854
+ expression: (node) => expressionOf(node, originOf2),
1855
+ statement: statementOf,
1856
+ siteOf,
1857
+ functionOf,
1858
+ writtenTo: (node) => context === null ? null : context.writtenTo(node),
1859
+ callable: (node) => {
1860
+ if (context === null || hasDictionarySplat(node)) {
1861
+ return null;
1862
+ }
1863
+ return context.callable(node);
1864
+ },
1865
+ mutatedInNestedFunction: (root, name) => {
1866
+ let mutated = mutatedByRoot.get(root.id);
1867
+ if (mutated === void 0) {
1868
+ mutated = namesMutatedInNestedFunctions(root, readingMethods);
1869
+ mutatedByRoot.set(root.id, mutated);
1870
+ }
1871
+ return mutated.has(name);
1872
+ },
1873
+ freeNamesOf,
1874
+ holeNameOf: placeholderName,
1875
+ rows
1876
+ };
1803
1877
  }
1804
- function shapeOfReturned(node, depth = 0) {
1805
- if (depth > MAX_DEPTH) {
1806
- return { type: "unknown" };
1807
- }
1808
- const scalar = SCALARS[node.type];
1809
- if (scalar !== void 0) {
1810
- return scalar;
1878
+ function placeholderName(node) {
1879
+ const peeled = peelValue(node);
1880
+ if (peeled.type === "identifier") {
1881
+ return peeled.text;
1811
1882
  }
1812
- if (node.type === "string") {
1813
- return { type: "literal", value: node.text.slice(1, -1) };
1883
+ if (peeled.type === "attribute") {
1884
+ return field(peeled, "attribute")?.text ?? "param";
1814
1885
  }
1815
- if (node.type === "integer") {
1816
- const value = Number.parseInt(node.text, 10);
1817
- return Number.isNaN(value) ? { type: "integer" } : { type: "literal", value };
1886
+ return "param";
1887
+ }
1888
+ function peelValue(node) {
1889
+ const wraps = node.type === "parenthesized_expression" || node.type === "await";
1890
+ const inner = wraps ? children(node)[0] : void 0;
1891
+ return inner === void 0 ? node : peelValue(inner);
1892
+ }
1893
+ var OPAQUE = { kind: "opaque" };
1894
+ function fromFields(node, names, fallback2, build) {
1895
+ const found = names.map((name) => field(node, name));
1896
+ if (found.some((part) => part === null)) {
1897
+ return fallback2;
1818
1898
  }
1819
- if (node.type === "float") {
1820
- return { type: "number" };
1899
+ return build(...found);
1900
+ }
1901
+ function expressionOf(node, originOf2) {
1902
+ const peeled = peelValue(node);
1903
+ const literal = LITERAL_TYPES[peeled.type];
1904
+ if (literal !== void 0) {
1905
+ return literal(peeled);
1821
1906
  }
1822
- if (node.type === "dictionary") {
1823
- return recordShape(node, depth);
1907
+ if (peeled.type === "string") {
1908
+ return stringExpression(peeled);
1824
1909
  }
1825
- if (node.type === "list" || node.type === "tuple" || node.type === "set") {
1826
- const first = children2(node)[0];
1910
+ if (peeled.type === "concatenated_string") {
1827
1911
  return {
1828
- type: "array",
1829
- items: first === void 0 ? { type: "unknown" } : shapeOfReturned(first, depth + 1)
1912
+ kind: "template",
1913
+ parts: children(peeled).map((part) => ({ expression: part }))
1830
1914
  };
1831
1915
  }
1832
- if (node.type === "identifier" || node.type === "attribute") {
1833
- return { type: "ref", name: node.text };
1834
- }
1835
- return { type: "unknown" };
1836
- }
1837
- function returnedBodyShape(statement) {
1838
- const returned = statement.namedChildren[0];
1839
- if (returned == null) {
1840
- return null;
1841
- }
1842
- const value = returned.type === "expression_list" ? children2(returned)[0] : returned;
1843
- if (value === void 0) {
1844
- return null;
1916
+ if (peeled.type === "identifier") {
1917
+ return { kind: "name", text: peeled.text };
1845
1918
  }
1846
- const shape = shapeOfReturned(value);
1847
- return shape.type === "unknown" ? null : shape;
1848
- }
1849
-
1850
- // src/rawSql.ts
1851
- import { storageBinding } from "@suss/ir-core";
1852
- import { readSqlAccess, sqlFromParts } from "@suss/sql";
1853
- function rawSqlEffects(calls, options) {
1854
- if (options.patterns.length === 0) {
1855
- return [];
1919
+ if (peeled.type === "attribute") {
1920
+ return fromFields(
1921
+ peeled,
1922
+ ["object", "attribute"],
1923
+ OPAQUE,
1924
+ (object, name) => ({
1925
+ kind: "member",
1926
+ object,
1927
+ name: name.text
1928
+ })
1929
+ );
1856
1930
  }
1857
- const effects = [];
1858
- for (const call of calls) {
1859
- const pattern = patternFor(call, options);
1860
- if (pattern === void 0) {
1861
- continue;
1862
- }
1863
- const statement = statementIn(call);
1864
- if (statement === null) {
1865
- continue;
1866
- }
1867
- for (const access of readSqlAccess(statement, {
1868
- dialect: pattern.storageSystem
1869
- })) {
1870
- effects.push({
1871
- type: "interaction",
1872
- binding: storageBinding({
1873
- recognition: `python-${pattern.module}`,
1874
- storageSystem: pattern.storageSystem,
1875
- scope: pattern.module,
1876
- container: access.table
1877
- }),
1878
- callee: field(call, "function")?.text ?? "",
1879
- interaction: {
1880
- class: "storage-access",
1881
- kind: access.kind,
1882
- fields: access.fields,
1883
- ...access.selector.length > 0 ? { selector: access.selector } : {},
1884
- operation: field(call, "function")?.text ?? ""
1885
- }
1886
- });
1931
+ if (peeled.type === "subscript") {
1932
+ const object = field(peeled, "value");
1933
+ const index = field(peeled, "subscript");
1934
+ if (object === null || index === null || index.type === "slice") {
1935
+ return { kind: "opaque" };
1887
1936
  }
1937
+ return { kind: "element", object, index };
1888
1938
  }
1889
- return effects;
1890
- }
1891
- function patternFor(call, options) {
1892
- const callee = field(call, "function");
1893
- if (callee === null || callee.type !== "identifier") {
1894
- return void 0;
1939
+ if (peeled.type === "list" || peeled.type === "tuple" || peeled.type === "expression_list") {
1940
+ return { kind: "array", items: children(peeled).map(elementOf) };
1895
1941
  }
1896
- const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
1897
- if (from === void 0) {
1898
- return void 0;
1942
+ if (peeled.type === "dictionary") {
1943
+ return { kind: "record", fields: children(peeled).flatMap(fieldOf) };
1899
1944
  }
1900
- const module = String(from[1]);
1901
- return options.patterns.find(
1902
- (pattern) => pattern.module === module && pattern.functions.includes(callee.text)
1903
- );
1904
- }
1905
- function statementIn(call) {
1906
- const args = field(call, "arguments");
1907
- const first = args?.namedChildren.find((child) => child !== null) ?? null;
1908
- if (first === null || first.type !== "string") {
1909
- return null;
1945
+ if (peeled.type === "call") {
1946
+ return callExpression(peeled, originOf2);
1910
1947
  }
1911
- const parts = [""];
1912
- for (const child of first.namedChildren) {
1913
- if (child === null) {
1914
- continue;
1915
- }
1916
- if (child.type === "string_content") {
1917
- parts[parts.length - 1] += child.text;
1918
- continue;
1919
- }
1920
- if (child.type === "interpolation") {
1921
- parts.push("");
1922
- }
1923
- }
1924
- const statement = sqlFromParts(parts);
1925
- return statement.trim() === "" ? null : statement;
1926
- }
1927
-
1928
- // src/storage.ts
1929
- import { storageBinding as storageBinding2 } from "@suss/ir-core";
1930
- function receiverCall(call) {
1931
- const callee = field(call, "function");
1932
- if (callee === null || callee.type !== "attribute") {
1933
- return null;
1948
+ if (peeled.type === "binary_operator" || peeled.type === "boolean_operator") {
1949
+ return fromFields(
1950
+ peeled,
1951
+ ["left", "operator", "right"],
1952
+ OPAQUE,
1953
+ (left, operator, right) => ({
1954
+ kind: "operator",
1955
+ operator: operator.text,
1956
+ operands: [left, right]
1957
+ })
1958
+ );
1934
1959
  }
1935
- const object = field(callee, "object");
1936
- return object !== null && object.type === "call" ? object : null;
1937
- }
1938
- function methodName(call) {
1939
- const callee = field(call, "function");
1940
- if (callee === null || callee.type !== "attribute") {
1941
- return callee?.text ?? "";
1960
+ if (peeled.type === "comparison_operator") {
1961
+ return comparisonExpression(peeled);
1942
1962
  }
1943
- return field(callee, "attribute")?.text ?? "";
1944
- }
1945
- function subjectOf(call) {
1946
- const callee = field(call, "function");
1947
- if (callee === null || callee.type !== "attribute") {
1948
- return callee?.text ?? "";
1963
+ if (peeled.type === "not_operator") {
1964
+ return fromFields(
1965
+ peeled,
1966
+ ["argument"],
1967
+ OPAQUE,
1968
+ (argument) => ({
1969
+ kind: "operator",
1970
+ operator: "not",
1971
+ operands: [argument]
1972
+ })
1973
+ );
1949
1974
  }
1950
- return field(callee, "object")?.text ?? "";
1951
- }
1952
- function rootOf(call) {
1953
- const receiver = receiverCall(call);
1954
- return receiver === null ? call : rootOf(receiver);
1955
- }
1956
- function chainsIn(calls) {
1957
- const endsWith = /* @__PURE__ */ new Map();
1958
- for (const call of calls) {
1959
- const receiver = receiverCall(call);
1960
- if (receiver === null) {
1961
- continue;
1962
- }
1963
- const start = rootOf(receiver);
1964
- const kept = endsWith.get(start.id);
1965
- if (kept === void 0 || call.endIndex > kept.endIndex) {
1966
- endsWith.set(start.id, call);
1975
+ if (peeled.type === "conditional_expression") {
1976
+ const [whenTrue, condition, whenFalse] = children(peeled);
1977
+ if (whenTrue === void 0 || condition === void 0 || whenFalse === void 0) {
1978
+ return { kind: "opaque" };
1967
1979
  }
1980
+ return { kind: "conditional", condition, whenTrue, whenFalse };
1968
1981
  }
1969
- const chains = [];
1970
- for (const call of calls) {
1971
- if (receiverCall(call) !== null) {
1972
- continue;
1973
- }
1974
- const last = endsWith.get(call.id) ?? call;
1975
- chains.push({
1976
- root: call,
1977
- last,
1978
- subject: subjectOf(call),
1979
- operation: methodName(last),
1980
- between: calls.filter(
1981
- (other) => other !== call && other !== last && other.startIndex >= call.startIndex && other.endIndex <= last.endIndex
1982
- )
1983
- });
1982
+ if (peeled.type === "lambda") {
1983
+ return { kind: "function", node: peeled };
1984
1984
  }
1985
- return chains;
1985
+ return { kind: "opaque" };
1986
1986
  }
1987
- function importedQueryFunction(options, chain) {
1988
- const callee = field(chain.root, "function");
1989
- if (callee === null || callee.type !== "identifier") {
1990
- return void 0;
1991
- }
1992
- const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
1993
- if (from === void 0) {
1994
- return void 0;
1987
+ function stringExpression(node) {
1988
+ const plain = stringLiteralValue(node);
1989
+ if (plain !== null) {
1990
+ return { kind: "literal", value: plain };
1995
1991
  }
1996
- return options.patterns.find(
1997
- (pattern) => String(from[1]) === pattern.module && (pattern.queryFunctions ?? []).includes(String(from[2]))
1998
- );
1999
- }
2000
- function fieldsOf(chain) {
2001
- const args = field(chain.root, "arguments");
2002
- const named = [];
2003
- for (const argument of args === null ? [] : children(args)) {
2004
- if (argument.type === "keyword_argument") {
2005
- const name = field(argument, "name");
2006
- if (name !== null) {
2007
- named.push(name.text);
2008
- }
1992
+ const parts = [];
1993
+ for (const child of children(node)) {
1994
+ if (child.type === "string_content") {
1995
+ parts.push({ text: stringContentValue(child) });
2009
1996
  continue;
2010
1997
  }
2011
- if (argument.type === "attribute") {
2012
- const property = field(argument, "attribute");
2013
- if (property !== null) {
2014
- named.push(property.text);
1998
+ if (child.type === "interpolation") {
1999
+ const expression = field(child, "expression") ?? children(child)[0];
2000
+ if (expression !== void 0) {
2001
+ parts.push({ expression });
2015
2002
  }
2016
2003
  }
2017
2004
  }
2018
- return named;
2005
+ return { kind: "template", parts };
2019
2006
  }
2020
- function selectorOf(call) {
2021
- const args = field(call, "arguments");
2022
- const picked = [];
2023
- for (const argument of args === null ? [] : children(args)) {
2024
- if (argument.type === "keyword_argument") {
2025
- const name = field(argument, "name");
2026
- if (name !== null) {
2027
- picked.push(name.text);
2028
- }
2029
- }
2007
+ function comparisonExpression(node) {
2008
+ const operands = children(node);
2009
+ const operators = node.children.filter(
2010
+ (child) => child !== null && !child.isNamed
2011
+ );
2012
+ const operator = operators[0];
2013
+ if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2014
+ return { kind: "opaque" };
2030
2015
  }
2031
- return picked;
2032
- }
2033
- function effectFor(pattern, chain, options) {
2034
- const picked = [
2035
- ...new Set([chain.last, ...chain.between].flatMap(selectorOf))
2036
- ];
2037
2016
  return {
2038
- type: "interaction",
2039
- binding: storageBinding2({
2040
- recognition: "python-storage",
2041
- storageSystem: pattern.storageSystem,
2042
- scope: pattern.module,
2043
- container: chain.subject
2044
- }),
2045
- callee: chain.last.text,
2046
- interaction: {
2047
- class: "storage-access",
2048
- kind: pattern.writes.includes(chain.operation) ? "write" : "read",
2049
- fields: fieldsOf(chain),
2050
- operation: chain.operation,
2051
- ...picked.length > 0 ? { selector: picked } : {}
2052
- }
2017
+ kind: "operator",
2018
+ operator: operator.text,
2019
+ operands: [operands[0], operands[1]]
2053
2020
  };
2054
2021
  }
2055
- function fileOf(key) {
2056
- const at = key.lastIndexOf(":");
2057
- return at === -1 ? key : key.slice(0, at);
2022
+ function callExpression(node, originOf2) {
2023
+ return fromFields(
2024
+ node,
2025
+ ["function", "arguments"],
2026
+ OPAQUE,
2027
+ (callee, argumentList) => {
2028
+ const peeledCallee = peelValue(callee);
2029
+ const args = argumentList.type === "argument_list" ? argumentsOf(argumentList) : [];
2030
+ const isMethod2 = peeledCallee.type === "attribute";
2031
+ return {
2032
+ kind: "call",
2033
+ callee: {
2034
+ receiver: isMethod2 ? field(peeledCallee, "object") : null,
2035
+ name: calleeName(peeledCallee),
2036
+ origin: () => originOf2(peeledCallee)
2037
+ },
2038
+ args,
2039
+ constructs: false
2040
+ };
2041
+ }
2042
+ );
2058
2043
  }
2059
- function returnTypeName(node) {
2060
- if (node === void 0) {
2061
- return null;
2044
+ function calleeName(callee) {
2045
+ if (callee.type === "identifier") {
2046
+ return callee.text;
2062
2047
  }
2063
- const annotation = field(node, "return_type");
2064
- if (annotation === null) {
2065
- return null;
2048
+ if (callee.type === "attribute") {
2049
+ return field(callee, "attribute")?.text ?? null;
2066
2050
  }
2067
- return annotation.type === "subscript" ? field(annotation, "value")?.text ?? null : annotation.text;
2051
+ return null;
2068
2052
  }
2069
- function storageCallees(calls, filePath, couldMatch) {
2070
- return chainsIn(calls).filter((chain) => couldMatch.has(methodName(chain.root))).map((chain) => field(chain.root, "function")).filter((callee) => callee !== null).map((callee) => nodeId(filePath, callee));
2053
+ function argumentsOf(argumentList) {
2054
+ return children(argumentList).flatMap((argument) => {
2055
+ if (argument.type === "dictionary_splat") {
2056
+ return [];
2057
+ }
2058
+ if (argument.type === "keyword_argument") {
2059
+ const name = field(argument, "name");
2060
+ const value = field(argument, "value");
2061
+ return name === null || value === null ? [] : [{ kind: "named", name: name.text, node: value }];
2062
+ }
2063
+ return [elementOf(argument)];
2064
+ });
2071
2065
  }
2072
- function storageEffects(calls, options) {
2073
- if (options.patterns.length === 0) {
2074
- return [];
2066
+ function hasDictionarySplat(call) {
2067
+ const argumentList = field(call, "arguments");
2068
+ if (argumentList === null) {
2069
+ return false;
2075
2070
  }
2076
- const named = new Set(
2077
- options.patterns.flatMap((pattern) => pattern.queryFunctions ?? [])
2078
- );
2079
- const chains = chainsIn(calls).filter(
2080
- (chain) => options.couldMatch.has(methodName(chain.root)) || named.has(methodName(chain.root))
2071
+ return children(argumentList).some(
2072
+ (argument) => argument.type === "dictionary_splat"
2081
2073
  );
2082
- if (chains.length === 0) {
2074
+ }
2075
+ function elementOf(node) {
2076
+ if (node.type === "list_splat") {
2077
+ const inner = children(node)[0];
2078
+ return inner === void 0 ? { kind: "value", node } : { kind: "spread", node: inner };
2079
+ }
2080
+ return { kind: "value", node };
2081
+ }
2082
+ function fieldOf(node) {
2083
+ if (node.type === "dictionary_splat") {
2084
+ const inner = children(node)[0];
2085
+ return inner === void 0 ? [] : [{ kind: "spread", node: inner }];
2086
+ }
2087
+ if (node.type !== "pair") {
2083
2088
  return [];
2084
2089
  }
2085
- const alreadyAsked = new Set(
2086
- options.facts.facts("wanted").map((row) => String(row[0]))
2087
- );
2088
- resolveCalls(
2089
- options.facts,
2090
- storageCallees(calls, options.filePath, options.couldMatch).filter(
2091
- (key) => !alreadyAsked.has(key)
2092
- )
2093
- );
2094
- const effects = [];
2095
- for (const chain of chains) {
2096
- const callee = field(chain.root, "function");
2097
- if (callee === null) {
2098
- continue;
2090
+ return fromFields(node, ["key", "value"], [], (key, value) => {
2091
+ const name = stringLiteralValue(key);
2092
+ return name === null ? [{ kind: "computed", name: key, value }] : [{ kind: "field", name, value }];
2093
+ });
2094
+ }
2095
+ var STATEMENT_TYPES = {
2096
+ expression_statement: expressionStatement,
2097
+ if_statement: ifStatement,
2098
+ elif_clause: ifStatement,
2099
+ for_statement: loopStatement,
2100
+ while_statement: loopStatement,
2101
+ return_statement: (node) => ({
2102
+ kind: "return",
2103
+ value: children(node)[0] ?? null
2104
+ }),
2105
+ try_statement: (node) => ({
2106
+ kind: "block",
2107
+ body: [
2108
+ ...blockStatements2(field(node, "body")),
2109
+ ...children(node).flatMap(
2110
+ (clause) => clause.type === "finally_clause" ? blockStatements2(children(clause)[0] ?? null) : []
2111
+ )
2112
+ ]
2113
+ }),
2114
+ with_statement: (node) => ({
2115
+ kind: "block",
2116
+ body: blockStatements2(field(node, "body"))
2117
+ })
2118
+ };
2119
+ function statementOf(node) {
2120
+ const lower = STATEMENT_TYPES[node.type];
2121
+ return lower === void 0 ? { kind: "opaque" } : lower(node);
2122
+ }
2123
+ function expressionStatement(node) {
2124
+ const inner = children(node)[0];
2125
+ if (inner === void 0) {
2126
+ return { kind: "opaque" };
2127
+ }
2128
+ if (inner.type === "assignment") {
2129
+ return assignment(inner, null);
2130
+ }
2131
+ if (inner.type === "augmented_assignment") {
2132
+ const operator = field(inner, "operator");
2133
+ return assignment(
2134
+ inner,
2135
+ operator === null ? null : operator.text.slice(0, -1)
2136
+ );
2137
+ }
2138
+ return { kind: "expression", value: inner };
2139
+ }
2140
+ function assignment(node, operator) {
2141
+ return fromFields(
2142
+ node,
2143
+ ["left", "right"],
2144
+ OPAQUE,
2145
+ (target, value) => {
2146
+ if (target.type === "pattern_list" || target.type === "tuple_pattern") {
2147
+ return {
2148
+ kind: "declare",
2149
+ bindings: patternNames(target).map((name) => ({ name, value: null }))
2150
+ };
2151
+ }
2152
+ return { kind: "assign", target, operator, value };
2099
2153
  }
2100
- const imported = importedQueryFunction(options, chain);
2101
- if (imported !== void 0) {
2102
- effects.push(effectFor(imported, chain, options));
2103
- continue;
2154
+ );
2155
+ }
2156
+ function patternNames(pattern) {
2157
+ return children(pattern).flatMap(
2158
+ (child) => child.type === "identifier" ? [child.text] : patternNames(child)
2159
+ );
2160
+ }
2161
+ function ifStatement(node) {
2162
+ return fromFields(
2163
+ node,
2164
+ ["condition", "consequence"],
2165
+ OPAQUE,
2166
+ (condition, consequence) => ({
2167
+ kind: "branch",
2168
+ condition,
2169
+ arms: [blockStatements2(consequence), otherArm(node)]
2170
+ })
2171
+ );
2172
+ }
2173
+ function otherArm(clause) {
2174
+ const statement = clause.type === "if_statement" ? clause : clause.parent;
2175
+ const clauses = statement === null ? [] : children(statement);
2176
+ const next = clauses.slice(clauses.findIndex((child) => child.id === clause.id) + 1).find(
2177
+ (child) => child.type === "elif_clause" || child.type === "else_clause"
2178
+ );
2179
+ if (next === void 0) {
2180
+ return [];
2181
+ }
2182
+ return next.type === "elif_clause" ? [next] : blockStatements2(field(next, "body"));
2183
+ }
2184
+ function loopStatement(node) {
2185
+ return { kind: "loop", body: blockStatements2(field(node, "body")) };
2186
+ }
2187
+ function blockStatements2(block) {
2188
+ return block === null ? [] : children(block);
2189
+ }
2190
+ function isRoot(node) {
2191
+ return ROOT_TYPES.has(node.type);
2192
+ }
2193
+ function isPathStatement(node, parent) {
2194
+ return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2195
+ }
2196
+ function siteOf(node) {
2197
+ const path6 = [];
2198
+ let current = node;
2199
+ for (; ; ) {
2200
+ const parent = current.parent;
2201
+ if (parent === null) {
2202
+ return null;
2104
2203
  }
2105
- const resolved = options.facts.facts("wantedResolves").filter((row) => String(row[0]) === nodeId(options.filePath, callee)).map((row) => String(row[1]));
2106
- const settled = resolved.length === 1 ? resolved[0] : void 0;
2107
- if (settled === void 0) {
2108
- continue;
2204
+ if (isPathStatement(current, parent)) {
2205
+ path6.unshift(current);
2109
2206
  }
2110
- const typeName = returnTypeName(options.definitionAt(settled));
2111
- if (typeName === null) {
2112
- continue;
2207
+ if (isRoot(parent)) {
2208
+ return { root: parent, path: path6 };
2113
2209
  }
2114
- const pattern = options.patterns.find(
2115
- (candidate) => candidate.queryTypes.includes(typeName) && options.facts.facts("pyImport").some(
2116
- (row) => String(row[0]) === fileOf(settled) && String(row[1]) === candidate.module
2117
- )
2210
+ current = parent;
2211
+ }
2212
+ }
2213
+ function functionOf(node) {
2214
+ if (node.type === "module") {
2215
+ return { parameters: [], body: children(node) };
2216
+ }
2217
+ if (node.type === "lambda") {
2218
+ const body = field(node, "body");
2219
+ if (body === null) {
2220
+ return null;
2221
+ }
2222
+ return { parameters: parameterShapes(node), body: { expression: body } };
2223
+ }
2224
+ if (node.type !== "function_definition") {
2225
+ return null;
2226
+ }
2227
+ const parameters = parameterShapes(node);
2228
+ return {
2229
+ parameters: isMethod(node) ? parameters.slice(1) : parameters,
2230
+ body: blockStatements2(field(node, "body"))
2231
+ };
2232
+ }
2233
+ function isMethod(fn) {
2234
+ const owner = fn.parent?.type === "decorated_definition" ? fn.parent.parent : fn.parent;
2235
+ return owner?.type === "block" && owner.parent?.type === "class_definition";
2236
+ }
2237
+ function namesMutatedInNestedFunctions(root, readingMethods) {
2238
+ const mutated = /* @__PURE__ */ new Set();
2239
+ const visit = (node, nested) => {
2240
+ const name = writtenNameOf(node, readingMethods);
2241
+ if (nested && name !== null) {
2242
+ mutated.add(name);
2243
+ }
2244
+ for (const child of children(node)) {
2245
+ visit(child, nested || isRoot(child));
2246
+ }
2247
+ };
2248
+ for (const child of children(root)) {
2249
+ visit(child, isRoot(child));
2250
+ }
2251
+ return mutated;
2252
+ }
2253
+ function writtenNameOf(node, readingMethods) {
2254
+ if (node.type === "assignment" || node.type === "augmented_assignment") {
2255
+ const target = field(node, "left");
2256
+ return target?.type === "identifier" ? target.text : null;
2257
+ }
2258
+ if (node.type === "call") {
2259
+ const callee = field(node, "function");
2260
+ if (callee?.type !== "attribute") {
2261
+ return null;
2262
+ }
2263
+ const receiver = field(callee, "object");
2264
+ const method = field(callee, "attribute");
2265
+ if (receiver?.type === "identifier" && method !== null && !readingMethods.has(method.text)) {
2266
+ return receiver.text;
2267
+ }
2268
+ }
2269
+ return null;
2270
+ }
2271
+ var DECLARING_PARENTS = /* @__PURE__ */ new Set([
2272
+ "parameters",
2273
+ "default_parameter",
2274
+ "typed_parameter",
2275
+ "typed_default_parameter",
2276
+ "lambda_parameters",
2277
+ "pattern_list",
2278
+ "tuple_pattern",
2279
+ "for_statement"
2280
+ ]);
2281
+ function freeNamesOf(fn) {
2282
+ const declared = /* @__PURE__ */ new Set();
2283
+ const read = /* @__PURE__ */ new Set();
2284
+ const visit = (node) => {
2285
+ for (const child of children(node)) {
2286
+ if (child.type !== "identifier") {
2287
+ visit(child);
2288
+ continue;
2289
+ }
2290
+ if (node.type === "attribute" && field(node, "attribute")?.id === child.id) {
2291
+ continue;
2292
+ }
2293
+ if (node.type === "keyword_argument" && field(node, "name")?.id === child.id) {
2294
+ continue;
2295
+ }
2296
+ if (DECLARING_PARENTS.has(node.type) || node.type === "assignment" && field(node, "left")?.id === child.id) {
2297
+ declared.add(child.text);
2298
+ continue;
2299
+ }
2300
+ read.add(child.text);
2301
+ }
2302
+ };
2303
+ visit(fn);
2304
+ return [...read].filter((name) => !declared.has(name));
2305
+ }
2306
+
2307
+ // src/values/origin.ts
2308
+ function originOf(callee, module) {
2309
+ const chain = memberChainOf(callee);
2310
+ if (chain === null) {
2311
+ return null;
2312
+ }
2313
+ const [rootName, ...rest] = chain;
2314
+ if (rootName === void 0) {
2315
+ return null;
2316
+ }
2317
+ const binding = resolveName(scopeAt(callee, module), rootName);
2318
+ if (binding === null) {
2319
+ return rest.length === 0 ? { module: "builtins", name: rootName } : null;
2320
+ }
2321
+ const path6 = importedPathOf(binding, rootName);
2322
+ if (path6 === null) {
2323
+ return null;
2324
+ }
2325
+ const dotted = [...path6, ...rest];
2326
+ const name = dotted.pop();
2327
+ if (name === void 0 || dotted.length === 0) {
2328
+ return null;
2329
+ }
2330
+ return { module: dotted.join("."), name };
2331
+ }
2332
+ function importedPathOf(binding, localName) {
2333
+ if (binding.kind === "importFrom") {
2334
+ if (binding.relativeLevel > 0) {
2335
+ return null;
2336
+ }
2337
+ return [...binding.module.split("."), binding.importedName];
2338
+ }
2339
+ if (binding.kind !== "import") {
2340
+ return null;
2341
+ }
2342
+ const unaliasedDotted = binding.module !== localName && binding.module.startsWith(`${localName}.`);
2343
+ return unaliasedDotted ? [localName] : binding.module.split(".");
2344
+ }
2345
+ function memberChainOf(node) {
2346
+ const names = [];
2347
+ let current = peelValue(node);
2348
+ while (current.type === "attribute") {
2349
+ const object = field(current, "object");
2350
+ const attribute = field(current, "attribute");
2351
+ if (object === null || attribute === null) {
2352
+ return null;
2353
+ }
2354
+ names.unshift(attribute.text);
2355
+ current = peelValue(object);
2356
+ }
2357
+ if (current.type !== "identifier") {
2358
+ return null;
2359
+ }
2360
+ return [current.text, ...names];
2361
+ }
2362
+ function scopeAt(node, module) {
2363
+ let current = node;
2364
+ while (current !== null) {
2365
+ const scope = module.scopeFor.get(current.id);
2366
+ if (scope !== void 0) {
2367
+ return scope;
2368
+ }
2369
+ current = current.parent;
2370
+ }
2371
+ return module.moduleScope;
2372
+ }
2373
+
2374
+ // src/values/rows.ts
2375
+ import {
2376
+ appended,
2377
+ concat,
2378
+ constant,
2379
+ environmentRead,
2380
+ equals,
2381
+ extended,
2382
+ fallback,
2383
+ formatArguments,
2384
+ hole,
2385
+ joined,
2386
+ joinedPath,
2387
+ literalOf,
2388
+ negated,
2389
+ operand,
2390
+ percentFormatted,
2391
+ plus,
2392
+ readableFallback,
2393
+ stripped,
2394
+ text,
2395
+ truthOf
2396
+ } from "@suss/values";
2397
+ var operatorRows = [
2398
+ {
2399
+ kind: "operator",
2400
+ operator: "+",
2401
+ arity: 2,
2402
+ apply: ([a, b]) => plus(operand(a), operand(b))
2403
+ },
2404
+ {
2405
+ kind: "operator",
2406
+ operator: "%",
2407
+ arity: 2,
2408
+ apply: ([a, b], contentOf) => percentFormatted(operand(a), formatArguments(contentOf(operand(b))))
2409
+ },
2410
+ {
2411
+ kind: "operator",
2412
+ operator: "==",
2413
+ arity: 2,
2414
+ apply: ([a, b]) => equals(operand(a), operand(b))
2415
+ },
2416
+ {
2417
+ kind: "operator",
2418
+ operator: "!=",
2419
+ arity: 2,
2420
+ apply: ([a, b]) => negated(equals(operand(a), operand(b)))
2421
+ },
2422
+ {
2423
+ kind: "operator",
2424
+ operator: "is",
2425
+ arity: 2,
2426
+ apply: ([a, b]) => equals(operand(a), operand(b))
2427
+ },
2428
+ {
2429
+ kind: "operator",
2430
+ operator: "is not",
2431
+ arity: 2,
2432
+ apply: ([a, b]) => negated(equals(operand(a), operand(b)))
2433
+ },
2434
+ {
2435
+ kind: "operator",
2436
+ operator: "not",
2437
+ arity: 1,
2438
+ apply: ([a]) => negated(operand(a))
2439
+ },
2440
+ {
2441
+ kind: "operator",
2442
+ operator: "or",
2443
+ arity: 2,
2444
+ apply: ([a, b]) => readableFallback(operand(a), operand(b), truthOf)
2445
+ },
2446
+ {
2447
+ kind: "operator",
2448
+ operator: "and",
2449
+ arity: 2,
2450
+ apply: ([a, b]) => fallback(operand(a), operand(b), (left) => {
2451
+ const truth = truthOf(left);
2452
+ return truth === null ? null : !truth;
2453
+ })
2454
+ }
2455
+ ];
2456
+ var stringRows = [
2457
+ {
2458
+ kind: "method",
2459
+ method: "join",
2460
+ on: "string",
2461
+ apply: ({ receiver, args, contentOf }) => ({
2462
+ result: joined(contentOf(operand(args[0])), operand(receiver))
2463
+ })
2464
+ },
2465
+ {
2466
+ kind: "method",
2467
+ method: "format",
2468
+ on: "string",
2469
+ apply: ({ receiver, args }) => ({
2470
+ result: braceFormatted(operand(receiver), args)
2471
+ })
2472
+ },
2473
+ {
2474
+ kind: "method",
2475
+ method: "strip",
2476
+ on: "string",
2477
+ apply: ({ receiver, args }) => ({
2478
+ result: args.length === 0 ? stripped(operand(receiver)) : hole("value")
2479
+ })
2480
+ },
2481
+ {
2482
+ kind: "method",
2483
+ method: "rstrip",
2484
+ on: "string",
2485
+ apply: ({ receiver, args }) => ({
2486
+ result: args.length === 0 ? stripped(operand(receiver), "end") : hole("value")
2487
+ })
2488
+ },
2489
+ {
2490
+ kind: "method",
2491
+ method: "lstrip",
2492
+ on: "string",
2493
+ apply: ({ receiver, args }) => ({
2494
+ result: args.length === 0 ? stripped(operand(receiver), "start") : hole("value")
2495
+ })
2496
+ }
2497
+ ];
2498
+ var sequenceRows = ["sequence", "unbounded"].flatMap((on) => [
2499
+ {
2500
+ kind: "method",
2501
+ method: "append",
2502
+ on,
2503
+ apply: ({ receiver, args }) => ({
2504
+ result: constant(null),
2505
+ receiver: appended(operand(receiver), args)
2506
+ })
2507
+ },
2508
+ {
2509
+ kind: "method",
2510
+ method: "extend",
2511
+ on,
2512
+ apply: ({ receiver, args, contentOf }) => ({
2513
+ result: constant(null),
2514
+ receiver: extended(operand(receiver), contentOf(operand(args[0])))
2515
+ })
2516
+ }
2517
+ ]);
2518
+ var calleeRows = [
2519
+ {
2520
+ kind: "callee",
2521
+ origin: { module: "builtins", name: "str" },
2522
+ apply: ({ args }) => ({ result: concat([operand(args[0])]) })
2523
+ },
2524
+ {
2525
+ kind: "callee",
2526
+ origin: { module: "os.path", name: "join" },
2527
+ apply: ({ args }) => ({ result: joinedPath(args) })
2528
+ },
2529
+ {
2530
+ kind: "callee",
2531
+ origin: { module: "os", name: "getenv" },
2532
+ apply: ({ args }) => ({ result: environmentRead(args) })
2533
+ },
2534
+ {
2535
+ kind: "callee",
2536
+ origin: { module: "os.environ", name: "get" },
2537
+ apply: ({ args }) => ({ result: environmentRead(args) })
2538
+ }
2539
+ ];
2540
+ var BRACE_PLACEHOLDER = /\{([^{}]*)\}/g;
2541
+ function braceFormatted(receiver, args) {
2542
+ const template = literalOf(receiver);
2543
+ if (template === null) {
2544
+ return hole("value");
2545
+ }
2546
+ const parts = [];
2547
+ let last = 0;
2548
+ let position = 0;
2549
+ for (const match of template.matchAll(BRACE_PLACEHOLDER)) {
2550
+ parts.push(text(template.slice(last, match.index)));
2551
+ const argument = args[position];
2552
+ const placeholder = match[1] ?? "";
2553
+ parts.push(
2554
+ argument === void 0 ? hole(
2555
+ placeholder === "" ? "value" : placeholder.split(/[!:]/)[0] ?? "value"
2556
+ ) : concat([argument])
2118
2557
  );
2119
- if (pattern === void 0) {
2120
- continue;
2121
- }
2122
- effects.push(effectFor(pattern, chain, options));
2558
+ position += 1;
2559
+ last = match.index + match[0].length;
2123
2560
  }
2124
- return effects;
2561
+ parts.push(text(template.slice(last)));
2562
+ return concat(parts);
2125
2563
  }
2564
+ var pythonRows = [
2565
+ ...operatorRows,
2566
+ ...stringRows,
2567
+ ...sequenceRows,
2568
+ ...calleeRows
2569
+ ];
2126
2570
 
2127
2571
  // src/values/evaluator.ts
2128
- import { nodeOfKey } from "@suss/resolution";
2129
- import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
2130
-
2131
- // src/values/lowering.ts
2132
- var ROOT_TYPES = /* @__PURE__ */ new Set(["module", "function_definition", "lambda"]);
2133
- var LITERAL_TYPES = {
2134
- integer: (node) => ({ kind: "literal", value: Number(node.text) }),
2135
- float: (node) => ({ kind: "literal", value: Number(node.text) }),
2136
- true: () => ({ kind: "literal", value: true }),
2137
- false: () => ({ kind: "literal", value: false }),
2138
- none: () => ({ kind: "literal", value: null })
2139
- };
2140
- var WRITING_ROWS = /* @__PURE__ */ new Set(["append", "extend"]);
2141
- function readingMethodsOf(rows) {
2142
- return new Set(
2143
- rows.flatMap(
2144
- (row) => row.kind === "method" && !WRITING_ROWS.has(row.method) ? [row.method] : []
2572
+ var evaluators = /* @__PURE__ */ new WeakMap();
2573
+ var withoutFacts = /* @__PURE__ */ new WeakMap();
2574
+ var modulesByTree = /* @__PURE__ */ new WeakMap();
2575
+ function bindEvaluator(db, nodes) {
2576
+ for (const entry of nodes.files) {
2577
+ modulesByTree.set(entry.root.tree, entry.module);
2578
+ }
2579
+ evaluators.set(
2580
+ db,
2581
+ new Evaluator(
2582
+ pythonLowering({
2583
+ context: contextOver(db, nodes),
2584
+ originOf: calleeOrigin,
2585
+ rows: pythonRows
2586
+ })
2145
2587
  )
2146
2588
  );
2147
2589
  }
2148
- function pythonLowering(options) {
2149
- const { context, originOf: originOf2, rows } = options;
2150
- const readingMethods = readingMethodsOf(rows);
2151
- const mutatedByRoot = /* @__PURE__ */ new Map();
2152
- return {
2153
- idOf: (node) => node.id,
2154
- expression: (node) => expressionOf(node, originOf2),
2155
- statement: statementOf,
2156
- siteOf,
2157
- functionOf,
2158
- writtenTo: (node) => context === null ? null : context.writtenTo(node),
2159
- callable: (node) => {
2160
- if (context === null || hasDictionarySplat(node)) {
2161
- return null;
2162
- }
2163
- return context.callable(node);
2164
- },
2165
- mutatedInNestedFunction: (root, name) => {
2166
- let mutated = mutatedByRoot.get(root.id);
2167
- if (mutated === void 0) {
2168
- mutated = namesMutatedInNestedFunctions(root, readingMethods);
2169
- mutatedByRoot.set(root.id, mutated);
2170
- }
2171
- return mutated.has(name);
2172
- },
2173
- freeNamesOf,
2174
- holeNameOf: placeholderName,
2175
- rows
2176
- };
2177
- }
2178
- function placeholderName(node) {
2179
- const peeled = peelValue(node);
2180
- if (peeled.type === "identifier") {
2181
- return peeled.text;
2182
- }
2183
- if (peeled.type === "attribute") {
2184
- return field(peeled, "attribute")?.text ?? "param";
2185
- }
2186
- return "param";
2187
- }
2188
- function peelValue(node) {
2189
- const wraps = node.type === "parenthesized_expression" || node.type === "await";
2190
- const inner = wraps ? children(node)[0] : void 0;
2191
- return inner === void 0 ? node : peelValue(inner);
2590
+ function evaluatedValue(node, db) {
2591
+ return force(evaluatorFor(node, db).evaluate(node));
2192
2592
  }
2193
- var OPAQUE = { kind: "opaque" };
2194
- function fromFields(node, names, fallback2, build) {
2195
- const found = names.map((name) => field(node, name));
2196
- if (found.some((part) => part === null)) {
2197
- return fallback2;
2198
- }
2199
- return build(...found);
2593
+ function stringValueOf(node, db) {
2594
+ return literalOf2(evaluatedValue(node, db));
2200
2595
  }
2201
- function expressionOf(node, originOf2) {
2202
- const peeled = peelValue(node);
2203
- const literal = LITERAL_TYPES[peeled.type];
2204
- if (literal !== void 0) {
2205
- return literal(peeled);
2206
- }
2207
- if (peeled.type === "string") {
2208
- return stringExpression(peeled);
2209
- }
2210
- if (peeled.type === "concatenated_string") {
2211
- return {
2212
- kind: "template",
2213
- parts: children(peeled).map((part) => ({ expression: part }))
2214
- };
2215
- }
2216
- if (peeled.type === "identifier") {
2217
- return { kind: "name", text: peeled.text };
2218
- }
2219
- if (peeled.type === "attribute") {
2220
- return fromFields(
2221
- peeled,
2222
- ["object", "attribute"],
2223
- OPAQUE,
2224
- (object, name) => ({
2225
- kind: "member",
2226
- object,
2227
- name: name.text
2228
- })
2229
- );
2230
- }
2231
- if (peeled.type === "subscript") {
2232
- const object = field(peeled, "value");
2233
- const index = field(peeled, "subscript");
2234
- if (object === null || index === null || index.type === "slice") {
2235
- return { kind: "opaque" };
2236
- }
2237
- return { kind: "element", object, index };
2238
- }
2239
- if (peeled.type === "list" || peeled.type === "tuple" || peeled.type === "expression_list") {
2240
- return { kind: "array", items: children(peeled).map(elementOf) };
2241
- }
2242
- if (peeled.type === "dictionary") {
2243
- return { kind: "record", fields: children(peeled).flatMap(fieldOf) };
2244
- }
2245
- if (peeled.type === "call") {
2246
- return callExpression(peeled, originOf2);
2596
+ function evaluatorFor(node, db) {
2597
+ const bound = db === void 0 ? void 0 : evaluators.get(db);
2598
+ if (bound !== void 0) {
2599
+ return bound;
2247
2600
  }
2248
- if (peeled.type === "binary_operator" || peeled.type === "boolean_operator") {
2249
- return fromFields(
2250
- peeled,
2251
- ["left", "operator", "right"],
2252
- OPAQUE,
2253
- (left, operator, right) => ({
2254
- kind: "operator",
2255
- operator: operator.text,
2256
- operands: [left, right]
2601
+ const tree = node.tree;
2602
+ let local = withoutFacts.get(tree);
2603
+ if (local === void 0) {
2604
+ local = new Evaluator(
2605
+ pythonLowering({
2606
+ context: null,
2607
+ originOf: calleeOrigin,
2608
+ rows: pythonRows
2257
2609
  })
2258
2610
  );
2611
+ withoutFacts.set(tree, local);
2259
2612
  }
2260
- if (peeled.type === "comparison_operator") {
2261
- return comparisonExpression(peeled);
2613
+ return local;
2614
+ }
2615
+ function moduleOf(node) {
2616
+ const tree = node.tree;
2617
+ let module = modulesByTree.get(tree);
2618
+ if (module === void 0) {
2619
+ module = bindModule(tree.rootNode);
2620
+ modulesByTree.set(tree, module);
2262
2621
  }
2263
- if (peeled.type === "not_operator") {
2264
- return fromFields(
2265
- peeled,
2266
- ["argument"],
2267
- OPAQUE,
2268
- (argument) => ({
2269
- kind: "operator",
2270
- operator: "not",
2271
- operands: [argument]
2272
- })
2273
- );
2622
+ return module;
2623
+ }
2624
+ function calleeOrigin(callee) {
2625
+ return originOf(callee, moduleOf(callee));
2626
+ }
2627
+ function contextOver(db, nodes) {
2628
+ const filesByRoot = /* @__PURE__ */ new Map();
2629
+ const rootsByFile = /* @__PURE__ */ new Map();
2630
+ for (const entry of nodes.files) {
2631
+ filesByRoot.set(entry.root.id, entry);
2632
+ rootsByFile.set(entry.file, entry.root);
2274
2633
  }
2275
- if (peeled.type === "conditional_expression") {
2276
- const [whenTrue, condition, whenFalse] = children(peeled);
2277
- if (whenTrue === void 0 || condition === void 0 || whenFalse === void 0) {
2278
- return { kind: "opaque" };
2634
+ const keyOf2 = (node) => {
2635
+ const entry = filesByRoot.get(node.tree.rootNode.id);
2636
+ if (entry === void 0) {
2637
+ return null;
2279
2638
  }
2280
- return { kind: "conditional", condition, whenTrue, whenFalse };
2281
- }
2282
- if (peeled.type === "lambda") {
2283
- return { kind: "function", node: peeled };
2284
- }
2285
- return { kind: "opaque" };
2639
+ return readKey(entry.file, node, enclosingFunction(node));
2640
+ };
2641
+ return {
2642
+ writtenTo: (node) => {
2643
+ const key = keyOf2(node);
2644
+ if (key === null) {
2645
+ return null;
2646
+ }
2647
+ const answer = writtenValueOf(db, key);
2648
+ return answer === null ? null : nodeOfKey(rootsByFile, answer);
2649
+ },
2650
+ callable: (call) => {
2651
+ const callee = field(call, "function");
2652
+ const key = callee === null ? null : keyOf2(callee);
2653
+ if (key === null) {
2654
+ return null;
2655
+ }
2656
+ resolveCalls(db, [key]);
2657
+ const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2658
+ const settled = resolved.length === 1 ? resolved[0] : void 0;
2659
+ return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
2660
+ }
2661
+ };
2286
2662
  }
2287
- function stringExpression(node) {
2288
- const plain = stringLiteralValue(node);
2289
- if (plain !== null) {
2290
- return { kind: "literal", value: plain };
2663
+
2664
+ // src/paths/raisedResponses.ts
2665
+ var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
2666
+ function raisedResponses(body, options) {
2667
+ if (body === null || options.calls.length === 0) {
2668
+ return [];
2291
2669
  }
2292
- const parts = [];
2293
- for (const child of children(node)) {
2294
- if (child.type === "string_content") {
2295
- parts.push({ text: child.text });
2296
- continue;
2297
- }
2298
- if (child.type === "interpolation") {
2299
- const expression = field(child, "expression") ?? children(child)[0];
2300
- if (expression !== void 0) {
2301
- parts.push({ expression });
2670
+ const found = [];
2671
+ const walk = (node) => {
2672
+ for (const child of node.namedChildren) {
2673
+ if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
2674
+ continue;
2675
+ }
2676
+ const response = raisedResponseOf(child, options);
2677
+ if (response !== null) {
2678
+ found.push(response);
2302
2679
  }
2680
+ walk(child);
2303
2681
  }
2682
+ };
2683
+ walk(body);
2684
+ return found;
2685
+ }
2686
+ var ENDING_EXPRESSION = {
2687
+ raise_statement: (statement) => statement.namedChildren[0] ?? null,
2688
+ expression_statement: (statement) => {
2689
+ const only = statement.namedChildren[0];
2690
+ return only?.type === "call" ? only : null;
2304
2691
  }
2305
- return { kind: "template", parts };
2692
+ };
2693
+ function raisedResponseOf(statement, options) {
2694
+ const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
2695
+ if (expression === null) {
2696
+ return null;
2697
+ }
2698
+ const thrownByCall = statement.type !== "raise_statement";
2699
+ const call = expression.type === "call" ? expression : null;
2700
+ const callee = calleeOf(expression, call);
2701
+ const declared = declaredCallFor(callee, options);
2702
+ if (declared === void 0 && thrownByCall) {
2703
+ return null;
2704
+ }
2705
+ return {
2706
+ statement,
2707
+ thrownByCall,
2708
+ terminal: terminalOf(statement, callee, call, declared, options.facts)
2709
+ };
2306
2710
  }
2307
- function comparisonExpression(node) {
2308
- const operands = children(node);
2309
- const operators = node.children.filter(
2310
- (child) => child !== null && !child.isNamed
2311
- );
2312
- const operator = operators[0];
2313
- if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2314
- return { kind: "opaque" };
2711
+ function calleeOf(expression, call) {
2712
+ return call === null ? expression : field(call, "function") ?? expression;
2713
+ }
2714
+ function declaredCallFor(callee, options) {
2715
+ const origin = originOf(callee, options.module);
2716
+ if (origin === null) {
2717
+ return void 0;
2315
2718
  }
2719
+ const qualified = `${origin.module}.${origin.name}`;
2720
+ return options.calls.find((call) => call.callee === qualified);
2721
+ }
2722
+ function terminalOf(statement, callee, call, declared, facts) {
2316
2723
  return {
2317
- kind: "operator",
2318
- operator: operator.text,
2319
- operands: [operands[0], operands[1]]
2724
+ kind: "throw",
2725
+ statusCode: declared === void 0 ? null : statusOf(declared, call, facts),
2726
+ body: null,
2727
+ exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
2728
+ message: call === null ? null : writtenMessageOf(call),
2729
+ ...declared === void 0 ? {} : { producesResponse: true },
2730
+ component: null,
2731
+ renderTree: null,
2732
+ delegateTarget: null,
2733
+ emitEvent: null,
2734
+ location: rangeOf(statement)
2320
2735
  };
2321
2736
  }
2322
- function callExpression(node, originOf2) {
2323
- return fromFields(
2324
- node,
2325
- ["function", "arguments"],
2326
- OPAQUE,
2327
- (callee, argumentList) => {
2328
- const peeledCallee = peelValue(callee);
2329
- const args = argumentList.type === "argument_list" ? argumentsOf(argumentList) : [];
2330
- const isMethod2 = peeledCallee.type === "attribute";
2331
- return {
2332
- kind: "call",
2333
- callee: {
2334
- receiver: isMethod2 ? field(peeledCallee, "object") : null,
2335
- name: calleeName(peeledCallee),
2336
- origin: () => originOf2(peeledCallee)
2337
- },
2338
- args,
2339
- constructs: false
2340
- };
2341
- }
2737
+ function lastSegmentOf(dotted) {
2738
+ const segment = dotted.split(".").pop();
2739
+ return segment === void 0 || segment === "" ? null : segment;
2740
+ }
2741
+ function statusOf(declared, call, facts) {
2742
+ const written = call === null ? null : statusArgumentOf(declared, call);
2743
+ if (written === null) {
2744
+ return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
2745
+ }
2746
+ const value = constantOf(evaluatedValue(written, facts));
2747
+ if (typeof value === "number") {
2748
+ return { type: "literal", value };
2749
+ }
2750
+ return { type: "dynamic", sourceText: written.text };
2751
+ }
2752
+ function argumentsOf2(call) {
2753
+ return (field(call, "arguments")?.namedChildren ?? []).filter(
2754
+ (child) => child !== null
2342
2755
  );
2343
2756
  }
2344
- function calleeName(callee) {
2345
- if (callee.type === "identifier") {
2346
- return callee.text;
2757
+ function statusArgumentOf(declared, call) {
2758
+ const args = argumentsOf2(call);
2759
+ const keyed = args.find(
2760
+ (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
2761
+ );
2762
+ if (keyed !== void 0) {
2763
+ return field(keyed, "value");
2347
2764
  }
2348
- if (callee.type === "attribute") {
2349
- return field(callee, "attribute")?.text ?? null;
2765
+ if (declared.statusArgument === void 0) {
2766
+ return null;
2767
+ }
2768
+ const positional = args.filter((arg) => arg.type !== "keyword_argument");
2769
+ return positional[declared.statusArgument] ?? null;
2770
+ }
2771
+ function writtenMessageOf(call) {
2772
+ for (const arg of argumentsOf2(call)) {
2773
+ const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
2774
+ const literal = value === null ? null : stringLiteralValue(value);
2775
+ if (literal !== null) {
2776
+ return literal;
2777
+ }
2350
2778
  }
2351
2779
  return null;
2352
2780
  }
2353
- function argumentsOf(argumentList) {
2354
- return children(argumentList).flatMap((argument) => {
2355
- if (argument.type === "dictionary_splat") {
2356
- return [];
2781
+
2782
+ // src/paths/returnedShape.ts
2783
+ var MAX_DEPTH = 6;
2784
+ var SCALARS = {
2785
+ none: { type: "null" },
2786
+ true: { type: "boolean" },
2787
+ false: { type: "boolean" }
2788
+ };
2789
+ function children2(node) {
2790
+ return node.namedChildren.filter((child) => child !== null);
2791
+ }
2792
+ function recordShape(node, depth) {
2793
+ const properties = {};
2794
+ let everyKeyWritten = true;
2795
+ for (const pair of children2(node)) {
2796
+ if (pair.type !== "pair") {
2797
+ everyKeyWritten = false;
2798
+ continue;
2357
2799
  }
2358
- if (argument.type === "keyword_argument") {
2359
- const name = field(argument, "name");
2360
- const value = field(argument, "value");
2361
- return name === null || value === null ? [] : [{ kind: "named", name: name.text, node: value }];
2800
+ const key = field(pair, "key");
2801
+ const value = field(pair, "value");
2802
+ if (key === null || value === null || key.type !== "string") {
2803
+ everyKeyWritten = false;
2804
+ continue;
2362
2805
  }
2363
- return [elementOf(argument)];
2364
- });
2365
- }
2366
- function hasDictionarySplat(call) {
2367
- const argumentList = field(call, "arguments");
2368
- if (argumentList === null) {
2369
- return false;
2806
+ properties[key.text.slice(1, -1)] = shapeOfReturned(value, depth + 1);
2370
2807
  }
2371
- return children(argumentList).some(
2372
- (argument) => argument.type === "dictionary_splat"
2373
- );
2808
+ return everyKeyWritten && Object.keys(properties).length > 0 ? { type: "record", properties } : { type: "dictionary", values: { type: "unknown" } };
2374
2809
  }
2375
- function elementOf(node) {
2376
- if (node.type === "list_splat") {
2377
- const inner = children(node)[0];
2378
- return inner === void 0 ? { kind: "value", node } : { kind: "spread", node: inner };
2810
+ function shapeOfReturned(node, depth = 0) {
2811
+ if (depth > MAX_DEPTH) {
2812
+ return { type: "unknown" };
2379
2813
  }
2380
- return { kind: "value", node };
2381
- }
2382
- function fieldOf(node) {
2383
- if (node.type === "dictionary_splat") {
2384
- const inner = children(node)[0];
2385
- return inner === void 0 ? [] : [{ kind: "spread", node: inner }];
2814
+ const scalar = SCALARS[node.type];
2815
+ if (scalar !== void 0) {
2816
+ return scalar;
2386
2817
  }
2387
- if (node.type !== "pair") {
2388
- return [];
2818
+ if (node.type === "string") {
2819
+ return { type: "literal", value: node.text.slice(1, -1) };
2389
2820
  }
2390
- return fromFields(node, ["key", "value"], [], (key, value) => {
2391
- const name = stringLiteralValue(key);
2392
- return name === null ? [{ kind: "computed", name: key, value }] : [{ kind: "field", name, value }];
2393
- });
2821
+ if (node.type === "integer") {
2822
+ const value = Number.parseInt(node.text, 10);
2823
+ return Number.isNaN(value) ? { type: "integer" } : { type: "literal", value };
2824
+ }
2825
+ if (node.type === "float") {
2826
+ return { type: "number" };
2827
+ }
2828
+ if (node.type === "dictionary") {
2829
+ return recordShape(node, depth);
2830
+ }
2831
+ if (node.type === "list" || node.type === "tuple" || node.type === "set") {
2832
+ const first = children2(node)[0];
2833
+ return {
2834
+ type: "array",
2835
+ items: first === void 0 ? { type: "unknown" } : shapeOfReturned(first, depth + 1)
2836
+ };
2837
+ }
2838
+ if (node.type === "identifier" || node.type === "attribute") {
2839
+ return { type: "ref", name: node.text };
2840
+ }
2841
+ return { type: "unknown" };
2394
2842
  }
2395
- var STATEMENT_TYPES = {
2396
- expression_statement: expressionStatement,
2397
- if_statement: ifStatement,
2398
- elif_clause: ifStatement,
2399
- for_statement: loopStatement,
2400
- while_statement: loopStatement,
2401
- return_statement: (node) => ({
2402
- kind: "return",
2403
- value: children(node)[0] ?? null
2404
- }),
2405
- try_statement: (node) => ({
2406
- kind: "block",
2407
- body: [
2408
- ...blockStatements2(field(node, "body")),
2409
- ...children(node).flatMap(
2410
- (clause) => clause.type === "finally_clause" ? blockStatements2(children(clause)[0] ?? null) : []
2411
- )
2412
- ]
2413
- }),
2414
- with_statement: (node) => ({
2415
- kind: "block",
2416
- body: blockStatements2(field(node, "body"))
2417
- })
2418
- };
2419
- function statementOf(node) {
2420
- const lower = STATEMENT_TYPES[node.type];
2421
- return lower === void 0 ? { kind: "opaque" } : lower(node);
2843
+ function returnedBodyShape(statement) {
2844
+ const returned = statement.namedChildren[0];
2845
+ if (returned == null) {
2846
+ return null;
2847
+ }
2848
+ const value = returned.type === "expression_list" ? children2(returned)[0] : returned;
2849
+ if (value === void 0) {
2850
+ return null;
2851
+ }
2852
+ const shape = shapeOfReturned(value);
2853
+ return shape.type === "unknown" ? null : shape;
2422
2854
  }
2423
- function expressionStatement(node) {
2424
- const inner = children(node)[0];
2425
- if (inner === void 0) {
2426
- return { kind: "opaque" };
2855
+
2856
+ // src/rawSql.ts
2857
+ import { storageBinding } from "@suss/ir-core";
2858
+ import { readSqlAccess, sqlFromParts } from "@suss/sql";
2859
+ function rawSqlEffects(calls, options) {
2860
+ if (options.patterns.length === 0) {
2861
+ return [];
2862
+ }
2863
+ const effects = [];
2864
+ for (const call of calls) {
2865
+ const pattern = patternFor(call, options);
2866
+ if (pattern === void 0) {
2867
+ continue;
2868
+ }
2869
+ const statement = statementIn(call);
2870
+ if (statement === null) {
2871
+ continue;
2872
+ }
2873
+ for (const access of readSqlAccess(statement, {
2874
+ dialect: pattern.storageSystem
2875
+ })) {
2876
+ effects.push({
2877
+ type: "interaction",
2878
+ binding: storageBinding({
2879
+ recognition: `python-${pattern.module}`,
2880
+ storageSystem: pattern.storageSystem,
2881
+ scope: pattern.module,
2882
+ container: access.table
2883
+ }),
2884
+ callee: field(call, "function")?.text ?? "",
2885
+ interaction: {
2886
+ class: "storage-access",
2887
+ kind: access.kind,
2888
+ fields: access.fields,
2889
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
2890
+ operation: field(call, "function")?.text ?? ""
2891
+ }
2892
+ });
2893
+ }
2427
2894
  }
2428
- if (inner.type === "assignment") {
2429
- return assignment(inner, null);
2895
+ return effects;
2896
+ }
2897
+ function patternFor(call, options) {
2898
+ const callee = field(call, "function");
2899
+ if (callee === null || callee.type !== "identifier") {
2900
+ return void 0;
2430
2901
  }
2431
- if (inner.type === "augmented_assignment") {
2432
- const operator = field(inner, "operator");
2433
- return assignment(
2434
- inner,
2435
- operator === null ? null : operator.text.slice(0, -1)
2436
- );
2902
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
2903
+ if (from === void 0) {
2904
+ return void 0;
2437
2905
  }
2438
- return { kind: "expression", value: inner };
2439
- }
2440
- function assignment(node, operator) {
2441
- return fromFields(
2442
- node,
2443
- ["left", "right"],
2444
- OPAQUE,
2445
- (target, value) => {
2446
- if (target.type === "pattern_list" || target.type === "tuple_pattern") {
2447
- return {
2448
- kind: "declare",
2449
- bindings: patternNames(target).map((name) => ({ name, value: null }))
2450
- };
2451
- }
2452
- return { kind: "assign", target, operator, value };
2453
- }
2454
- );
2455
- }
2456
- function patternNames(pattern) {
2457
- return children(pattern).flatMap(
2458
- (child) => child.type === "identifier" ? [child.text] : patternNames(child)
2459
- );
2460
- }
2461
- function ifStatement(node) {
2462
- return fromFields(
2463
- node,
2464
- ["condition", "consequence"],
2465
- OPAQUE,
2466
- (condition, consequence) => ({
2467
- kind: "branch",
2468
- condition,
2469
- arms: [blockStatements2(consequence), otherArm(node)]
2470
- })
2906
+ const module = String(from[1]);
2907
+ return options.patterns.find(
2908
+ (pattern) => pattern.module === module && pattern.functions.includes(callee.text)
2471
2909
  );
2472
2910
  }
2473
- function otherArm(clause) {
2474
- const statement = clause.type === "if_statement" ? clause : clause.parent;
2475
- const clauses = statement === null ? [] : children(statement);
2476
- const next = clauses.slice(clauses.findIndex((child) => child.id === clause.id) + 1).find(
2477
- (child) => child.type === "elif_clause" || child.type === "else_clause"
2478
- );
2479
- if (next === void 0) {
2480
- return [];
2911
+ function statementIn(call) {
2912
+ const args = field(call, "arguments");
2913
+ const first = args?.namedChildren.find((child) => child !== null) ?? null;
2914
+ if (first === null || first.type !== "string") {
2915
+ return null;
2481
2916
  }
2482
- return next.type === "elif_clause" ? [next] : blockStatements2(field(next, "body"));
2483
- }
2484
- function loopStatement(node) {
2485
- return { kind: "loop", body: blockStatements2(field(node, "body")) };
2486
- }
2487
- function blockStatements2(block) {
2488
- return block === null ? [] : children(block);
2489
- }
2490
- function isRoot(node) {
2491
- return ROOT_TYPES.has(node.type);
2492
- }
2493
- function isPathStatement(node, parent) {
2494
- return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2495
- }
2496
- function siteOf(node) {
2497
- const path6 = [];
2498
- let current = node;
2499
- for (; ; ) {
2500
- const parent = current.parent;
2501
- if (parent === null) {
2502
- return null;
2917
+ const parts = [""];
2918
+ for (const child of first.namedChildren) {
2919
+ if (child === null) {
2920
+ continue;
2503
2921
  }
2504
- if (isPathStatement(current, parent)) {
2505
- path6.unshift(current);
2922
+ if (child.type === "string_content") {
2923
+ parts[parts.length - 1] += stringContentValue(child);
2924
+ continue;
2506
2925
  }
2507
- if (isRoot(parent)) {
2508
- return { root: parent, path: path6 };
2926
+ if (child.type === "interpolation") {
2927
+ parts.push("");
2509
2928
  }
2510
- current = parent;
2511
2929
  }
2930
+ const statement = sqlFromParts(parts);
2931
+ return statement.trim() === "" ? null : statement;
2512
2932
  }
2513
- function functionOf(node) {
2514
- if (node.type === "module") {
2515
- return { parameters: [], body: children(node) };
2933
+
2934
+ // src/storage.ts
2935
+ import { storageBinding as storageBinding2 } from "@suss/ir-core";
2936
+ function receiverCall(call) {
2937
+ const callee = field(call, "function");
2938
+ if (callee === null || callee.type !== "attribute") {
2939
+ return null;
2516
2940
  }
2517
- if (node.type === "lambda") {
2518
- const body = field(node, "body");
2519
- if (body === null) {
2520
- return null;
2521
- }
2522
- return { parameters: parameterShapes(node), body: { expression: body } };
2941
+ const object = field(callee, "object");
2942
+ return object !== null && object.type === "call" ? object : null;
2943
+ }
2944
+ function methodName(call) {
2945
+ const callee = field(call, "function");
2946
+ if (callee === null || callee.type !== "attribute") {
2947
+ return callee?.text ?? "";
2523
2948
  }
2524
- if (node.type !== "function_definition") {
2525
- return null;
2949
+ return field(callee, "attribute")?.text ?? "";
2950
+ }
2951
+ function subjectOf(call) {
2952
+ const callee = field(call, "function");
2953
+ if (callee === null || callee.type !== "attribute") {
2954
+ return callee?.text ?? "";
2526
2955
  }
2527
- const parameters = parameterShapes(node);
2528
- return {
2529
- parameters: isMethod(node) ? parameters.slice(1) : parameters,
2530
- body: blockStatements2(field(node, "body"))
2531
- };
2956
+ return field(callee, "object")?.text ?? "";
2532
2957
  }
2533
- function isMethod(fn) {
2534
- const owner = fn.parent?.type === "decorated_definition" ? fn.parent.parent : fn.parent;
2535
- return owner?.type === "block" && owner.parent?.type === "class_definition";
2958
+ function rootOf(call) {
2959
+ const receiver = receiverCall(call);
2960
+ return receiver === null ? call : rootOf(receiver);
2536
2961
  }
2537
- function namesMutatedInNestedFunctions(root, readingMethods) {
2538
- const mutated = /* @__PURE__ */ new Set();
2539
- const visit = (node, nested) => {
2540
- const name = writtenNameOf(node, readingMethods);
2541
- if (nested && name !== null) {
2542
- mutated.add(name);
2962
+ function chainsIn(calls) {
2963
+ const endsWith = /* @__PURE__ */ new Map();
2964
+ for (const call of calls) {
2965
+ const receiver = receiverCall(call);
2966
+ if (receiver === null) {
2967
+ continue;
2543
2968
  }
2544
- for (const child of children(node)) {
2545
- visit(child, nested || isRoot(child));
2969
+ const start = rootOf(receiver);
2970
+ const kept = endsWith.get(start.id);
2971
+ if (kept === void 0 || call.endIndex > kept.endIndex) {
2972
+ endsWith.set(start.id, call);
2546
2973
  }
2547
- };
2548
- for (const child of children(root)) {
2549
- visit(child, isRoot(child));
2550
- }
2551
- return mutated;
2552
- }
2553
- function writtenNameOf(node, readingMethods) {
2554
- if (node.type === "assignment" || node.type === "augmented_assignment") {
2555
- const target = field(node, "left");
2556
- return target?.type === "identifier" ? target.text : null;
2557
2974
  }
2558
- if (node.type === "call") {
2559
- const callee = field(node, "function");
2560
- if (callee?.type !== "attribute") {
2561
- return null;
2562
- }
2563
- const receiver = field(callee, "object");
2564
- const method = field(callee, "attribute");
2565
- if (receiver?.type === "identifier" && method !== null && !readingMethods.has(method.text)) {
2566
- return receiver.text;
2975
+ const chains = [];
2976
+ for (const call of calls) {
2977
+ if (receiverCall(call) !== null) {
2978
+ continue;
2567
2979
  }
2980
+ const last = endsWith.get(call.id) ?? call;
2981
+ chains.push({
2982
+ root: call,
2983
+ last,
2984
+ subject: subjectOf(call),
2985
+ operation: methodName(last),
2986
+ between: calls.filter(
2987
+ (other) => other !== call && other !== last && other.startIndex >= call.startIndex && other.endIndex <= last.endIndex
2988
+ )
2989
+ });
2568
2990
  }
2569
- return null;
2991
+ return chains;
2570
2992
  }
2571
- var DECLARING_PARENTS = /* @__PURE__ */ new Set([
2572
- "parameters",
2573
- "default_parameter",
2574
- "typed_parameter",
2575
- "typed_default_parameter",
2576
- "lambda_parameters",
2577
- "pattern_list",
2578
- "tuple_pattern",
2579
- "for_statement"
2580
- ]);
2581
- function freeNamesOf(fn) {
2582
- const declared = /* @__PURE__ */ new Set();
2583
- const read = /* @__PURE__ */ new Set();
2584
- const visit = (node) => {
2585
- for (const child of children(node)) {
2586
- if (child.type !== "identifier") {
2587
- visit(child);
2588
- continue;
2589
- }
2590
- if (node.type === "attribute" && field(node, "attribute")?.id === child.id) {
2591
- continue;
2592
- }
2593
- if (node.type === "keyword_argument" && field(node, "name")?.id === child.id) {
2594
- continue;
2595
- }
2596
- if (DECLARING_PARENTS.has(node.type) || node.type === "assignment" && field(node, "left")?.id === child.id) {
2597
- declared.add(child.text);
2598
- continue;
2599
- }
2600
- read.add(child.text);
2601
- }
2602
- };
2603
- visit(fn);
2604
- return [...read].filter((name) => !declared.has(name));
2993
+ function importedQueryFunction(options, chain) {
2994
+ const callee = field(chain.root, "function");
2995
+ if (callee === null || callee.type !== "identifier") {
2996
+ return void 0;
2997
+ }
2998
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
2999
+ if (from === void 0) {
3000
+ return void 0;
3001
+ }
3002
+ return options.patterns.find(
3003
+ (pattern) => String(from[1]) === pattern.module && (pattern.queryFunctions ?? []).includes(String(from[2]))
3004
+ );
2605
3005
  }
2606
-
2607
- // src/values/origin.ts
2608
- function originOf(callee, module) {
2609
- const chain = memberChainOf(callee);
2610
- if (chain === null) {
3006
+ function receiverName(chain) {
3007
+ const callee = field(chain.root, "function");
3008
+ if (callee === null || callee.type !== "attribute") {
2611
3009
  return null;
2612
3010
  }
2613
- const [rootName, ...rest] = chain;
2614
- if (rootName === void 0) {
2615
- return null;
3011
+ const object = field(callee, "object");
3012
+ return object !== null && object.type === "identifier" ? object.text : null;
3013
+ }
3014
+ function typeNameOf(annotation) {
3015
+ if (annotation.type === "type" && annotation.namedChildren[0]) {
3016
+ return typeNameOf(annotation.namedChildren[0]);
2616
3017
  }
2617
- const binding = resolveName(scopeAt(callee, module), rootName);
2618
- if (binding === null) {
2619
- return rest.length === 0 ? { module: "builtins", name: rootName } : null;
3018
+ if (annotation.type === "identifier") {
3019
+ return annotation.text;
2620
3020
  }
2621
- const path6 = importedPathOf(binding, rootName);
2622
- if (path6 === null) {
2623
- return null;
3021
+ const quoted = stringLiteralValue(annotation);
3022
+ if (quoted !== null) {
3023
+ return quoted;
2624
3024
  }
2625
- const dotted = [...path6, ...rest];
2626
- const name = dotted.pop();
2627
- if (name === void 0 || dotted.length === 0) {
2628
- return null;
3025
+ if (annotation.type === "generic_type") {
3026
+ const outer = annotation.namedChildren[0];
3027
+ const first = genericTypeArgs(annotation)[0];
3028
+ if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
3029
+ return typeNameOf(first);
3030
+ }
2629
3031
  }
2630
- return { module: dotted.join("."), name };
3032
+ return null;
2631
3033
  }
2632
- function importedPathOf(binding, localName) {
2633
- if (binding.kind === "importFrom") {
2634
- if (binding.relativeLevel > 0) {
3034
+ function typeGivenBy(statement, name) {
3035
+ if (statement.type === "assignment") {
3036
+ if (field(statement, "left")?.text !== name) {
2635
3037
  return null;
2636
3038
  }
2637
- return [...binding.module.split("."), binding.importedName];
3039
+ const annotation = field(statement, "type");
3040
+ if (annotation !== null) {
3041
+ return typeNameOf(annotation);
3042
+ }
3043
+ const right = field(statement, "right");
3044
+ const callee = right?.type === "call" ? field(right, "function") : null;
3045
+ return callee?.type === "identifier" ? callee.text : null;
2638
3046
  }
2639
- if (binding.kind !== "import") {
2640
- return null;
3047
+ if (statement.type === "as_pattern") {
3048
+ const alias = field(statement, "alias");
3049
+ const value = statement.namedChildren[0] ?? null;
3050
+ const callee = value?.type === "call" ? field(value, "function") : null;
3051
+ return alias?.text === name && callee?.type === "identifier" ? callee.text : null;
2641
3052
  }
2642
- const unaliasedDotted = binding.module !== localName && binding.module.startsWith(`${localName}.`);
2643
- return unaliasedDotted ? [localName] : binding.module.split(".");
3053
+ return null;
2644
3054
  }
2645
- function memberChainOf(node) {
2646
- const names = [];
2647
- let current = peelValue(node);
2648
- while (current.type === "attribute") {
2649
- const object = field(current, "object");
2650
- const attribute = field(current, "attribute");
2651
- if (object === null || attribute === null) {
2652
- return null;
3055
+ function declaredTypeName(name, from) {
3056
+ const fn = enclosingFunction(from);
3057
+ if (fn === null) {
3058
+ return null;
3059
+ }
3060
+ const params = field(fn, "parameters");
3061
+ for (const param of params === null ? [] : children(params)) {
3062
+ const info = parameterNameAndType(param);
3063
+ if (info?.name === name && info.typeNode !== null) {
3064
+ return typeNameOf(info.typeNode);
2653
3065
  }
2654
- names.unshift(attribute.text);
2655
- current = peelValue(object);
2656
3066
  }
2657
- if (current.type !== "identifier") {
3067
+ const visit = (node) => {
3068
+ const given = typeGivenBy(node, name);
3069
+ if (given !== null) {
3070
+ return given;
3071
+ }
3072
+ for (const child of children(node)) {
3073
+ if (child.type === "function_definition" || child.type === "lambda") {
3074
+ continue;
3075
+ }
3076
+ const found = visit(child);
3077
+ if (found !== null) {
3078
+ return found;
3079
+ }
3080
+ }
2658
3081
  return null;
2659
- }
2660
- return [current.text, ...names];
2661
- }
2662
- function scopeAt(node, module) {
2663
- let current = node;
2664
- while (current !== null) {
2665
- const scope = module.scopeFor.get(current.id);
2666
- if (scope !== void 0) {
2667
- return scope;
3082
+ };
3083
+ const body = field(fn, "body");
3084
+ for (const statement of body === null ? [] : bodyStatements(body)) {
3085
+ const found = visit(statement);
3086
+ if (found !== null) {
3087
+ return found;
2668
3088
  }
2669
- current = current.parent;
2670
3089
  }
2671
- return module.moduleScope;
3090
+ return null;
2672
3091
  }
2673
-
2674
- // src/values/rows.ts
2675
- import {
2676
- appended,
2677
- concat,
2678
- constant,
2679
- environmentRead,
2680
- equals,
2681
- extended,
2682
- fallback,
2683
- formatArguments,
2684
- hole,
2685
- joined,
2686
- joinedPath,
2687
- literalOf,
2688
- negated,
2689
- operand,
2690
- percentFormatted,
2691
- plus,
2692
- readableFallback,
2693
- stripped,
2694
- text,
2695
- truthOf
2696
- } from "@suss/values";
2697
- var operatorRows = [
2698
- {
2699
- kind: "operator",
2700
- operator: "+",
2701
- arity: 2,
2702
- apply: ([a, b]) => plus(operand(a), operand(b))
2703
- },
2704
- {
2705
- kind: "operator",
2706
- operator: "%",
2707
- arity: 2,
2708
- apply: ([a, b], contentOf) => percentFormatted(operand(a), formatArguments(contentOf(operand(b))))
2709
- },
2710
- {
2711
- kind: "operator",
2712
- operator: "==",
2713
- arity: 2,
2714
- apply: ([a, b]) => equals(operand(a), operand(b))
2715
- },
2716
- {
2717
- kind: "operator",
2718
- operator: "!=",
2719
- arity: 2,
2720
- apply: ([a, b]) => negated(equals(operand(a), operand(b)))
2721
- },
2722
- {
2723
- kind: "operator",
2724
- operator: "is",
2725
- arity: 2,
2726
- apply: ([a, b]) => equals(operand(a), operand(b))
2727
- },
2728
- {
2729
- kind: "operator",
2730
- operator: "is not",
2731
- arity: 2,
2732
- apply: ([a, b]) => negated(equals(operand(a), operand(b)))
2733
- },
2734
- {
2735
- kind: "operator",
2736
- operator: "not",
2737
- arity: 1,
2738
- apply: ([a]) => negated(operand(a))
2739
- },
2740
- {
2741
- kind: "operator",
2742
- operator: "or",
2743
- arity: 2,
2744
- apply: ([a, b]) => readableFallback(operand(a), operand(b), truthOf)
2745
- },
2746
- {
2747
- kind: "operator",
2748
- operator: "and",
2749
- arity: 2,
2750
- apply: ([a, b]) => fallback(operand(a), operand(b), (left) => {
2751
- const truth = truthOf(left);
2752
- return truth === null ? null : !truth;
2753
- })
2754
- }
2755
- ];
2756
- var stringRows = [
2757
- {
2758
- kind: "method",
2759
- method: "join",
2760
- on: "string",
2761
- apply: ({ receiver, args, contentOf }) => ({
2762
- result: joined(contentOf(operand(args[0])), operand(receiver))
2763
- })
2764
- },
2765
- {
2766
- kind: "method",
2767
- method: "format",
2768
- on: "string",
2769
- apply: ({ receiver, args }) => ({
2770
- result: braceFormatted(operand(receiver), args)
2771
- })
2772
- },
2773
- {
2774
- kind: "method",
2775
- method: "strip",
2776
- on: "string",
2777
- apply: ({ receiver, args }) => ({
2778
- result: args.length === 0 ? stripped(operand(receiver)) : hole("value")
2779
- })
2780
- },
2781
- {
2782
- kind: "method",
2783
- method: "rstrip",
2784
- on: "string",
2785
- apply: ({ receiver, args }) => ({
2786
- result: args.length === 0 ? stripped(operand(receiver), "end") : hole("value")
2787
- })
2788
- },
2789
- {
2790
- kind: "method",
2791
- method: "lstrip",
2792
- on: "string",
2793
- apply: ({ receiver, args }) => ({
2794
- result: args.length === 0 ? stripped(operand(receiver), "start") : hole("value")
2795
- })
2796
- }
2797
- ];
2798
- var sequenceRows = ["sequence", "unbounded"].flatMap((on) => [
2799
- {
2800
- kind: "method",
2801
- method: "append",
2802
- on,
2803
- apply: ({ receiver, args }) => ({
2804
- result: constant(null),
2805
- receiver: appended(operand(receiver), args)
2806
- })
2807
- },
2808
- {
2809
- kind: "method",
2810
- method: "extend",
2811
- on,
2812
- apply: ({ receiver, args, contentOf }) => ({
2813
- result: constant(null),
2814
- receiver: extended(operand(receiver), contentOf(operand(args[0])))
2815
- })
3092
+ function typedReceiverPattern(options, chain) {
3093
+ const receiver = receiverName(chain);
3094
+ if (receiver === null) {
3095
+ return void 0;
2816
3096
  }
2817
- ]);
2818
- var calleeRows = [
2819
- {
2820
- kind: "callee",
2821
- origin: { module: "builtins", name: "str" },
2822
- apply: ({ args }) => ({ result: concat([operand(args[0])]) })
2823
- },
2824
- {
2825
- kind: "callee",
2826
- origin: { module: "os.path", name: "join" },
2827
- apply: ({ args }) => ({ result: joinedPath(args) })
2828
- },
2829
- {
2830
- kind: "callee",
2831
- origin: { module: "os", name: "getenv" },
2832
- apply: ({ args }) => ({ result: environmentRead(args) })
2833
- },
2834
- {
2835
- kind: "callee",
2836
- origin: { module: "os.environ", name: "get" },
2837
- apply: ({ args }) => ({ result: environmentRead(args) })
3097
+ const typeName = declaredTypeName(receiver, chain.root);
3098
+ if (typeName === null) {
3099
+ return void 0;
2838
3100
  }
2839
- ];
2840
- var BRACE_PLACEHOLDER = /\{([^{}]*)\}/g;
2841
- function braceFormatted(receiver, args) {
2842
- const template = literalOf(receiver);
2843
- if (template === null) {
2844
- return hole("value");
3101
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${typeName}`);
3102
+ if (from === void 0) {
3103
+ return void 0;
2845
3104
  }
2846
- const parts = [];
2847
- let last = 0;
2848
- let position = 0;
2849
- for (const match of template.matchAll(BRACE_PLACEHOLDER)) {
2850
- parts.push(text(template.slice(last, match.index)));
2851
- const argument = args[position];
2852
- const placeholder = match[1] ?? "";
2853
- parts.push(
2854
- argument === void 0 ? hole(
2855
- placeholder === "" ? "value" : placeholder.split(/[!:]/)[0] ?? "value"
2856
- ) : concat([argument])
2857
- );
2858
- position += 1;
2859
- last = match.index + match[0].length;
3105
+ return options.patterns.find(
3106
+ (pattern) => String(from[1]) === pattern.module && pattern.queryTypes.includes(String(from[2]))
3107
+ );
3108
+ }
3109
+ function fieldsOf(chain, valueMethods) {
3110
+ const args = field(chain.root, "arguments");
3111
+ const named = [];
3112
+ for (const argument of args === null ? [] : children(args)) {
3113
+ if (argument.type === "keyword_argument") {
3114
+ const name = field(argument, "name");
3115
+ if (name !== null) {
3116
+ named.push(name.text);
3117
+ }
3118
+ continue;
3119
+ }
3120
+ if (argument.type === "attribute") {
3121
+ const property = field(argument, "attribute");
3122
+ if (property !== null) {
3123
+ named.push(property.text);
3124
+ }
3125
+ }
2860
3126
  }
2861
- parts.push(text(template.slice(last)));
2862
- return concat(parts);
3127
+ for (const call of laterCalls(chain)) {
3128
+ if (valueMethods.includes(methodNameOf(call))) {
3129
+ named.push(...keywordNames(call));
3130
+ }
3131
+ }
3132
+ return [...new Set(named)];
2863
3133
  }
2864
- var pythonRows = [
2865
- ...operatorRows,
2866
- ...stringRows,
2867
- ...sequenceRows,
2868
- ...calleeRows
2869
- ];
2870
-
2871
- // src/values/evaluator.ts
2872
- var evaluators = /* @__PURE__ */ new WeakMap();
2873
- var withoutFacts = /* @__PURE__ */ new WeakMap();
2874
- var modulesByTree = /* @__PURE__ */ new WeakMap();
2875
- function bindEvaluator(db, nodes) {
2876
- for (const entry of nodes.files) {
2877
- modulesByTree.set(entry.root.tree, entry.module);
3134
+ function laterCalls(chain) {
3135
+ return [...chain.between, chain.last].filter((call) => call !== chain.root);
3136
+ }
3137
+ function methodNameOf(call) {
3138
+ const callee = field(call, "function");
3139
+ return callee?.type === "attribute" ? field(callee, "attribute")?.text ?? "" : callee?.text ?? "";
3140
+ }
3141
+ function keywordNames(call) {
3142
+ const args = field(call, "arguments");
3143
+ const picked = [];
3144
+ for (const argument of args === null ? [] : children(args)) {
3145
+ if (argument.type === "keyword_argument") {
3146
+ const name = field(argument, "name");
3147
+ if (name !== null) {
3148
+ picked.push(name.text);
3149
+ }
3150
+ }
2878
3151
  }
2879
- evaluators.set(
2880
- db,
2881
- new Evaluator(
2882
- pythonLowering({
2883
- context: contextOver(db, nodes),
2884
- originOf: calleeOrigin,
2885
- rows: pythonRows
2886
- })
3152
+ return picked;
3153
+ }
3154
+ function selectorOf(chain, valueMethods) {
3155
+ return [
3156
+ ...new Set(
3157
+ laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap(keywordNames)
2887
3158
  )
2888
- );
3159
+ ];
2889
3160
  }
2890
- function evaluatedValue(node, db) {
2891
- return force(evaluatorFor(node, db).evaluate(node));
3161
+ function effectFor(pattern, chain, operation) {
3162
+ const valueMethods = pattern.valueMethods ?? [];
3163
+ const picked = selectorOf(chain, valueMethods);
3164
+ return {
3165
+ type: "interaction",
3166
+ binding: storageBinding2({
3167
+ recognition: "python-storage",
3168
+ storageSystem: pattern.storageSystem,
3169
+ scope: pattern.module,
3170
+ container: chain.subject
3171
+ }),
3172
+ callee: chain.last.text,
3173
+ interaction: {
3174
+ class: "storage-access",
3175
+ kind: pattern.writes.includes(operation) ? "write" : "read",
3176
+ fields: fieldsOf(chain, valueMethods),
3177
+ operation,
3178
+ ...picked.length > 0 ? { selector: picked } : {}
3179
+ }
3180
+ };
2892
3181
  }
2893
- function stringValueOf(node, db) {
2894
- return literalOf2(evaluatedValue(node, db));
3182
+ function fileOf(key) {
3183
+ const at = key.lastIndexOf(":");
3184
+ return at === -1 ? key : key.slice(0, at);
2895
3185
  }
2896
- function evaluatorFor(node, db) {
2897
- const bound = db === void 0 ? void 0 : evaluators.get(db);
2898
- if (bound !== void 0) {
2899
- return bound;
3186
+ function returnTypeName(node) {
3187
+ if (node === void 0) {
3188
+ return null;
2900
3189
  }
2901
- const tree = node.tree;
2902
- let local = withoutFacts.get(tree);
2903
- if (local === void 0) {
2904
- local = new Evaluator(
2905
- pythonLowering({
2906
- context: null,
2907
- originOf: calleeOrigin,
2908
- rows: pythonRows
2909
- })
2910
- );
2911
- withoutFacts.set(tree, local);
3190
+ const annotation = field(node, "return_type");
3191
+ if (annotation === null) {
3192
+ return null;
2912
3193
  }
2913
- return local;
3194
+ return annotation.type === "subscript" ? field(annotation, "value")?.text ?? null : annotation.text;
2914
3195
  }
2915
- function moduleOf(node) {
2916
- const tree = node.tree;
2917
- let module = modulesByTree.get(tree);
2918
- if (module === void 0) {
2919
- module = bindModule(tree.rootNode);
2920
- modulesByTree.set(tree, module);
3196
+ function storageCallees(calls, filePath, couldMatch) {
3197
+ return chainsIn(calls).filter((chain) => couldMatch.has(methodName(chain.root))).map((chain) => field(chain.root, "function")).filter((callee) => callee !== null).map((callee) => readKey(filePath, callee, enclosingFunction(callee)));
3198
+ }
3199
+ function resolvedMethodPattern(options, callee) {
3200
+ const resolved = options.facts.facts("wantedResolves").filter(
3201
+ (row) => String(row[0]) === readKey(options.filePath, callee, enclosingFunction(callee))
3202
+ ).map((row) => String(row[1]));
3203
+ const settled = resolved.length === 1 ? resolved[0] : void 0;
3204
+ if (settled === void 0) {
3205
+ return void 0;
2921
3206
  }
2922
- return module;
2923
- }
2924
- function calleeOrigin(callee) {
2925
- return originOf(callee, moduleOf(callee));
3207
+ const typeName = returnTypeName(options.definitionAt(settled));
3208
+ if (typeName === null) {
3209
+ return void 0;
3210
+ }
3211
+ return options.patterns.find(
3212
+ (candidate) => candidate.queryTypes.includes(typeName) && options.facts.facts("pyImport").some(
3213
+ (row) => String(row[0]) === fileOf(settled) && String(row[1]) === candidate.module
3214
+ )
3215
+ );
2926
3216
  }
2927
- function contextOver(db, nodes) {
2928
- const filesByRoot = /* @__PURE__ */ new Map();
2929
- const rootsByFile = /* @__PURE__ */ new Map();
2930
- for (const entry of nodes.files) {
2931
- filesByRoot.set(entry.root.id, entry);
2932
- rootsByFile.set(entry.file, entry.root);
3217
+ function matchedChains(calls, options) {
3218
+ if (options.patterns.length === 0) {
3219
+ return [];
2933
3220
  }
2934
- const keyOf2 = (node) => {
2935
- const entry = filesByRoot.get(node.tree.rootNode.id);
2936
- if (entry === void 0) {
2937
- return null;
3221
+ const named = new Set(
3222
+ options.patterns.flatMap((pattern) => pattern.queryFunctions ?? [])
3223
+ );
3224
+ const chains = chainsIn(calls).filter(
3225
+ (chain) => options.couldMatch.has(methodName(chain.root)) || named.has(methodName(chain.root)) || typedReceiverPattern(options, chain) !== void 0
3226
+ );
3227
+ if (chains.length === 0) {
3228
+ return [];
3229
+ }
3230
+ const alreadyAsked = new Set(
3231
+ options.facts.facts("wanted").map((row) => String(row[0]))
3232
+ );
3233
+ resolveCalls(
3234
+ options.facts,
3235
+ storageCallees(calls, options.filePath, options.couldMatch).filter(
3236
+ (key) => !alreadyAsked.has(key)
3237
+ )
3238
+ );
3239
+ const matched = [];
3240
+ for (const chain of chains) {
3241
+ const callee = field(chain.root, "function");
3242
+ if (callee === null) {
3243
+ continue;
2938
3244
  }
2939
- return readKey(entry.file, node, enclosingFunction(node));
2940
- };
2941
- return {
2942
- writtenTo: (node) => {
2943
- const key = keyOf2(node);
2944
- if (key === null) {
2945
- return null;
2946
- }
2947
- const answer = writtenValueOf(db, key);
2948
- return answer === null ? null : nodeOfKey(rootsByFile, answer);
2949
- },
2950
- callable: (call) => {
2951
- const callee = field(call, "function");
2952
- const key = callee === null ? null : keyOf2(callee);
2953
- if (key === null) {
2954
- return null;
2955
- }
2956
- resolveCalls(db, [key]);
2957
- const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2958
- const settled = resolved.length === 1 ? resolved[0] : void 0;
2959
- return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
3245
+ const imported = importedQueryFunction(options, chain);
3246
+ if (imported !== void 0) {
3247
+ matched.push({
3248
+ chain,
3249
+ pattern: imported,
3250
+ operation: methodName(chain.root)
3251
+ });
3252
+ continue;
2960
3253
  }
2961
- };
3254
+ const typed = typedReceiverPattern(options, chain);
3255
+ if (typed !== void 0) {
3256
+ const silent = (typed.recordsNothing ?? []).includes(
3257
+ methodName(chain.root)
3258
+ );
3259
+ matched.push({
3260
+ chain,
3261
+ pattern: typed,
3262
+ operation: silent ? null : chain.operation
3263
+ });
3264
+ continue;
3265
+ }
3266
+ const pattern = resolvedMethodPattern(options, callee);
3267
+ if (pattern !== void 0) {
3268
+ matched.push({ chain, pattern, operation: chain.operation });
3269
+ }
3270
+ }
3271
+ return matched;
3272
+ }
3273
+ function storageCallIds(calls, options) {
3274
+ return new Set(
3275
+ matchedChains(calls, options).map(({ chain }) => chain.root.id)
3276
+ );
3277
+ }
3278
+ function storageEffects(calls, options) {
3279
+ return matchedChains(calls, options).flatMap(
3280
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation)]
3281
+ );
2962
3282
  }
2963
3283
 
2964
3284
  // src/discovery.ts
@@ -3223,6 +3543,8 @@ function classRouteUnits(pattern, pack, classification, classNode, module, optio
3223
3543
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
3224
3544
  requestBodyFromAnnotatedClass: pattern.annotatedClassIsRequestBody === true,
3225
3545
  readsReturnedStatus: pattern.statusFromReturnedTuple === true,
3546
+ responseStatusCalls: pattern.responseStatusCalls ?? [],
3547
+ facts: options.facts,
3226
3548
  injectedCallees: new Set(pattern.injectedParameterCallees ?? []),
3227
3549
  definitionNode: maybeMethod,
3228
3550
  enclosingScope: classScope,
@@ -3407,36 +3729,106 @@ function effectsReaching(effects, conditions) {
3407
3729
  (effect) => guardsHoldOn(effect.preconditions, conditions)
3408
3730
  );
3409
3731
  }
3410
- function branchesFromReturns(readsReturnedStatus, definitionNode, effects, responseShape, declaredStatus) {
3411
- if (!readsReturnedStatus) {
3412
- return null;
3732
+ function returnStatusReading(statement, ctx) {
3733
+ if (!ctx.readsReturnedStatus) {
3734
+ return ctx.declaredStatus.reading;
3413
3735
  }
3414
- const body = field(definitionNode, "body");
3415
- const returns = returnStatements(body);
3416
- if (returns.length < 2) {
3736
+ const read = statusOfReturn(statement);
3737
+ const range = rangeOf(statement);
3738
+ if (read.kind === "status") {
3739
+ return writtenReading(read.value, range);
3740
+ }
3741
+ if (read.kind === "none") {
3742
+ return absentReading;
3743
+ }
3744
+ return unreadableReading(
3745
+ "This return gives a status this reading cannot resolve to a number, so this outcome claims none",
3746
+ range
3747
+ );
3748
+ }
3749
+ function returnBranch(statement, ctx) {
3750
+ const range = rangeOf(statement);
3751
+ const writtenBody = returnedBodyShape(statement);
3752
+ return {
3753
+ terminal: {
3754
+ kind: "response",
3755
+ // These stay null here. The extractor fills them in from the two
3756
+ // readings below.
3757
+ statusCode: null,
3758
+ body: { typeText: null, shape: null },
3759
+ exceptionType: null,
3760
+ message: null,
3761
+ component: null,
3762
+ renderTree: null,
3763
+ delegateTarget: null,
3764
+ emitEvent: null,
3765
+ location: range
3766
+ },
3767
+ statusCodeReading: {
3768
+ reading: returnStatusReading(statement, ctx),
3769
+ ...ctx.declaredStatus.libraryDefault !== void 0 ? { libraryDefault: ctx.declaredStatus.libraryDefault } : {}
3770
+ },
3771
+ bodyShapeReading: {
3772
+ reading: writtenBody === null ? ctx.responseShape.reading : writtenReading(writtenBody, range)
3773
+ },
3774
+ location: range
3775
+ };
3776
+ }
3777
+ function terminalBranchOf(found, ctx) {
3778
+ const table = {
3779
+ return: (variant) => returnBranch(variant.statement, ctx),
3780
+ raise: (variant) => ({
3781
+ terminal: variant.terminal,
3782
+ location: rangeOf(variant.statement)
3783
+ })
3784
+ };
3785
+ return dispatchByType(table, found);
3786
+ }
3787
+ function routeTerminals(body, raised) {
3788
+ const found = [
3789
+ ...returnStatements(body).map(
3790
+ (statement) => ({ type: "return", statement })
3791
+ ),
3792
+ ...raised.map(
3793
+ (response) => ({
3794
+ type: "raise",
3795
+ statement: response.statement,
3796
+ terminal: response.terminal
3797
+ })
3798
+ )
3799
+ ];
3800
+ return found.sort((a, b) => a.statement.startIndex - b.statement.startIndex);
3801
+ }
3802
+ function branchesPerTerminal(options) {
3803
+ const body = field(options.definitionNode, "body");
3804
+ const raised = raisedResponses(body, {
3805
+ calls: options.responseStatusCalls,
3806
+ module: options.module,
3807
+ facts: options.facts
3808
+ });
3809
+ const terminals = routeTerminals(body, raised);
3810
+ if (terminals.length < 2) {
3417
3811
  return null;
3418
3812
  }
3419
- const lowered = lowerPythonBody(body, returns);
3813
+ const statements = terminals.map((found) => found.statement);
3814
+ const thrownByCall = new NodeSet(
3815
+ raised.filter((response) => response.thrownByCall).map((response) => response.statement)
3816
+ );
3817
+ const lowered = lowerPythonBody(body, statements, thrownByCall);
3420
3818
  const enumerated = enumerateOrDegrade2(
3421
3819
  {
3422
3820
  statements: lowered.statements,
3423
3821
  terminalsByStmt: lowered.terminalsByStmt
3424
3822
  },
3425
- returns
3823
+ statements
3426
3824
  );
3427
3825
  const branches = [];
3428
- for (const statement of returns) {
3429
- const paths = enumerated.byTerminal.get(statement);
3826
+ for (const found of terminals) {
3827
+ const paths = enumerated.byTerminal.get(found.statement);
3430
3828
  if (paths === void 0) {
3431
3829
  continue;
3432
3830
  }
3433
- const read = statusOfReturn(statement);
3434
- const range = rangeOf(statement);
3435
- const writtenBody = returnedBodyShape(statement);
3436
- const statusReading = read.kind === "status" ? writtenReading(read.value, range) : read.kind === "none" ? absentReading : unreadableReading(
3437
- "This return gives a status this reading cannot resolve to a number, so this outcome claims none",
3438
- range
3439
- );
3831
+ const branch = terminalBranchOf(found, options.ctx);
3440
3832
  for (const path6 of paths) {
3441
3833
  const conditions = path6.map((condition) => ({
3442
3834
  sourceText: condition.sourceText,
@@ -3445,28 +3837,9 @@ function branchesFromReturns(readsReturnedStatus, definitionNode, effects, respo
3445
3837
  source: condition.source
3446
3838
  }));
3447
3839
  branches.push({
3840
+ ...branch,
3448
3841
  conditions,
3449
- terminal: {
3450
- kind: "response",
3451
- statusCode: null,
3452
- body: { typeText: null, shape: null },
3453
- exceptionType: null,
3454
- message: null,
3455
- component: null,
3456
- renderTree: null,
3457
- delegateTarget: null,
3458
- emitEvent: null,
3459
- location: range
3460
- },
3461
- statusCodeReading: {
3462
- reading: statusReading,
3463
- ...declaredStatus.libraryDefault !== void 0 ? { libraryDefault: declaredStatus.libraryDefault } : {}
3464
- },
3465
- bodyShapeReading: {
3466
- reading: writtenBody === null ? responseShape.reading : writtenReading(writtenBody, range)
3467
- },
3468
- effects: effectsReaching(effects, conditions),
3469
- location: range,
3842
+ effects: effectsReaching(options.effects, conditions),
3470
3843
  isDefault: path6.length === 0
3471
3844
  });
3472
3845
  }
@@ -3571,6 +3944,8 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
3571
3944
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
3572
3945
  requestBodyFromAnnotatedClass: pattern.annotatedClassIsRequestBody === true,
3573
3946
  readsReturnedStatus: pattern.statusFromReturnedTuple === true,
3947
+ responseStatusCalls: pattern.responseStatusCalls ?? [],
3948
+ facts: options.facts,
3574
3949
  injectedCallees: new Set(pattern.injectedParameterCallees ?? []),
3575
3950
  definitionNode: functionNode,
3576
3951
  enclosingScope: module.moduleScope,
@@ -3679,17 +4054,18 @@ function buildRouteUnit(options) {
3679
4054
  );
3680
4055
  const effects = invocationEffects(definitionNode);
3681
4056
  const extra = recognizedBodyEffects(definitionNode, module, storageLookup);
3682
- const perReturn = branchesFromReturns(
3683
- readsReturnedStatus,
4057
+ const perTerminal = branchesPerTerminal({
3684
4058
  definitionNode,
3685
4059
  effects,
3686
- responseShape,
3687
- statusCode
3688
- );
3689
- const branches = (perReturn ?? []).map(
4060
+ module,
4061
+ facts: options.facts,
4062
+ responseStatusCalls: options.responseStatusCalls,
4063
+ ctx: { readsReturnedStatus, responseShape, declaredStatus: statusCode }
4064
+ });
4065
+ const branches = (perTerminal ?? []).map(
3690
4066
  (branch) => extra.length === 0 ? branch : { ...branch, extraEffects: extra }
3691
4067
  );
3692
- if (perReturn === null && (responseShape.reading.kind !== "absent" || statusCode.reading.kind !== "absent" || extra.length > 0 || effects.length > 0)) {
4068
+ if (perTerminal === null && (responseShape.reading.kind !== "absent" || statusCode.reading.kind !== "absent" || extra.length > 0 || effects.length > 0)) {
3693
4069
  branches.push({
3694
4070
  conditions: [],
3695
4071
  terminal: {
@@ -3771,6 +4147,15 @@ function recognizedBodyEffects(definitionNode, module, storageLookup) {
3771
4147
  ];
3772
4148
  return [...envReadEffects(definitionNode, module), ...storage];
3773
4149
  }
4150
+ function recognizedCallIds(definitionNode, storageLookup) {
4151
+ if (storageLookup === void 0) {
4152
+ return /* @__PURE__ */ new Set();
4153
+ }
4154
+ return storageCallIds(bodyCalls(definitionNode), {
4155
+ ...storageLookup,
4156
+ filePath: storageLookup.factsPath
4157
+ });
4158
+ }
3774
4159
  function bodyContentOf(bodyNode) {
3775
4160
  for (const stmt of bodyStatements(bodyNode)) {
3776
4161
  if (stmt.type === "pass_statement") {
@@ -3885,26 +4270,6 @@ function isInjectedParameter(param, injectedCallees) {
3885
4270
  };
3886
4271
  return containsInjectorCall(field(param, "type"), 0);
3887
4272
  }
3888
- function parameterNameAndType(param) {
3889
- if (param.type === "identifier") {
3890
- return { name: param.text, typeNode: null };
3891
- }
3892
- if (param.type === "typed_parameter") {
3893
- const inner = param.namedChildren.find(
3894
- (child) => child !== null && child.type === "identifier"
3895
- );
3896
- return inner !== void 0 ? { name: inner.text, typeNode: field(param, "type") } : null;
3897
- }
3898
- if (param.type === "default_parameter") {
3899
- const nameNode = field(param, "name");
3900
- return nameNode?.type === "identifier" ? { name: nameNode.text, typeNode: null } : null;
3901
- }
3902
- if (param.type === "typed_default_parameter") {
3903
- const nameNode = field(param, "name");
3904
- return nameNode !== null ? { name: nameNode.text, typeNode: field(param, "type") } : null;
3905
- }
3906
- return null;
3907
- }
3908
4273
 
3909
4274
  // src/facts/why.ts
3910
4275
  import fs4 from "fs";
@@ -4449,7 +4814,11 @@ function reachedFunctions(seeds, options) {
4449
4814
  if (source === void 0) {
4450
4815
  continue;
4451
4816
  }
4452
- const scan = scanBody(source, ctx);
4817
+ const scan = scanBody(
4818
+ source,
4819
+ ctx,
4820
+ recognizedCallIds(source.node, options.storageFor(source.file))
4821
+ );
4453
4822
  if (scan.stops.length > 0) {
4454
4823
  stopsByKey.set(key, scan.stops);
4455
4824
  }
@@ -4514,7 +4883,7 @@ var EMPTY_SCAN = {
4514
4883
  parameterCalls: [],
4515
4884
  passedPositions: /* @__PURE__ */ new Set()
4516
4885
  };
4517
- function scanBody(source, ctx) {
4886
+ function scanBody(source, ctx, recognized) {
4518
4887
  const followed = [];
4519
4888
  const stops = [];
4520
4889
  const placements = new TargetPlacements();
@@ -4579,7 +4948,7 @@ function scanBody(source, ctx) {
4579
4948
  );
4580
4949
  if (outcome.kind === "stopped") {
4581
4950
  const stopKey = `${outcome.reason}:${callee}`;
4582
- if (!seen.has(stopKey) && worthRecording(outcome.reason)) {
4951
+ if (!seen.has(stopKey) && !recognized.has(call.id) && worthRecording(outcome.reason)) {
4583
4952
  seen.add(stopKey);
4584
4953
  stops.push({ callee, reason: outcome.reason });
4585
4954
  }