@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.cjs CHANGED
@@ -100,6 +100,40 @@ function enclosingFunction(node) {
100
100
  }
101
101
  return null;
102
102
  }
103
+ function stringContentValue(content) {
104
+ const base = content.startIndex;
105
+ let text2 = "";
106
+ let cursor = 0;
107
+ for (const child of children(content)) {
108
+ if (child.type !== "escape_interpolation") {
109
+ continue;
110
+ }
111
+ text2 += content.text.slice(cursor, child.startIndex - base);
112
+ text2 += child.text.slice(0, 1);
113
+ cursor = child.endIndex - base;
114
+ }
115
+ return text2 + content.text.slice(cursor);
116
+ }
117
+ function parameterNameAndType(param) {
118
+ if (param.type === "identifier") {
119
+ return { name: param.text, typeNode: null };
120
+ }
121
+ if (param.type === "typed_parameter") {
122
+ const inner = param.namedChildren.find(
123
+ (child) => child !== null && child.type === "identifier"
124
+ );
125
+ return inner !== void 0 ? { name: inner.text, typeNode: field(param, "type") } : null;
126
+ }
127
+ if (param.type === "default_parameter") {
128
+ const nameNode = field(param, "name");
129
+ return nameNode?.type === "identifier" ? { name: nameNode.text, typeNode: null } : null;
130
+ }
131
+ if (param.type === "typed_default_parameter") {
132
+ const nameNode = field(param, "name");
133
+ return nameNode !== null ? { name: nameNode.text, typeNode: field(param, "type") } : null;
134
+ }
135
+ return null;
136
+ }
103
137
  function stringLiteralValue(node) {
104
138
  if (node.type !== "string") {
105
139
  return null;
@@ -110,7 +144,7 @@ function stringLiteralValue(node) {
110
144
  continue;
111
145
  }
112
146
  if (child.type === "string_content") {
113
- content += child.text;
147
+ content += stringContentValue(child);
114
148
  continue;
115
149
  }
116
150
  if (child.type === "interpolation") {
@@ -138,6 +172,8 @@ function stripDecorators(node) {
138
172
  );
139
173
  return { definition, decorators };
140
174
  }
175
+ var NodeSet = class extends import_extractor.IdSet {
176
+ };
141
177
  var NodeMap = class extends import_extractor.IdMap {
142
178
  };
143
179
 
@@ -835,7 +871,7 @@ function passesParametersThrough(def, inner) {
835
871
  // src/discovery.ts
836
872
  var import_behavioral_ir2 = require("@suss/behavioral-ir");
837
873
  var import_extractor4 = require("@suss/extractor");
838
- var import_values6 = require("@suss/values");
874
+ var import_values7 = require("@suss/values");
839
875
 
840
876
  // src/envReads.ts
841
877
  var import_behavioral_ir = require("@suss/behavioral-ir");
@@ -1465,14 +1501,14 @@ var EXIT_KINDS = {
1465
1501
  continue_statement: "continue"
1466
1502
  };
1467
1503
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
1468
- function exitKindOf(node) {
1504
+ function exitKindOf(node, thrown) {
1469
1505
  let sawReturn = false;
1470
1506
  const raises = (current) => {
1471
1507
  for (const child of current.namedChildren) {
1472
1508
  if (child === null || NESTED_FUNCTION_TYPES.has(child.type)) {
1473
1509
  continue;
1474
1510
  }
1475
- if (child.type === "raise_statement") {
1511
+ if (child.type === "raise_statement" || thrown.has(child)) {
1476
1512
  return true;
1477
1513
  }
1478
1514
  if (child.type === "return_statement") {
@@ -1506,10 +1542,12 @@ function childBlock(node) {
1506
1542
  var Lowerer = class {
1507
1543
  // tree-sitter hands back a fresh wrapper object each time a child is
1508
1544
  // read, so a terminal is matched by its stable node id.
1509
- constructor(terminals) {
1545
+ constructor(terminals, thrown) {
1510
1546
  this.terminals = terminals;
1547
+ this.thrown = thrown;
1511
1548
  }
1512
1549
  terminals;
1550
+ thrown;
1513
1551
  terminalsByStmt = /* @__PURE__ */ new Map();
1514
1552
  terminalHome = new NodeMap();
1515
1553
  attach(statement, candidates) {
@@ -1542,7 +1580,7 @@ var Lowerer = class {
1542
1580
  condition: handleOf(field(clause, "condition")),
1543
1581
  thenBody: this.lowerBlock(field(clause, "consequence")),
1544
1582
  elseBody: this.lowerIfTail(clauses, index + 1),
1545
- exitKind: exitKindOf(clause)
1583
+ exitKind: exitKindOf(clause, this.thrown)
1546
1584
  };
1547
1585
  this.attach(nested, [field(clause, "condition")]);
1548
1586
  return [nested];
@@ -1556,7 +1594,7 @@ var Lowerer = class {
1556
1594
  condition: handleOf(field(node, "condition")),
1557
1595
  thenBody: this.lowerBlock(field(node, "consequence")),
1558
1596
  elseBody: this.lowerIfTail(clauses, 0),
1559
- exitKind: exitKindOf(node)
1597
+ exitKind: exitKindOf(node, this.thrown)
1560
1598
  };
1561
1599
  }
1562
1600
  lowerTry(node) {
@@ -1571,7 +1609,7 @@ var Lowerer = class {
1571
1609
  tryBody: this.lowerBlock(field(node, "body")),
1572
1610
  catchBody: excepts.length > 0 ? excepts.flatMap((clause) => this.lowerBlock(childBlock(clause))) : null,
1573
1611
  finallyBody: finallyClause === null ? null : this.lowerBlock(childBlock(finallyClause)),
1574
- exitKind: exitKindOf(node)
1612
+ exitKind: exitKindOf(node, this.thrown)
1575
1613
  };
1576
1614
  }
1577
1615
  lowerMatch(node) {
@@ -1588,15 +1626,21 @@ var Lowerer = class {
1588
1626
  body: this.lowerBlock(field(clause, "consequence"))
1589
1627
  };
1590
1628
  });
1591
- return { kind: "switch", groups, exitKind: exitKindOf(node) };
1629
+ return { kind: "switch", groups, exitKind: exitKindOf(node, this.thrown) };
1592
1630
  }
1593
1631
  lower(node) {
1594
- const exit = EXIT_KINDS[node.type];
1595
- if (exit !== void 0) {
1596
- return this.attach({ kind: "exit", exit, exitKind: exitKindOf(node) }, [
1632
+ if (this.thrown.has(node)) {
1633
+ return this.attach({ kind: "exit", exit: "throw", exitKind: "throw" }, [
1597
1634
  node
1598
1635
  ]);
1599
1636
  }
1637
+ const exit = EXIT_KINDS[node.type];
1638
+ if (exit !== void 0) {
1639
+ return this.attach(
1640
+ { kind: "exit", exit, exitKind: exitKindOf(node, this.thrown) },
1641
+ [node]
1642
+ );
1643
+ }
1600
1644
  if (node.type === "if_statement") {
1601
1645
  return this.attach(this.lowerIf(node), [field(node, "condition")]);
1602
1646
  }
@@ -1607,7 +1651,7 @@ var Lowerer = class {
1607
1651
  kind: "loop",
1608
1652
  condition: handleOf(header),
1609
1653
  body: this.lowerBlock(field(node, "body")),
1610
- exitKind: exitKindOf(node)
1654
+ exitKind: exitKindOf(node, this.thrown)
1611
1655
  },
1612
1656
  [header]
1613
1657
  );
@@ -1618,12 +1662,16 @@ var Lowerer = class {
1618
1662
  if (node.type === "match_statement") {
1619
1663
  return this.attach(this.lowerMatch(node), [field(node, "subject")]);
1620
1664
  }
1621
- return this.attach({ kind: "opaque", exitKind: exitKindOf(node) }, [node]);
1665
+ return this.attach(
1666
+ { kind: "opaque", exitKind: exitKindOf(node, this.thrown) },
1667
+ [node]
1668
+ );
1622
1669
  }
1623
1670
  };
1624
- function lowerPythonBody(body, terminals) {
1671
+ function lowerPythonBody(body, terminals, thrown = new NodeSet()) {
1625
1672
  const lowerer = new Lowerer(
1626
- new Map(terminals.map((terminal) => [terminal.id, terminal]))
1673
+ new Map(terminals.map((terminal) => [terminal.id, terminal])),
1674
+ thrown
1627
1675
  );
1628
1676
  return {
1629
1677
  statements: lowerer.lowerBlock(body),
@@ -1820,1171 +1868,1443 @@ function invocationEffects(definitionNode) {
1820
1868
  });
1821
1869
  }
1822
1870
 
1823
- // src/paths/returnedShape.ts
1824
- var MAX_DEPTH = 6;
1825
- var SCALARS = {
1826
- none: { type: "null" },
1827
- true: { type: "boolean" },
1828
- false: { type: "boolean" }
1871
+ // src/paths/raisedResponses.ts
1872
+ var import_values5 = require("@suss/values");
1873
+
1874
+ // src/values/evaluator.ts
1875
+ var import_resolution2 = require("@suss/resolution");
1876
+ var import_values3 = require("@suss/values");
1877
+
1878
+ // src/values/lowering.ts
1879
+ var ROOT_TYPES = /* @__PURE__ */ new Set(["module", "function_definition", "lambda"]);
1880
+ var LITERAL_TYPES = {
1881
+ integer: (node) => ({ kind: "literal", value: Number(node.text) }),
1882
+ float: (node) => ({ kind: "literal", value: Number(node.text) }),
1883
+ true: () => ({ kind: "literal", value: true }),
1884
+ false: () => ({ kind: "literal", value: false }),
1885
+ none: () => ({ kind: "literal", value: null })
1829
1886
  };
1830
- function children2(node) {
1831
- return node.namedChildren.filter((child) => child !== null);
1887
+ var WRITING_ROWS = /* @__PURE__ */ new Set(["append", "extend"]);
1888
+ function readingMethodsOf(rows) {
1889
+ return new Set(
1890
+ rows.flatMap(
1891
+ (row) => row.kind === "method" && !WRITING_ROWS.has(row.method) ? [row.method] : []
1892
+ )
1893
+ );
1832
1894
  }
1833
- function recordShape(node, depth) {
1834
- const properties = {};
1835
- let everyKeyWritten = true;
1836
- for (const pair of children2(node)) {
1837
- if (pair.type !== "pair") {
1838
- everyKeyWritten = false;
1839
- continue;
1840
- }
1841
- const key = field(pair, "key");
1842
- const value = field(pair, "value");
1843
- if (key === null || value === null || key.type !== "string") {
1844
- everyKeyWritten = false;
1845
- continue;
1846
- }
1847
- properties[key.text.slice(1, -1)] = shapeOfReturned(value, depth + 1);
1848
- }
1849
- return everyKeyWritten && Object.keys(properties).length > 0 ? { type: "record", properties } : { type: "dictionary", values: { type: "unknown" } };
1895
+ function pythonLowering(options) {
1896
+ const { context, originOf: originOf2, rows } = options;
1897
+ const readingMethods = readingMethodsOf(rows);
1898
+ const mutatedByRoot = /* @__PURE__ */ new Map();
1899
+ return {
1900
+ idOf: (node) => node.id,
1901
+ expression: (node) => expressionOf(node, originOf2),
1902
+ statement: statementOf,
1903
+ siteOf,
1904
+ functionOf,
1905
+ writtenTo: (node) => context === null ? null : context.writtenTo(node),
1906
+ callable: (node) => {
1907
+ if (context === null || hasDictionarySplat(node)) {
1908
+ return null;
1909
+ }
1910
+ return context.callable(node);
1911
+ },
1912
+ mutatedInNestedFunction: (root, name) => {
1913
+ let mutated = mutatedByRoot.get(root.id);
1914
+ if (mutated === void 0) {
1915
+ mutated = namesMutatedInNestedFunctions(root, readingMethods);
1916
+ mutatedByRoot.set(root.id, mutated);
1917
+ }
1918
+ return mutated.has(name);
1919
+ },
1920
+ freeNamesOf,
1921
+ holeNameOf: placeholderName,
1922
+ rows
1923
+ };
1850
1924
  }
1851
- function shapeOfReturned(node, depth = 0) {
1852
- if (depth > MAX_DEPTH) {
1853
- return { type: "unknown" };
1854
- }
1855
- const scalar = SCALARS[node.type];
1856
- if (scalar !== void 0) {
1857
- return scalar;
1925
+ function placeholderName(node) {
1926
+ const peeled = peelValue(node);
1927
+ if (peeled.type === "identifier") {
1928
+ return peeled.text;
1858
1929
  }
1859
- if (node.type === "string") {
1860
- return { type: "literal", value: node.text.slice(1, -1) };
1930
+ if (peeled.type === "attribute") {
1931
+ return field(peeled, "attribute")?.text ?? "param";
1861
1932
  }
1862
- if (node.type === "integer") {
1863
- const value = Number.parseInt(node.text, 10);
1864
- return Number.isNaN(value) ? { type: "integer" } : { type: "literal", value };
1933
+ return "param";
1934
+ }
1935
+ function peelValue(node) {
1936
+ const wraps = node.type === "parenthesized_expression" || node.type === "await";
1937
+ const inner = wraps ? children(node)[0] : void 0;
1938
+ return inner === void 0 ? node : peelValue(inner);
1939
+ }
1940
+ var OPAQUE = { kind: "opaque" };
1941
+ function fromFields(node, names, fallback2, build) {
1942
+ const found = names.map((name) => field(node, name));
1943
+ if (found.some((part) => part === null)) {
1944
+ return fallback2;
1865
1945
  }
1866
- if (node.type === "float") {
1867
- return { type: "number" };
1946
+ return build(...found);
1947
+ }
1948
+ function expressionOf(node, originOf2) {
1949
+ const peeled = peelValue(node);
1950
+ const literal = LITERAL_TYPES[peeled.type];
1951
+ if (literal !== void 0) {
1952
+ return literal(peeled);
1868
1953
  }
1869
- if (node.type === "dictionary") {
1870
- return recordShape(node, depth);
1954
+ if (peeled.type === "string") {
1955
+ return stringExpression(peeled);
1871
1956
  }
1872
- if (node.type === "list" || node.type === "tuple" || node.type === "set") {
1873
- const first = children2(node)[0];
1957
+ if (peeled.type === "concatenated_string") {
1874
1958
  return {
1875
- type: "array",
1876
- items: first === void 0 ? { type: "unknown" } : shapeOfReturned(first, depth + 1)
1959
+ kind: "template",
1960
+ parts: children(peeled).map((part) => ({ expression: part }))
1877
1961
  };
1878
1962
  }
1879
- if (node.type === "identifier" || node.type === "attribute") {
1880
- return { type: "ref", name: node.text };
1881
- }
1882
- return { type: "unknown" };
1883
- }
1884
- function returnedBodyShape(statement) {
1885
- const returned = statement.namedChildren[0];
1886
- if (returned == null) {
1887
- return null;
1888
- }
1889
- const value = returned.type === "expression_list" ? children2(returned)[0] : returned;
1890
- if (value === void 0) {
1891
- return null;
1963
+ if (peeled.type === "identifier") {
1964
+ return { kind: "name", text: peeled.text };
1892
1965
  }
1893
- const shape = shapeOfReturned(value);
1894
- return shape.type === "unknown" ? null : shape;
1895
- }
1896
-
1897
- // src/rawSql.ts
1898
- var import_ir_core = require("@suss/ir-core");
1899
- var import_sql = require("@suss/sql");
1900
- function rawSqlEffects(calls, options) {
1901
- if (options.patterns.length === 0) {
1902
- return [];
1966
+ if (peeled.type === "attribute") {
1967
+ return fromFields(
1968
+ peeled,
1969
+ ["object", "attribute"],
1970
+ OPAQUE,
1971
+ (object, name) => ({
1972
+ kind: "member",
1973
+ object,
1974
+ name: name.text
1975
+ })
1976
+ );
1903
1977
  }
1904
- const effects = [];
1905
- for (const call of calls) {
1906
- const pattern = patternFor(call, options);
1907
- if (pattern === void 0) {
1908
- continue;
1909
- }
1910
- const statement = statementIn(call);
1911
- if (statement === null) {
1912
- continue;
1913
- }
1914
- for (const access of (0, import_sql.readSqlAccess)(statement, {
1915
- dialect: pattern.storageSystem
1916
- })) {
1917
- effects.push({
1918
- type: "interaction",
1919
- binding: (0, import_ir_core.storageBinding)({
1920
- recognition: `python-${pattern.module}`,
1921
- storageSystem: pattern.storageSystem,
1922
- scope: pattern.module,
1923
- container: access.table
1924
- }),
1925
- callee: field(call, "function")?.text ?? "",
1926
- interaction: {
1927
- class: "storage-access",
1928
- kind: access.kind,
1929
- fields: access.fields,
1930
- ...access.selector.length > 0 ? { selector: access.selector } : {},
1931
- operation: field(call, "function")?.text ?? ""
1932
- }
1933
- });
1978
+ if (peeled.type === "subscript") {
1979
+ const object = field(peeled, "value");
1980
+ const index = field(peeled, "subscript");
1981
+ if (object === null || index === null || index.type === "slice") {
1982
+ return { kind: "opaque" };
1934
1983
  }
1984
+ return { kind: "element", object, index };
1935
1985
  }
1936
- return effects;
1937
- }
1938
- function patternFor(call, options) {
1939
- const callee = field(call, "function");
1940
- if (callee === null || callee.type !== "identifier") {
1941
- return void 0;
1986
+ if (peeled.type === "list" || peeled.type === "tuple" || peeled.type === "expression_list") {
1987
+ return { kind: "array", items: children(peeled).map(elementOf) };
1942
1988
  }
1943
- const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
1944
- if (from === void 0) {
1945
- return void 0;
1989
+ if (peeled.type === "dictionary") {
1990
+ return { kind: "record", fields: children(peeled).flatMap(fieldOf) };
1946
1991
  }
1947
- const module2 = String(from[1]);
1948
- return options.patterns.find(
1949
- (pattern) => pattern.module === module2 && pattern.functions.includes(callee.text)
1950
- );
1951
- }
1952
- function statementIn(call) {
1953
- const args = field(call, "arguments");
1954
- const first = args?.namedChildren.find((child) => child !== null) ?? null;
1955
- if (first === null || first.type !== "string") {
1956
- return null;
1992
+ if (peeled.type === "call") {
1993
+ return callExpression(peeled, originOf2);
1957
1994
  }
1958
- const parts = [""];
1959
- for (const child of first.namedChildren) {
1960
- if (child === null) {
1961
- continue;
1962
- }
1963
- if (child.type === "string_content") {
1964
- parts[parts.length - 1] += child.text;
1965
- continue;
1966
- }
1967
- if (child.type === "interpolation") {
1968
- parts.push("");
1969
- }
1970
- }
1971
- const statement = (0, import_sql.sqlFromParts)(parts);
1972
- return statement.trim() === "" ? null : statement;
1973
- }
1974
-
1975
- // src/storage.ts
1976
- var import_ir_core2 = require("@suss/ir-core");
1977
- function receiverCall(call) {
1978
- const callee = field(call, "function");
1979
- if (callee === null || callee.type !== "attribute") {
1980
- return null;
1995
+ if (peeled.type === "binary_operator" || peeled.type === "boolean_operator") {
1996
+ return fromFields(
1997
+ peeled,
1998
+ ["left", "operator", "right"],
1999
+ OPAQUE,
2000
+ (left, operator, right) => ({
2001
+ kind: "operator",
2002
+ operator: operator.text,
2003
+ operands: [left, right]
2004
+ })
2005
+ );
1981
2006
  }
1982
- const object = field(callee, "object");
1983
- return object !== null && object.type === "call" ? object : null;
1984
- }
1985
- function methodName(call) {
1986
- const callee = field(call, "function");
1987
- if (callee === null || callee.type !== "attribute") {
1988
- return callee?.text ?? "";
2007
+ if (peeled.type === "comparison_operator") {
2008
+ return comparisonExpression(peeled);
1989
2009
  }
1990
- return field(callee, "attribute")?.text ?? "";
1991
- }
1992
- function subjectOf(call) {
1993
- const callee = field(call, "function");
1994
- if (callee === null || callee.type !== "attribute") {
1995
- return callee?.text ?? "";
2010
+ if (peeled.type === "not_operator") {
2011
+ return fromFields(
2012
+ peeled,
2013
+ ["argument"],
2014
+ OPAQUE,
2015
+ (argument) => ({
2016
+ kind: "operator",
2017
+ operator: "not",
2018
+ operands: [argument]
2019
+ })
2020
+ );
1996
2021
  }
1997
- return field(callee, "object")?.text ?? "";
1998
- }
1999
- function rootOf(call) {
2000
- const receiver = receiverCall(call);
2001
- return receiver === null ? call : rootOf(receiver);
2002
- }
2003
- function chainsIn(calls) {
2004
- const endsWith = /* @__PURE__ */ new Map();
2005
- for (const call of calls) {
2006
- const receiver = receiverCall(call);
2007
- if (receiver === null) {
2008
- continue;
2009
- }
2010
- const start = rootOf(receiver);
2011
- const kept = endsWith.get(start.id);
2012
- if (kept === void 0 || call.endIndex > kept.endIndex) {
2013
- endsWith.set(start.id, call);
2022
+ if (peeled.type === "conditional_expression") {
2023
+ const [whenTrue, condition, whenFalse] = children(peeled);
2024
+ if (whenTrue === void 0 || condition === void 0 || whenFalse === void 0) {
2025
+ return { kind: "opaque" };
2014
2026
  }
2027
+ return { kind: "conditional", condition, whenTrue, whenFalse };
2015
2028
  }
2016
- const chains = [];
2017
- for (const call of calls) {
2018
- if (receiverCall(call) !== null) {
2019
- continue;
2020
- }
2021
- const last = endsWith.get(call.id) ?? call;
2022
- chains.push({
2023
- root: call,
2024
- last,
2025
- subject: subjectOf(call),
2026
- operation: methodName(last),
2027
- between: calls.filter(
2028
- (other) => other !== call && other !== last && other.startIndex >= call.startIndex && other.endIndex <= last.endIndex
2029
- )
2030
- });
2029
+ if (peeled.type === "lambda") {
2030
+ return { kind: "function", node: peeled };
2031
2031
  }
2032
- return chains;
2032
+ return { kind: "opaque" };
2033
2033
  }
2034
- function importedQueryFunction(options, chain) {
2035
- const callee = field(chain.root, "function");
2036
- if (callee === null || callee.type !== "identifier") {
2037
- return void 0;
2038
- }
2039
- const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
2040
- if (from === void 0) {
2041
- return void 0;
2034
+ function stringExpression(node) {
2035
+ const plain = stringLiteralValue(node);
2036
+ if (plain !== null) {
2037
+ return { kind: "literal", value: plain };
2042
2038
  }
2043
- return options.patterns.find(
2044
- (pattern) => String(from[1]) === pattern.module && (pattern.queryFunctions ?? []).includes(String(from[2]))
2045
- );
2046
- }
2047
- function fieldsOf(chain) {
2048
- const args = field(chain.root, "arguments");
2049
- const named = [];
2050
- for (const argument of args === null ? [] : children(args)) {
2051
- if (argument.type === "keyword_argument") {
2052
- const name = field(argument, "name");
2053
- if (name !== null) {
2054
- named.push(name.text);
2055
- }
2039
+ const parts = [];
2040
+ for (const child of children(node)) {
2041
+ if (child.type === "string_content") {
2042
+ parts.push({ text: stringContentValue(child) });
2056
2043
  continue;
2057
2044
  }
2058
- if (argument.type === "attribute") {
2059
- const property = field(argument, "attribute");
2060
- if (property !== null) {
2061
- named.push(property.text);
2045
+ if (child.type === "interpolation") {
2046
+ const expression = field(child, "expression") ?? children(child)[0];
2047
+ if (expression !== void 0) {
2048
+ parts.push({ expression });
2062
2049
  }
2063
2050
  }
2064
2051
  }
2065
- return named;
2052
+ return { kind: "template", parts };
2066
2053
  }
2067
- function selectorOf(call) {
2068
- const args = field(call, "arguments");
2069
- const picked = [];
2070
- for (const argument of args === null ? [] : children(args)) {
2071
- if (argument.type === "keyword_argument") {
2072
- const name = field(argument, "name");
2073
- if (name !== null) {
2074
- picked.push(name.text);
2075
- }
2076
- }
2054
+ function comparisonExpression(node) {
2055
+ const operands = children(node);
2056
+ const operators = node.children.filter(
2057
+ (child) => child !== null && !child.isNamed
2058
+ );
2059
+ const operator = operators[0];
2060
+ if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2061
+ return { kind: "opaque" };
2077
2062
  }
2078
- return picked;
2079
- }
2080
- function effectFor(pattern, chain, options) {
2081
- const picked = [
2082
- ...new Set([chain.last, ...chain.between].flatMap(selectorOf))
2083
- ];
2084
2063
  return {
2085
- type: "interaction",
2086
- binding: (0, import_ir_core2.storageBinding)({
2087
- recognition: "python-storage",
2088
- storageSystem: pattern.storageSystem,
2089
- scope: pattern.module,
2090
- container: chain.subject
2091
- }),
2092
- callee: chain.last.text,
2093
- interaction: {
2094
- class: "storage-access",
2095
- kind: pattern.writes.includes(chain.operation) ? "write" : "read",
2096
- fields: fieldsOf(chain),
2097
- operation: chain.operation,
2098
- ...picked.length > 0 ? { selector: picked } : {}
2099
- }
2064
+ kind: "operator",
2065
+ operator: operator.text,
2066
+ operands: [operands[0], operands[1]]
2100
2067
  };
2101
2068
  }
2102
- function fileOf(key) {
2103
- const at = key.lastIndexOf(":");
2104
- return at === -1 ? key : key.slice(0, at);
2069
+ function callExpression(node, originOf2) {
2070
+ return fromFields(
2071
+ node,
2072
+ ["function", "arguments"],
2073
+ OPAQUE,
2074
+ (callee, argumentList) => {
2075
+ const peeledCallee = peelValue(callee);
2076
+ const args = argumentList.type === "argument_list" ? argumentsOf(argumentList) : [];
2077
+ const isMethod2 = peeledCallee.type === "attribute";
2078
+ return {
2079
+ kind: "call",
2080
+ callee: {
2081
+ receiver: isMethod2 ? field(peeledCallee, "object") : null,
2082
+ name: calleeName(peeledCallee),
2083
+ origin: () => originOf2(peeledCallee)
2084
+ },
2085
+ args,
2086
+ constructs: false
2087
+ };
2088
+ }
2089
+ );
2105
2090
  }
2106
- function returnTypeName(node) {
2107
- if (node === void 0) {
2108
- return null;
2091
+ function calleeName(callee) {
2092
+ if (callee.type === "identifier") {
2093
+ return callee.text;
2109
2094
  }
2110
- const annotation = field(node, "return_type");
2111
- if (annotation === null) {
2112
- return null;
2095
+ if (callee.type === "attribute") {
2096
+ return field(callee, "attribute")?.text ?? null;
2113
2097
  }
2114
- return annotation.type === "subscript" ? field(annotation, "value")?.text ?? null : annotation.text;
2098
+ return null;
2115
2099
  }
2116
- function storageCallees(calls, filePath, couldMatch) {
2117
- 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));
2100
+ function argumentsOf(argumentList) {
2101
+ return children(argumentList).flatMap((argument) => {
2102
+ if (argument.type === "dictionary_splat") {
2103
+ return [];
2104
+ }
2105
+ if (argument.type === "keyword_argument") {
2106
+ const name = field(argument, "name");
2107
+ const value = field(argument, "value");
2108
+ return name === null || value === null ? [] : [{ kind: "named", name: name.text, node: value }];
2109
+ }
2110
+ return [elementOf(argument)];
2111
+ });
2118
2112
  }
2119
- function storageEffects(calls, options) {
2120
- if (options.patterns.length === 0) {
2121
- return [];
2113
+ function hasDictionarySplat(call) {
2114
+ const argumentList = field(call, "arguments");
2115
+ if (argumentList === null) {
2116
+ return false;
2122
2117
  }
2123
- const named = new Set(
2124
- options.patterns.flatMap((pattern) => pattern.queryFunctions ?? [])
2125
- );
2126
- const chains = chainsIn(calls).filter(
2127
- (chain) => options.couldMatch.has(methodName(chain.root)) || named.has(methodName(chain.root))
2118
+ return children(argumentList).some(
2119
+ (argument) => argument.type === "dictionary_splat"
2128
2120
  );
2129
- if (chains.length === 0) {
2121
+ }
2122
+ function elementOf(node) {
2123
+ if (node.type === "list_splat") {
2124
+ const inner = children(node)[0];
2125
+ return inner === void 0 ? { kind: "value", node } : { kind: "spread", node: inner };
2126
+ }
2127
+ return { kind: "value", node };
2128
+ }
2129
+ function fieldOf(node) {
2130
+ if (node.type === "dictionary_splat") {
2131
+ const inner = children(node)[0];
2132
+ return inner === void 0 ? [] : [{ kind: "spread", node: inner }];
2133
+ }
2134
+ if (node.type !== "pair") {
2130
2135
  return [];
2131
2136
  }
2132
- const alreadyAsked = new Set(
2133
- options.facts.facts("wanted").map((row) => String(row[0]))
2134
- );
2135
- resolveCalls(
2136
- options.facts,
2137
- storageCallees(calls, options.filePath, options.couldMatch).filter(
2138
- (key) => !alreadyAsked.has(key)
2139
- )
2140
- );
2141
- const effects = [];
2142
- for (const chain of chains) {
2143
- const callee = field(chain.root, "function");
2144
- if (callee === null) {
2145
- continue;
2146
- }
2147
- const imported = importedQueryFunction(options, chain);
2148
- if (imported !== void 0) {
2149
- effects.push(effectFor(imported, chain, options));
2150
- continue;
2151
- }
2152
- const resolved = options.facts.facts("wantedResolves").filter((row) => String(row[0]) === nodeId(options.filePath, callee)).map((row) => String(row[1]));
2153
- const settled = resolved.length === 1 ? resolved[0] : void 0;
2154
- if (settled === void 0) {
2155
- continue;
2156
- }
2157
- const typeName = returnTypeName(options.definitionAt(settled));
2158
- if (typeName === null) {
2159
- continue;
2160
- }
2161
- const pattern = options.patterns.find(
2162
- (candidate) => candidate.queryTypes.includes(typeName) && options.facts.facts("pyImport").some(
2163
- (row) => String(row[0]) === fileOf(settled) && String(row[1]) === candidate.module
2137
+ return fromFields(node, ["key", "value"], [], (key, value) => {
2138
+ const name = stringLiteralValue(key);
2139
+ return name === null ? [{ kind: "computed", name: key, value }] : [{ kind: "field", name, value }];
2140
+ });
2141
+ }
2142
+ var STATEMENT_TYPES = {
2143
+ expression_statement: expressionStatement,
2144
+ if_statement: ifStatement,
2145
+ elif_clause: ifStatement,
2146
+ for_statement: loopStatement,
2147
+ while_statement: loopStatement,
2148
+ return_statement: (node) => ({
2149
+ kind: "return",
2150
+ value: children(node)[0] ?? null
2151
+ }),
2152
+ try_statement: (node) => ({
2153
+ kind: "block",
2154
+ body: [
2155
+ ...blockStatements2(field(node, "body")),
2156
+ ...children(node).flatMap(
2157
+ (clause) => clause.type === "finally_clause" ? blockStatements2(children(clause)[0] ?? null) : []
2164
2158
  )
2159
+ ]
2160
+ }),
2161
+ with_statement: (node) => ({
2162
+ kind: "block",
2163
+ body: blockStatements2(field(node, "body"))
2164
+ })
2165
+ };
2166
+ function statementOf(node) {
2167
+ const lower = STATEMENT_TYPES[node.type];
2168
+ return lower === void 0 ? { kind: "opaque" } : lower(node);
2169
+ }
2170
+ function expressionStatement(node) {
2171
+ const inner = children(node)[0];
2172
+ if (inner === void 0) {
2173
+ return { kind: "opaque" };
2174
+ }
2175
+ if (inner.type === "assignment") {
2176
+ return assignment(inner, null);
2177
+ }
2178
+ if (inner.type === "augmented_assignment") {
2179
+ const operator = field(inner, "operator");
2180
+ return assignment(
2181
+ inner,
2182
+ operator === null ? null : operator.text.slice(0, -1)
2165
2183
  );
2166
- if (pattern === void 0) {
2167
- continue;
2168
- }
2169
- effects.push(effectFor(pattern, chain, options));
2170
2184
  }
2171
- return effects;
2185
+ return { kind: "expression", value: inner };
2172
2186
  }
2173
-
2174
- // src/values/evaluator.ts
2175
- var import_resolution2 = require("@suss/resolution");
2176
- var import_values4 = require("@suss/values");
2177
-
2178
- // src/values/lowering.ts
2179
- var ROOT_TYPES = /* @__PURE__ */ new Set(["module", "function_definition", "lambda"]);
2180
- var LITERAL_TYPES = {
2181
- integer: (node) => ({ kind: "literal", value: Number(node.text) }),
2182
- float: (node) => ({ kind: "literal", value: Number(node.text) }),
2183
- true: () => ({ kind: "literal", value: true }),
2184
- false: () => ({ kind: "literal", value: false }),
2185
- none: () => ({ kind: "literal", value: null })
2186
- };
2187
- var WRITING_ROWS = /* @__PURE__ */ new Set(["append", "extend"]);
2188
- function readingMethodsOf(rows) {
2189
- return new Set(
2190
- rows.flatMap(
2191
- (row) => row.kind === "method" && !WRITING_ROWS.has(row.method) ? [row.method] : []
2192
- )
2187
+ function assignment(node, operator) {
2188
+ return fromFields(
2189
+ node,
2190
+ ["left", "right"],
2191
+ OPAQUE,
2192
+ (target, value) => {
2193
+ if (target.type === "pattern_list" || target.type === "tuple_pattern") {
2194
+ return {
2195
+ kind: "declare",
2196
+ bindings: patternNames(target).map((name) => ({ name, value: null }))
2197
+ };
2198
+ }
2199
+ return { kind: "assign", target, operator, value };
2200
+ }
2193
2201
  );
2194
2202
  }
2195
- function pythonLowering(options) {
2196
- const { context, originOf: originOf2, rows } = options;
2197
- const readingMethods = readingMethodsOf(rows);
2198
- const mutatedByRoot = /* @__PURE__ */ new Map();
2199
- return {
2200
- idOf: (node) => node.id,
2201
- expression: (node) => expressionOf(node, originOf2),
2202
- statement: statementOf,
2203
- siteOf,
2204
- functionOf,
2205
- writtenTo: (node) => context === null ? null : context.writtenTo(node),
2206
- callable: (node) => {
2207
- if (context === null || hasDictionarySplat(node)) {
2208
- return null;
2209
- }
2210
- return context.callable(node);
2211
- },
2212
- mutatedInNestedFunction: (root, name) => {
2213
- let mutated = mutatedByRoot.get(root.id);
2214
- if (mutated === void 0) {
2215
- mutated = namesMutatedInNestedFunctions(root, readingMethods);
2216
- mutatedByRoot.set(root.id, mutated);
2217
- }
2218
- return mutated.has(name);
2219
- },
2220
- freeNamesOf,
2221
- holeNameOf: placeholderName,
2222
- rows
2223
- };
2203
+ function patternNames(pattern) {
2204
+ return children(pattern).flatMap(
2205
+ (child) => child.type === "identifier" ? [child.text] : patternNames(child)
2206
+ );
2224
2207
  }
2225
- function placeholderName(node) {
2226
- const peeled = peelValue(node);
2227
- if (peeled.type === "identifier") {
2228
- return peeled.text;
2229
- }
2230
- if (peeled.type === "attribute") {
2231
- return field(peeled, "attribute")?.text ?? "param";
2208
+ function ifStatement(node) {
2209
+ return fromFields(
2210
+ node,
2211
+ ["condition", "consequence"],
2212
+ OPAQUE,
2213
+ (condition, consequence) => ({
2214
+ kind: "branch",
2215
+ condition,
2216
+ arms: [blockStatements2(consequence), otherArm(node)]
2217
+ })
2218
+ );
2219
+ }
2220
+ function otherArm(clause) {
2221
+ const statement = clause.type === "if_statement" ? clause : clause.parent;
2222
+ const clauses = statement === null ? [] : children(statement);
2223
+ const next = clauses.slice(clauses.findIndex((child) => child.id === clause.id) + 1).find(
2224
+ (child) => child.type === "elif_clause" || child.type === "else_clause"
2225
+ );
2226
+ if (next === void 0) {
2227
+ return [];
2232
2228
  }
2233
- return "param";
2229
+ return next.type === "elif_clause" ? [next] : blockStatements2(field(next, "body"));
2234
2230
  }
2235
- function peelValue(node) {
2236
- const wraps = node.type === "parenthesized_expression" || node.type === "await";
2237
- const inner = wraps ? children(node)[0] : void 0;
2238
- return inner === void 0 ? node : peelValue(inner);
2231
+ function loopStatement(node) {
2232
+ return { kind: "loop", body: blockStatements2(field(node, "body")) };
2239
2233
  }
2240
- var OPAQUE = { kind: "opaque" };
2241
- function fromFields(node, names, fallback2, build) {
2242
- const found = names.map((name) => field(node, name));
2243
- if (found.some((part) => part === null)) {
2244
- return fallback2;
2245
- }
2246
- return build(...found);
2234
+ function blockStatements2(block) {
2235
+ return block === null ? [] : children(block);
2247
2236
  }
2248
- function expressionOf(node, originOf2) {
2249
- const peeled = peelValue(node);
2250
- const literal = LITERAL_TYPES[peeled.type];
2251
- if (literal !== void 0) {
2252
- return literal(peeled);
2253
- }
2254
- if (peeled.type === "string") {
2255
- return stringExpression(peeled);
2256
- }
2257
- if (peeled.type === "concatenated_string") {
2258
- return {
2259
- kind: "template",
2260
- parts: children(peeled).map((part) => ({ expression: part }))
2261
- };
2262
- }
2263
- if (peeled.type === "identifier") {
2264
- return { kind: "name", text: peeled.text };
2265
- }
2266
- if (peeled.type === "attribute") {
2267
- return fromFields(
2268
- peeled,
2269
- ["object", "attribute"],
2270
- OPAQUE,
2271
- (object, name) => ({
2272
- kind: "member",
2273
- object,
2274
- name: name.text
2275
- })
2276
- );
2277
- }
2278
- if (peeled.type === "subscript") {
2279
- const object = field(peeled, "value");
2280
- const index = field(peeled, "subscript");
2281
- if (object === null || index === null || index.type === "slice") {
2282
- return { kind: "opaque" };
2237
+ function isRoot(node) {
2238
+ return ROOT_TYPES.has(node.type);
2239
+ }
2240
+ function isPathStatement(node, parent) {
2241
+ return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2242
+ }
2243
+ function siteOf(node) {
2244
+ const path6 = [];
2245
+ let current = node;
2246
+ for (; ; ) {
2247
+ const parent = current.parent;
2248
+ if (parent === null) {
2249
+ return null;
2283
2250
  }
2284
- return { kind: "element", object, index };
2251
+ if (isPathStatement(current, parent)) {
2252
+ path6.unshift(current);
2253
+ }
2254
+ if (isRoot(parent)) {
2255
+ return { root: parent, path: path6 };
2256
+ }
2257
+ current = parent;
2285
2258
  }
2286
- if (peeled.type === "list" || peeled.type === "tuple" || peeled.type === "expression_list") {
2287
- return { kind: "array", items: children(peeled).map(elementOf) };
2259
+ }
2260
+ function functionOf(node) {
2261
+ if (node.type === "module") {
2262
+ return { parameters: [], body: children(node) };
2288
2263
  }
2289
- if (peeled.type === "dictionary") {
2290
- return { kind: "record", fields: children(peeled).flatMap(fieldOf) };
2264
+ if (node.type === "lambda") {
2265
+ const body = field(node, "body");
2266
+ if (body === null) {
2267
+ return null;
2268
+ }
2269
+ return { parameters: parameterShapes(node), body: { expression: body } };
2291
2270
  }
2292
- if (peeled.type === "call") {
2293
- return callExpression(peeled, originOf2);
2271
+ if (node.type !== "function_definition") {
2272
+ return null;
2294
2273
  }
2295
- if (peeled.type === "binary_operator" || peeled.type === "boolean_operator") {
2296
- return fromFields(
2297
- peeled,
2298
- ["left", "operator", "right"],
2299
- OPAQUE,
2300
- (left, operator, right) => ({
2301
- kind: "operator",
2302
- operator: operator.text,
2303
- operands: [left, right]
2304
- })
2274
+ const parameters = parameterShapes(node);
2275
+ return {
2276
+ parameters: isMethod(node) ? parameters.slice(1) : parameters,
2277
+ body: blockStatements2(field(node, "body"))
2278
+ };
2279
+ }
2280
+ function isMethod(fn) {
2281
+ const owner = fn.parent?.type === "decorated_definition" ? fn.parent.parent : fn.parent;
2282
+ return owner?.type === "block" && owner.parent?.type === "class_definition";
2283
+ }
2284
+ function namesMutatedInNestedFunctions(root, readingMethods) {
2285
+ const mutated = /* @__PURE__ */ new Set();
2286
+ const visit = (node, nested) => {
2287
+ const name = writtenNameOf(node, readingMethods);
2288
+ if (nested && name !== null) {
2289
+ mutated.add(name);
2290
+ }
2291
+ for (const child of children(node)) {
2292
+ visit(child, nested || isRoot(child));
2293
+ }
2294
+ };
2295
+ for (const child of children(root)) {
2296
+ visit(child, isRoot(child));
2297
+ }
2298
+ return mutated;
2299
+ }
2300
+ function writtenNameOf(node, readingMethods) {
2301
+ if (node.type === "assignment" || node.type === "augmented_assignment") {
2302
+ const target = field(node, "left");
2303
+ return target?.type === "identifier" ? target.text : null;
2304
+ }
2305
+ if (node.type === "call") {
2306
+ const callee = field(node, "function");
2307
+ if (callee?.type !== "attribute") {
2308
+ return null;
2309
+ }
2310
+ const receiver = field(callee, "object");
2311
+ const method = field(callee, "attribute");
2312
+ if (receiver?.type === "identifier" && method !== null && !readingMethods.has(method.text)) {
2313
+ return receiver.text;
2314
+ }
2315
+ }
2316
+ return null;
2317
+ }
2318
+ var DECLARING_PARENTS = /* @__PURE__ */ new Set([
2319
+ "parameters",
2320
+ "default_parameter",
2321
+ "typed_parameter",
2322
+ "typed_default_parameter",
2323
+ "lambda_parameters",
2324
+ "pattern_list",
2325
+ "tuple_pattern",
2326
+ "for_statement"
2327
+ ]);
2328
+ function freeNamesOf(fn) {
2329
+ const declared = /* @__PURE__ */ new Set();
2330
+ const read = /* @__PURE__ */ new Set();
2331
+ const visit = (node) => {
2332
+ for (const child of children(node)) {
2333
+ if (child.type !== "identifier") {
2334
+ visit(child);
2335
+ continue;
2336
+ }
2337
+ if (node.type === "attribute" && field(node, "attribute")?.id === child.id) {
2338
+ continue;
2339
+ }
2340
+ if (node.type === "keyword_argument" && field(node, "name")?.id === child.id) {
2341
+ continue;
2342
+ }
2343
+ if (DECLARING_PARENTS.has(node.type) || node.type === "assignment" && field(node, "left")?.id === child.id) {
2344
+ declared.add(child.text);
2345
+ continue;
2346
+ }
2347
+ read.add(child.text);
2348
+ }
2349
+ };
2350
+ visit(fn);
2351
+ return [...read].filter((name) => !declared.has(name));
2352
+ }
2353
+
2354
+ // src/values/origin.ts
2355
+ function originOf(callee, module2) {
2356
+ const chain = memberChainOf(callee);
2357
+ if (chain === null) {
2358
+ return null;
2359
+ }
2360
+ const [rootName, ...rest] = chain;
2361
+ if (rootName === void 0) {
2362
+ return null;
2363
+ }
2364
+ const binding = resolveName(scopeAt(callee, module2), rootName);
2365
+ if (binding === null) {
2366
+ return rest.length === 0 ? { module: "builtins", name: rootName } : null;
2367
+ }
2368
+ const path6 = importedPathOf(binding, rootName);
2369
+ if (path6 === null) {
2370
+ return null;
2371
+ }
2372
+ const dotted = [...path6, ...rest];
2373
+ const name = dotted.pop();
2374
+ if (name === void 0 || dotted.length === 0) {
2375
+ return null;
2376
+ }
2377
+ return { module: dotted.join("."), name };
2378
+ }
2379
+ function importedPathOf(binding, localName) {
2380
+ if (binding.kind === "importFrom") {
2381
+ if (binding.relativeLevel > 0) {
2382
+ return null;
2383
+ }
2384
+ return [...binding.module.split("."), binding.importedName];
2385
+ }
2386
+ if (binding.kind !== "import") {
2387
+ return null;
2388
+ }
2389
+ const unaliasedDotted = binding.module !== localName && binding.module.startsWith(`${localName}.`);
2390
+ return unaliasedDotted ? [localName] : binding.module.split(".");
2391
+ }
2392
+ function memberChainOf(node) {
2393
+ const names = [];
2394
+ let current = peelValue(node);
2395
+ while (current.type === "attribute") {
2396
+ const object = field(current, "object");
2397
+ const attribute = field(current, "attribute");
2398
+ if (object === null || attribute === null) {
2399
+ return null;
2400
+ }
2401
+ names.unshift(attribute.text);
2402
+ current = peelValue(object);
2403
+ }
2404
+ if (current.type !== "identifier") {
2405
+ return null;
2406
+ }
2407
+ return [current.text, ...names];
2408
+ }
2409
+ function scopeAt(node, module2) {
2410
+ let current = node;
2411
+ while (current !== null) {
2412
+ const scope = module2.scopeFor.get(current.id);
2413
+ if (scope !== void 0) {
2414
+ return scope;
2415
+ }
2416
+ current = current.parent;
2417
+ }
2418
+ return module2.moduleScope;
2419
+ }
2420
+
2421
+ // src/values/rows.ts
2422
+ var import_values2 = require("@suss/values");
2423
+ var operatorRows = [
2424
+ {
2425
+ kind: "operator",
2426
+ operator: "+",
2427
+ arity: 2,
2428
+ apply: ([a, b]) => (0, import_values2.plus)((0, import_values2.operand)(a), (0, import_values2.operand)(b))
2429
+ },
2430
+ {
2431
+ kind: "operator",
2432
+ operator: "%",
2433
+ arity: 2,
2434
+ apply: ([a, b], contentOf) => (0, import_values2.percentFormatted)((0, import_values2.operand)(a), (0, import_values2.formatArguments)(contentOf((0, import_values2.operand)(b))))
2435
+ },
2436
+ {
2437
+ kind: "operator",
2438
+ operator: "==",
2439
+ arity: 2,
2440
+ apply: ([a, b]) => (0, import_values2.equals)((0, import_values2.operand)(a), (0, import_values2.operand)(b))
2441
+ },
2442
+ {
2443
+ kind: "operator",
2444
+ operator: "!=",
2445
+ arity: 2,
2446
+ apply: ([a, b]) => (0, import_values2.negated)((0, import_values2.equals)((0, import_values2.operand)(a), (0, import_values2.operand)(b)))
2447
+ },
2448
+ {
2449
+ kind: "operator",
2450
+ operator: "is",
2451
+ arity: 2,
2452
+ apply: ([a, b]) => (0, import_values2.equals)((0, import_values2.operand)(a), (0, import_values2.operand)(b))
2453
+ },
2454
+ {
2455
+ kind: "operator",
2456
+ operator: "is not",
2457
+ arity: 2,
2458
+ apply: ([a, b]) => (0, import_values2.negated)((0, import_values2.equals)((0, import_values2.operand)(a), (0, import_values2.operand)(b)))
2459
+ },
2460
+ {
2461
+ kind: "operator",
2462
+ operator: "not",
2463
+ arity: 1,
2464
+ apply: ([a]) => (0, import_values2.negated)((0, import_values2.operand)(a))
2465
+ },
2466
+ {
2467
+ kind: "operator",
2468
+ operator: "or",
2469
+ arity: 2,
2470
+ apply: ([a, b]) => (0, import_values2.readableFallback)((0, import_values2.operand)(a), (0, import_values2.operand)(b), import_values2.truthOf)
2471
+ },
2472
+ {
2473
+ kind: "operator",
2474
+ operator: "and",
2475
+ arity: 2,
2476
+ apply: ([a, b]) => (0, import_values2.fallback)((0, import_values2.operand)(a), (0, import_values2.operand)(b), (left) => {
2477
+ const truth = (0, import_values2.truthOf)(left);
2478
+ return truth === null ? null : !truth;
2479
+ })
2480
+ }
2481
+ ];
2482
+ var stringRows = [
2483
+ {
2484
+ kind: "method",
2485
+ method: "join",
2486
+ on: "string",
2487
+ apply: ({ receiver, args, contentOf }) => ({
2488
+ result: (0, import_values2.joined)(contentOf((0, import_values2.operand)(args[0])), (0, import_values2.operand)(receiver))
2489
+ })
2490
+ },
2491
+ {
2492
+ kind: "method",
2493
+ method: "format",
2494
+ on: "string",
2495
+ apply: ({ receiver, args }) => ({
2496
+ result: braceFormatted((0, import_values2.operand)(receiver), args)
2497
+ })
2498
+ },
2499
+ {
2500
+ kind: "method",
2501
+ method: "strip",
2502
+ on: "string",
2503
+ apply: ({ receiver, args }) => ({
2504
+ result: args.length === 0 ? (0, import_values2.stripped)((0, import_values2.operand)(receiver)) : (0, import_values2.hole)("value")
2505
+ })
2506
+ },
2507
+ {
2508
+ kind: "method",
2509
+ method: "rstrip",
2510
+ on: "string",
2511
+ apply: ({ receiver, args }) => ({
2512
+ result: args.length === 0 ? (0, import_values2.stripped)((0, import_values2.operand)(receiver), "end") : (0, import_values2.hole)("value")
2513
+ })
2514
+ },
2515
+ {
2516
+ kind: "method",
2517
+ method: "lstrip",
2518
+ on: "string",
2519
+ apply: ({ receiver, args }) => ({
2520
+ result: args.length === 0 ? (0, import_values2.stripped)((0, import_values2.operand)(receiver), "start") : (0, import_values2.hole)("value")
2521
+ })
2522
+ }
2523
+ ];
2524
+ var sequenceRows = ["sequence", "unbounded"].flatMap((on) => [
2525
+ {
2526
+ kind: "method",
2527
+ method: "append",
2528
+ on,
2529
+ apply: ({ receiver, args }) => ({
2530
+ result: (0, import_values2.constant)(null),
2531
+ receiver: (0, import_values2.appended)((0, import_values2.operand)(receiver), args)
2532
+ })
2533
+ },
2534
+ {
2535
+ kind: "method",
2536
+ method: "extend",
2537
+ on,
2538
+ apply: ({ receiver, args, contentOf }) => ({
2539
+ result: (0, import_values2.constant)(null),
2540
+ receiver: (0, import_values2.extended)((0, import_values2.operand)(receiver), contentOf((0, import_values2.operand)(args[0])))
2541
+ })
2542
+ }
2543
+ ]);
2544
+ var calleeRows = [
2545
+ {
2546
+ kind: "callee",
2547
+ origin: { module: "builtins", name: "str" },
2548
+ apply: ({ args }) => ({ result: (0, import_values2.concat)([(0, import_values2.operand)(args[0])]) })
2549
+ },
2550
+ {
2551
+ kind: "callee",
2552
+ origin: { module: "os.path", name: "join" },
2553
+ apply: ({ args }) => ({ result: (0, import_values2.joinedPath)(args) })
2554
+ },
2555
+ {
2556
+ kind: "callee",
2557
+ origin: { module: "os", name: "getenv" },
2558
+ apply: ({ args }) => ({ result: (0, import_values2.environmentRead)(args) })
2559
+ },
2560
+ {
2561
+ kind: "callee",
2562
+ origin: { module: "os.environ", name: "get" },
2563
+ apply: ({ args }) => ({ result: (0, import_values2.environmentRead)(args) })
2564
+ }
2565
+ ];
2566
+ var BRACE_PLACEHOLDER = /\{([^{}]*)\}/g;
2567
+ function braceFormatted(receiver, args) {
2568
+ const template = (0, import_values2.literalOf)(receiver);
2569
+ if (template === null) {
2570
+ return (0, import_values2.hole)("value");
2571
+ }
2572
+ const parts = [];
2573
+ let last = 0;
2574
+ let position = 0;
2575
+ for (const match of template.matchAll(BRACE_PLACEHOLDER)) {
2576
+ parts.push((0, import_values2.text)(template.slice(last, match.index)));
2577
+ const argument = args[position];
2578
+ const placeholder = match[1] ?? "";
2579
+ parts.push(
2580
+ argument === void 0 ? (0, import_values2.hole)(
2581
+ placeholder === "" ? "value" : placeholder.split(/[!:]/)[0] ?? "value"
2582
+ ) : (0, import_values2.concat)([argument])
2305
2583
  );
2584
+ position += 1;
2585
+ last = match.index + match[0].length;
2306
2586
  }
2307
- if (peeled.type === "comparison_operator") {
2308
- return comparisonExpression(peeled);
2587
+ parts.push((0, import_values2.text)(template.slice(last)));
2588
+ return (0, import_values2.concat)(parts);
2589
+ }
2590
+ var pythonRows = [
2591
+ ...operatorRows,
2592
+ ...stringRows,
2593
+ ...sequenceRows,
2594
+ ...calleeRows
2595
+ ];
2596
+
2597
+ // src/values/evaluator.ts
2598
+ var evaluators = /* @__PURE__ */ new WeakMap();
2599
+ var withoutFacts = /* @__PURE__ */ new WeakMap();
2600
+ var modulesByTree = /* @__PURE__ */ new WeakMap();
2601
+ function bindEvaluator(db, nodes) {
2602
+ for (const entry of nodes.files) {
2603
+ modulesByTree.set(entry.root.tree, entry.module);
2309
2604
  }
2310
- if (peeled.type === "not_operator") {
2311
- return fromFields(
2312
- peeled,
2313
- ["argument"],
2314
- OPAQUE,
2315
- (argument) => ({
2316
- kind: "operator",
2317
- operator: "not",
2318
- operands: [argument]
2605
+ evaluators.set(
2606
+ db,
2607
+ new import_values3.Evaluator(
2608
+ pythonLowering({
2609
+ context: contextOver(db, nodes),
2610
+ originOf: calleeOrigin,
2611
+ rows: pythonRows
2612
+ })
2613
+ )
2614
+ );
2615
+ }
2616
+ function evaluatedValue(node, db) {
2617
+ return (0, import_values3.force)(evaluatorFor(node, db).evaluate(node));
2618
+ }
2619
+ function stringValueOf(node, db) {
2620
+ return (0, import_values3.literalOf)(evaluatedValue(node, db));
2621
+ }
2622
+ function evaluatorFor(node, db) {
2623
+ const bound = db === void 0 ? void 0 : evaluators.get(db);
2624
+ if (bound !== void 0) {
2625
+ return bound;
2626
+ }
2627
+ const tree = node.tree;
2628
+ let local = withoutFacts.get(tree);
2629
+ if (local === void 0) {
2630
+ local = new import_values3.Evaluator(
2631
+ pythonLowering({
2632
+ context: null,
2633
+ originOf: calleeOrigin,
2634
+ rows: pythonRows
2319
2635
  })
2320
2636
  );
2637
+ withoutFacts.set(tree, local);
2321
2638
  }
2322
- if (peeled.type === "conditional_expression") {
2323
- const [whenTrue, condition, whenFalse] = children(peeled);
2324
- if (whenTrue === void 0 || condition === void 0 || whenFalse === void 0) {
2325
- return { kind: "opaque" };
2326
- }
2327
- return { kind: "conditional", condition, whenTrue, whenFalse };
2328
- }
2329
- if (peeled.type === "lambda") {
2330
- return { kind: "function", node: peeled };
2639
+ return local;
2640
+ }
2641
+ function moduleOf(node) {
2642
+ const tree = node.tree;
2643
+ let module2 = modulesByTree.get(tree);
2644
+ if (module2 === void 0) {
2645
+ module2 = bindModule(tree.rootNode);
2646
+ modulesByTree.set(tree, module2);
2331
2647
  }
2332
- return { kind: "opaque" };
2648
+ return module2;
2333
2649
  }
2334
- function stringExpression(node) {
2335
- const plain = stringLiteralValue(node);
2336
- if (plain !== null) {
2337
- return { kind: "literal", value: plain };
2650
+ function calleeOrigin(callee) {
2651
+ return originOf(callee, moduleOf(callee));
2652
+ }
2653
+ function contextOver(db, nodes) {
2654
+ const filesByRoot = /* @__PURE__ */ new Map();
2655
+ const rootsByFile = /* @__PURE__ */ new Map();
2656
+ for (const entry of nodes.files) {
2657
+ filesByRoot.set(entry.root.id, entry);
2658
+ rootsByFile.set(entry.file, entry.root);
2338
2659
  }
2339
- const parts = [];
2340
- for (const child of children(node)) {
2341
- if (child.type === "string_content") {
2342
- parts.push({ text: child.text });
2343
- continue;
2660
+ const keyOf2 = (node) => {
2661
+ const entry = filesByRoot.get(node.tree.rootNode.id);
2662
+ if (entry === void 0) {
2663
+ return null;
2344
2664
  }
2345
- if (child.type === "interpolation") {
2346
- const expression = field(child, "expression") ?? children(child)[0];
2347
- if (expression !== void 0) {
2348
- parts.push({ expression });
2665
+ return readKey(entry.file, node, enclosingFunction(node));
2666
+ };
2667
+ return {
2668
+ writtenTo: (node) => {
2669
+ const key = keyOf2(node);
2670
+ if (key === null) {
2671
+ return null;
2672
+ }
2673
+ const answer = writtenValueOf(db, key);
2674
+ return answer === null ? null : (0, import_resolution2.nodeOfKey)(rootsByFile, answer);
2675
+ },
2676
+ callable: (call) => {
2677
+ const callee = field(call, "function");
2678
+ const key = callee === null ? null : keyOf2(callee);
2679
+ if (key === null) {
2680
+ return null;
2349
2681
  }
2682
+ resolveCalls(db, [key]);
2683
+ const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2684
+ const settled = resolved.length === 1 ? resolved[0] : void 0;
2685
+ return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
2350
2686
  }
2687
+ };
2688
+ }
2689
+
2690
+ // src/paths/raisedResponses.ts
2691
+ var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
2692
+ function raisedResponses(body, options) {
2693
+ if (body === null || options.calls.length === 0) {
2694
+ return [];
2351
2695
  }
2352
- return { kind: "template", parts };
2696
+ const found = [];
2697
+ const walk = (node) => {
2698
+ for (const child of node.namedChildren) {
2699
+ if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
2700
+ continue;
2701
+ }
2702
+ const response = raisedResponseOf(child, options);
2703
+ if (response !== null) {
2704
+ found.push(response);
2705
+ }
2706
+ walk(child);
2707
+ }
2708
+ };
2709
+ walk(body);
2710
+ return found;
2353
2711
  }
2354
- function comparisonExpression(node) {
2355
- const operands = children(node);
2356
- const operators = node.children.filter(
2357
- (child) => child !== null && !child.isNamed
2358
- );
2359
- const operator = operators[0];
2360
- if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2361
- return { kind: "opaque" };
2712
+ var ENDING_EXPRESSION = {
2713
+ raise_statement: (statement) => statement.namedChildren[0] ?? null,
2714
+ expression_statement: (statement) => {
2715
+ const only = statement.namedChildren[0];
2716
+ return only?.type === "call" ? only : null;
2717
+ }
2718
+ };
2719
+ function raisedResponseOf(statement, options) {
2720
+ const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
2721
+ if (expression === null) {
2722
+ return null;
2723
+ }
2724
+ const thrownByCall = statement.type !== "raise_statement";
2725
+ const call = expression.type === "call" ? expression : null;
2726
+ const callee = calleeOf(expression, call);
2727
+ const declared = declaredCallFor(callee, options);
2728
+ if (declared === void 0 && thrownByCall) {
2729
+ return null;
2362
2730
  }
2363
2731
  return {
2364
- kind: "operator",
2365
- operator: operator.text,
2366
- operands: [operands[0], operands[1]]
2732
+ statement,
2733
+ thrownByCall,
2734
+ terminal: terminalOf(statement, callee, call, declared, options.facts)
2367
2735
  };
2368
2736
  }
2369
- function callExpression(node, originOf2) {
2370
- return fromFields(
2371
- node,
2372
- ["function", "arguments"],
2373
- OPAQUE,
2374
- (callee, argumentList) => {
2375
- const peeledCallee = peelValue(callee);
2376
- const args = argumentList.type === "argument_list" ? argumentsOf(argumentList) : [];
2377
- const isMethod2 = peeledCallee.type === "attribute";
2378
- return {
2379
- kind: "call",
2380
- callee: {
2381
- receiver: isMethod2 ? field(peeledCallee, "object") : null,
2382
- name: calleeName(peeledCallee),
2383
- origin: () => originOf2(peeledCallee)
2384
- },
2385
- args,
2386
- constructs: false
2387
- };
2388
- }
2389
- );
2737
+ function calleeOf(expression, call) {
2738
+ return call === null ? expression : field(call, "function") ?? expression;
2390
2739
  }
2391
- function calleeName(callee) {
2392
- if (callee.type === "identifier") {
2393
- return callee.text;
2394
- }
2395
- if (callee.type === "attribute") {
2396
- return field(callee, "attribute")?.text ?? null;
2740
+ function declaredCallFor(callee, options) {
2741
+ const origin = originOf(callee, options.module);
2742
+ if (origin === null) {
2743
+ return void 0;
2397
2744
  }
2398
- return null;
2745
+ const qualified = `${origin.module}.${origin.name}`;
2746
+ return options.calls.find((call) => call.callee === qualified);
2399
2747
  }
2400
- function argumentsOf(argumentList) {
2401
- return children(argumentList).flatMap((argument) => {
2402
- if (argument.type === "dictionary_splat") {
2403
- return [];
2404
- }
2405
- if (argument.type === "keyword_argument") {
2406
- const name = field(argument, "name");
2407
- const value = field(argument, "value");
2408
- return name === null || value === null ? [] : [{ kind: "named", name: name.text, node: value }];
2409
- }
2410
- return [elementOf(argument)];
2411
- });
2748
+ function terminalOf(statement, callee, call, declared, facts) {
2749
+ return {
2750
+ kind: "throw",
2751
+ statusCode: declared === void 0 ? null : statusOf(declared, call, facts),
2752
+ body: null,
2753
+ exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
2754
+ message: call === null ? null : writtenMessageOf(call),
2755
+ ...declared === void 0 ? {} : { producesResponse: true },
2756
+ component: null,
2757
+ renderTree: null,
2758
+ delegateTarget: null,
2759
+ emitEvent: null,
2760
+ location: rangeOf(statement)
2761
+ };
2412
2762
  }
2413
- function hasDictionarySplat(call) {
2414
- const argumentList = field(call, "arguments");
2415
- if (argumentList === null) {
2416
- return false;
2763
+ function lastSegmentOf(dotted) {
2764
+ const segment = dotted.split(".").pop();
2765
+ return segment === void 0 || segment === "" ? null : segment;
2766
+ }
2767
+ function statusOf(declared, call, facts) {
2768
+ const written = call === null ? null : statusArgumentOf(declared, call);
2769
+ if (written === null) {
2770
+ return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
2417
2771
  }
2418
- return children(argumentList).some(
2419
- (argument) => argument.type === "dictionary_splat"
2772
+ const value = (0, import_values5.constantOf)(evaluatedValue(written, facts));
2773
+ if (typeof value === "number") {
2774
+ return { type: "literal", value };
2775
+ }
2776
+ return { type: "dynamic", sourceText: written.text };
2777
+ }
2778
+ function argumentsOf2(call) {
2779
+ return (field(call, "arguments")?.namedChildren ?? []).filter(
2780
+ (child) => child !== null
2420
2781
  );
2421
2782
  }
2422
- function elementOf(node) {
2423
- if (node.type === "list_splat") {
2424
- const inner = children(node)[0];
2425
- return inner === void 0 ? { kind: "value", node } : { kind: "spread", node: inner };
2783
+ function statusArgumentOf(declared, call) {
2784
+ const args = argumentsOf2(call);
2785
+ const keyed = args.find(
2786
+ (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
2787
+ );
2788
+ if (keyed !== void 0) {
2789
+ return field(keyed, "value");
2426
2790
  }
2427
- return { kind: "value", node };
2428
- }
2429
- function fieldOf(node) {
2430
- if (node.type === "dictionary_splat") {
2431
- const inner = children(node)[0];
2432
- return inner === void 0 ? [] : [{ kind: "spread", node: inner }];
2791
+ if (declared.statusArgument === void 0) {
2792
+ return null;
2433
2793
  }
2434
- if (node.type !== "pair") {
2435
- return [];
2794
+ const positional = args.filter((arg) => arg.type !== "keyword_argument");
2795
+ return positional[declared.statusArgument] ?? null;
2796
+ }
2797
+ function writtenMessageOf(call) {
2798
+ for (const arg of argumentsOf2(call)) {
2799
+ const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
2800
+ const literal = value === null ? null : stringLiteralValue(value);
2801
+ if (literal !== null) {
2802
+ return literal;
2803
+ }
2436
2804
  }
2437
- return fromFields(node, ["key", "value"], [], (key, value) => {
2438
- const name = stringLiteralValue(key);
2439
- return name === null ? [{ kind: "computed", name: key, value }] : [{ kind: "field", name, value }];
2440
- });
2805
+ return null;
2441
2806
  }
2442
- var STATEMENT_TYPES = {
2443
- expression_statement: expressionStatement,
2444
- if_statement: ifStatement,
2445
- elif_clause: ifStatement,
2446
- for_statement: loopStatement,
2447
- while_statement: loopStatement,
2448
- return_statement: (node) => ({
2449
- kind: "return",
2450
- value: children(node)[0] ?? null
2451
- }),
2452
- try_statement: (node) => ({
2453
- kind: "block",
2454
- body: [
2455
- ...blockStatements2(field(node, "body")),
2456
- ...children(node).flatMap(
2457
- (clause) => clause.type === "finally_clause" ? blockStatements2(children(clause)[0] ?? null) : []
2458
- )
2459
- ]
2460
- }),
2461
- with_statement: (node) => ({
2462
- kind: "block",
2463
- body: blockStatements2(field(node, "body"))
2464
- })
2807
+
2808
+ // src/paths/returnedShape.ts
2809
+ var MAX_DEPTH = 6;
2810
+ var SCALARS = {
2811
+ none: { type: "null" },
2812
+ true: { type: "boolean" },
2813
+ false: { type: "boolean" }
2465
2814
  };
2466
- function statementOf(node) {
2467
- const lower = STATEMENT_TYPES[node.type];
2468
- return lower === void 0 ? { kind: "opaque" } : lower(node);
2815
+ function children2(node) {
2816
+ return node.namedChildren.filter((child) => child !== null);
2469
2817
  }
2470
- function expressionStatement(node) {
2471
- const inner = children(node)[0];
2472
- if (inner === void 0) {
2473
- return { kind: "opaque" };
2818
+ function recordShape(node, depth) {
2819
+ const properties = {};
2820
+ let everyKeyWritten = true;
2821
+ for (const pair of children2(node)) {
2822
+ if (pair.type !== "pair") {
2823
+ everyKeyWritten = false;
2824
+ continue;
2825
+ }
2826
+ const key = field(pair, "key");
2827
+ const value = field(pair, "value");
2828
+ if (key === null || value === null || key.type !== "string") {
2829
+ everyKeyWritten = false;
2830
+ continue;
2831
+ }
2832
+ properties[key.text.slice(1, -1)] = shapeOfReturned(value, depth + 1);
2474
2833
  }
2475
- if (inner.type === "assignment") {
2476
- return assignment(inner, null);
2834
+ return everyKeyWritten && Object.keys(properties).length > 0 ? { type: "record", properties } : { type: "dictionary", values: { type: "unknown" } };
2835
+ }
2836
+ function shapeOfReturned(node, depth = 0) {
2837
+ if (depth > MAX_DEPTH) {
2838
+ return { type: "unknown" };
2839
+ }
2840
+ const scalar = SCALARS[node.type];
2841
+ if (scalar !== void 0) {
2842
+ return scalar;
2843
+ }
2844
+ if (node.type === "string") {
2845
+ return { type: "literal", value: node.text.slice(1, -1) };
2846
+ }
2847
+ if (node.type === "integer") {
2848
+ const value = Number.parseInt(node.text, 10);
2849
+ return Number.isNaN(value) ? { type: "integer" } : { type: "literal", value };
2850
+ }
2851
+ if (node.type === "float") {
2852
+ return { type: "number" };
2853
+ }
2854
+ if (node.type === "dictionary") {
2855
+ return recordShape(node, depth);
2856
+ }
2857
+ if (node.type === "list" || node.type === "tuple" || node.type === "set") {
2858
+ const first = children2(node)[0];
2859
+ return {
2860
+ type: "array",
2861
+ items: first === void 0 ? { type: "unknown" } : shapeOfReturned(first, depth + 1)
2862
+ };
2477
2863
  }
2478
- if (inner.type === "augmented_assignment") {
2479
- const operator = field(inner, "operator");
2480
- return assignment(
2481
- inner,
2482
- operator === null ? null : operator.text.slice(0, -1)
2483
- );
2864
+ if (node.type === "identifier" || node.type === "attribute") {
2865
+ return { type: "ref", name: node.text };
2484
2866
  }
2485
- return { kind: "expression", value: inner };
2486
- }
2487
- function assignment(node, operator) {
2488
- return fromFields(
2489
- node,
2490
- ["left", "right"],
2491
- OPAQUE,
2492
- (target, value) => {
2493
- if (target.type === "pattern_list" || target.type === "tuple_pattern") {
2494
- return {
2495
- kind: "declare",
2496
- bindings: patternNames(target).map((name) => ({ name, value: null }))
2497
- };
2498
- }
2499
- return { kind: "assign", target, operator, value };
2500
- }
2501
- );
2502
- }
2503
- function patternNames(pattern) {
2504
- return children(pattern).flatMap(
2505
- (child) => child.type === "identifier" ? [child.text] : patternNames(child)
2506
- );
2867
+ return { type: "unknown" };
2507
2868
  }
2508
- function ifStatement(node) {
2509
- return fromFields(
2510
- node,
2511
- ["condition", "consequence"],
2512
- OPAQUE,
2513
- (condition, consequence) => ({
2514
- kind: "branch",
2515
- condition,
2516
- arms: [blockStatements2(consequence), otherArm(node)]
2517
- })
2518
- );
2869
+ function returnedBodyShape(statement) {
2870
+ const returned = statement.namedChildren[0];
2871
+ if (returned == null) {
2872
+ return null;
2873
+ }
2874
+ const value = returned.type === "expression_list" ? children2(returned)[0] : returned;
2875
+ if (value === void 0) {
2876
+ return null;
2877
+ }
2878
+ const shape = shapeOfReturned(value);
2879
+ return shape.type === "unknown" ? null : shape;
2519
2880
  }
2520
- function otherArm(clause) {
2521
- const statement = clause.type === "if_statement" ? clause : clause.parent;
2522
- const clauses = statement === null ? [] : children(statement);
2523
- const next = clauses.slice(clauses.findIndex((child) => child.id === clause.id) + 1).find(
2524
- (child) => child.type === "elif_clause" || child.type === "else_clause"
2525
- );
2526
- if (next === void 0) {
2881
+
2882
+ // src/rawSql.ts
2883
+ var import_ir_core = require("@suss/ir-core");
2884
+ var import_sql = require("@suss/sql");
2885
+ function rawSqlEffects(calls, options) {
2886
+ if (options.patterns.length === 0) {
2527
2887
  return [];
2528
2888
  }
2529
- return next.type === "elif_clause" ? [next] : blockStatements2(field(next, "body"));
2530
- }
2531
- function loopStatement(node) {
2532
- return { kind: "loop", body: blockStatements2(field(node, "body")) };
2533
- }
2534
- function blockStatements2(block) {
2535
- return block === null ? [] : children(block);
2536
- }
2537
- function isRoot(node) {
2538
- return ROOT_TYPES.has(node.type);
2539
- }
2540
- function isPathStatement(node, parent) {
2541
- return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2542
- }
2543
- function siteOf(node) {
2544
- const path6 = [];
2545
- let current = node;
2546
- for (; ; ) {
2547
- const parent = current.parent;
2548
- if (parent === null) {
2549
- return null;
2889
+ const effects = [];
2890
+ for (const call of calls) {
2891
+ const pattern = patternFor(call, options);
2892
+ if (pattern === void 0) {
2893
+ continue;
2550
2894
  }
2551
- if (isPathStatement(current, parent)) {
2552
- path6.unshift(current);
2895
+ const statement = statementIn(call);
2896
+ if (statement === null) {
2897
+ continue;
2553
2898
  }
2554
- if (isRoot(parent)) {
2555
- return { root: parent, path: path6 };
2899
+ for (const access of (0, import_sql.readSqlAccess)(statement, {
2900
+ dialect: pattern.storageSystem
2901
+ })) {
2902
+ effects.push({
2903
+ type: "interaction",
2904
+ binding: (0, import_ir_core.storageBinding)({
2905
+ recognition: `python-${pattern.module}`,
2906
+ storageSystem: pattern.storageSystem,
2907
+ scope: pattern.module,
2908
+ container: access.table
2909
+ }),
2910
+ callee: field(call, "function")?.text ?? "",
2911
+ interaction: {
2912
+ class: "storage-access",
2913
+ kind: access.kind,
2914
+ fields: access.fields,
2915
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
2916
+ operation: field(call, "function")?.text ?? ""
2917
+ }
2918
+ });
2556
2919
  }
2557
- current = parent;
2558
2920
  }
2921
+ return effects;
2559
2922
  }
2560
- function functionOf(node) {
2561
- if (node.type === "module") {
2562
- return { parameters: [], body: children(node) };
2923
+ function patternFor(call, options) {
2924
+ const callee = field(call, "function");
2925
+ if (callee === null || callee.type !== "identifier") {
2926
+ return void 0;
2563
2927
  }
2564
- if (node.type === "lambda") {
2565
- const body = field(node, "body");
2566
- if (body === null) {
2567
- return null;
2568
- }
2569
- return { parameters: parameterShapes(node), body: { expression: body } };
2928
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
2929
+ if (from === void 0) {
2930
+ return void 0;
2570
2931
  }
2571
- if (node.type !== "function_definition") {
2932
+ const module2 = String(from[1]);
2933
+ return options.patterns.find(
2934
+ (pattern) => pattern.module === module2 && pattern.functions.includes(callee.text)
2935
+ );
2936
+ }
2937
+ function statementIn(call) {
2938
+ const args = field(call, "arguments");
2939
+ const first = args?.namedChildren.find((child) => child !== null) ?? null;
2940
+ if (first === null || first.type !== "string") {
2572
2941
  return null;
2573
2942
  }
2574
- const parameters = parameterShapes(node);
2575
- return {
2576
- parameters: isMethod(node) ? parameters.slice(1) : parameters,
2577
- body: blockStatements2(field(node, "body"))
2578
- };
2579
- }
2580
- function isMethod(fn) {
2581
- const owner = fn.parent?.type === "decorated_definition" ? fn.parent.parent : fn.parent;
2582
- return owner?.type === "block" && owner.parent?.type === "class_definition";
2583
- }
2584
- function namesMutatedInNestedFunctions(root, readingMethods) {
2585
- const mutated = /* @__PURE__ */ new Set();
2586
- const visit = (node, nested) => {
2587
- const name = writtenNameOf(node, readingMethods);
2588
- if (nested && name !== null) {
2589
- mutated.add(name);
2943
+ const parts = [""];
2944
+ for (const child of first.namedChildren) {
2945
+ if (child === null) {
2946
+ continue;
2590
2947
  }
2591
- for (const child of children(node)) {
2592
- visit(child, nested || isRoot(child));
2948
+ if (child.type === "string_content") {
2949
+ parts[parts.length - 1] += stringContentValue(child);
2950
+ continue;
2951
+ }
2952
+ if (child.type === "interpolation") {
2953
+ parts.push("");
2593
2954
  }
2594
- };
2595
- for (const child of children(root)) {
2596
- visit(child, isRoot(child));
2597
2955
  }
2598
- return mutated;
2956
+ const statement = (0, import_sql.sqlFromParts)(parts);
2957
+ return statement.trim() === "" ? null : statement;
2599
2958
  }
2600
- function writtenNameOf(node, readingMethods) {
2601
- if (node.type === "assignment" || node.type === "augmented_assignment") {
2602
- const target = field(node, "left");
2603
- return target?.type === "identifier" ? target.text : null;
2959
+
2960
+ // src/storage.ts
2961
+ var import_ir_core2 = require("@suss/ir-core");
2962
+ function receiverCall(call) {
2963
+ const callee = field(call, "function");
2964
+ if (callee === null || callee.type !== "attribute") {
2965
+ return null;
2604
2966
  }
2605
- if (node.type === "call") {
2606
- const callee = field(node, "function");
2607
- if (callee?.type !== "attribute") {
2608
- return null;
2609
- }
2610
- const receiver = field(callee, "object");
2611
- const method = field(callee, "attribute");
2612
- if (receiver?.type === "identifier" && method !== null && !readingMethods.has(method.text)) {
2613
- return receiver.text;
2614
- }
2967
+ const object = field(callee, "object");
2968
+ return object !== null && object.type === "call" ? object : null;
2969
+ }
2970
+ function methodName(call) {
2971
+ const callee = field(call, "function");
2972
+ if (callee === null || callee.type !== "attribute") {
2973
+ return callee?.text ?? "";
2615
2974
  }
2616
- return null;
2975
+ return field(callee, "attribute")?.text ?? "";
2617
2976
  }
2618
- var DECLARING_PARENTS = /* @__PURE__ */ new Set([
2619
- "parameters",
2620
- "default_parameter",
2621
- "typed_parameter",
2622
- "typed_default_parameter",
2623
- "lambda_parameters",
2624
- "pattern_list",
2625
- "tuple_pattern",
2626
- "for_statement"
2627
- ]);
2628
- function freeNamesOf(fn) {
2629
- const declared = /* @__PURE__ */ new Set();
2630
- const read = /* @__PURE__ */ new Set();
2631
- const visit = (node) => {
2632
- for (const child of children(node)) {
2633
- if (child.type !== "identifier") {
2634
- visit(child);
2635
- continue;
2636
- }
2637
- if (node.type === "attribute" && field(node, "attribute")?.id === child.id) {
2638
- continue;
2639
- }
2640
- if (node.type === "keyword_argument" && field(node, "name")?.id === child.id) {
2641
- continue;
2642
- }
2643
- if (DECLARING_PARENTS.has(node.type) || node.type === "assignment" && field(node, "left")?.id === child.id) {
2644
- declared.add(child.text);
2645
- continue;
2646
- }
2647
- read.add(child.text);
2977
+ function subjectOf(call) {
2978
+ const callee = field(call, "function");
2979
+ if (callee === null || callee.type !== "attribute") {
2980
+ return callee?.text ?? "";
2981
+ }
2982
+ return field(callee, "object")?.text ?? "";
2983
+ }
2984
+ function rootOf(call) {
2985
+ const receiver = receiverCall(call);
2986
+ return receiver === null ? call : rootOf(receiver);
2987
+ }
2988
+ function chainsIn(calls) {
2989
+ const endsWith = /* @__PURE__ */ new Map();
2990
+ for (const call of calls) {
2991
+ const receiver = receiverCall(call);
2992
+ if (receiver === null) {
2993
+ continue;
2648
2994
  }
2649
- };
2650
- visit(fn);
2651
- return [...read].filter((name) => !declared.has(name));
2995
+ const start = rootOf(receiver);
2996
+ const kept = endsWith.get(start.id);
2997
+ if (kept === void 0 || call.endIndex > kept.endIndex) {
2998
+ endsWith.set(start.id, call);
2999
+ }
3000
+ }
3001
+ const chains = [];
3002
+ for (const call of calls) {
3003
+ if (receiverCall(call) !== null) {
3004
+ continue;
3005
+ }
3006
+ const last = endsWith.get(call.id) ?? call;
3007
+ chains.push({
3008
+ root: call,
3009
+ last,
3010
+ subject: subjectOf(call),
3011
+ operation: methodName(last),
3012
+ between: calls.filter(
3013
+ (other) => other !== call && other !== last && other.startIndex >= call.startIndex && other.endIndex <= last.endIndex
3014
+ )
3015
+ });
3016
+ }
3017
+ return chains;
2652
3018
  }
2653
-
2654
- // src/values/origin.ts
2655
- function originOf(callee, module2) {
2656
- const chain = memberChainOf(callee);
2657
- if (chain === null) {
2658
- return null;
3019
+ function importedQueryFunction(options, chain) {
3020
+ const callee = field(chain.root, "function");
3021
+ if (callee === null || callee.type !== "identifier") {
3022
+ return void 0;
2659
3023
  }
2660
- const [rootName, ...rest] = chain;
2661
- if (rootName === void 0) {
3024
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
3025
+ if (from === void 0) {
3026
+ return void 0;
3027
+ }
3028
+ return options.patterns.find(
3029
+ (pattern) => String(from[1]) === pattern.module && (pattern.queryFunctions ?? []).includes(String(from[2]))
3030
+ );
3031
+ }
3032
+ function receiverName(chain) {
3033
+ const callee = field(chain.root, "function");
3034
+ if (callee === null || callee.type !== "attribute") {
2662
3035
  return null;
2663
3036
  }
2664
- const binding = resolveName(scopeAt(callee, module2), rootName);
2665
- if (binding === null) {
2666
- return rest.length === 0 ? { module: "builtins", name: rootName } : null;
3037
+ const object = field(callee, "object");
3038
+ return object !== null && object.type === "identifier" ? object.text : null;
3039
+ }
3040
+ function typeNameOf(annotation) {
3041
+ if (annotation.type === "type" && annotation.namedChildren[0]) {
3042
+ return typeNameOf(annotation.namedChildren[0]);
2667
3043
  }
2668
- const path6 = importedPathOf(binding, rootName);
2669
- if (path6 === null) {
2670
- return null;
3044
+ if (annotation.type === "identifier") {
3045
+ return annotation.text;
2671
3046
  }
2672
- const dotted = [...path6, ...rest];
2673
- const name = dotted.pop();
2674
- if (name === void 0 || dotted.length === 0) {
2675
- return null;
3047
+ const quoted = stringLiteralValue(annotation);
3048
+ if (quoted !== null) {
3049
+ return quoted;
2676
3050
  }
2677
- return { module: dotted.join("."), name };
3051
+ if (annotation.type === "generic_type") {
3052
+ const outer = annotation.namedChildren[0];
3053
+ const first = genericTypeArgs(annotation)[0];
3054
+ if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
3055
+ return typeNameOf(first);
3056
+ }
3057
+ }
3058
+ return null;
2678
3059
  }
2679
- function importedPathOf(binding, localName) {
2680
- if (binding.kind === "importFrom") {
2681
- if (binding.relativeLevel > 0) {
3060
+ function typeGivenBy(statement, name) {
3061
+ if (statement.type === "assignment") {
3062
+ if (field(statement, "left")?.text !== name) {
2682
3063
  return null;
2683
3064
  }
2684
- return [...binding.module.split("."), binding.importedName];
3065
+ const annotation = field(statement, "type");
3066
+ if (annotation !== null) {
3067
+ return typeNameOf(annotation);
3068
+ }
3069
+ const right = field(statement, "right");
3070
+ const callee = right?.type === "call" ? field(right, "function") : null;
3071
+ return callee?.type === "identifier" ? callee.text : null;
2685
3072
  }
2686
- if (binding.kind !== "import") {
2687
- return null;
3073
+ if (statement.type === "as_pattern") {
3074
+ const alias = field(statement, "alias");
3075
+ const value = statement.namedChildren[0] ?? null;
3076
+ const callee = value?.type === "call" ? field(value, "function") : null;
3077
+ return alias?.text === name && callee?.type === "identifier" ? callee.text : null;
2688
3078
  }
2689
- const unaliasedDotted = binding.module !== localName && binding.module.startsWith(`${localName}.`);
2690
- return unaliasedDotted ? [localName] : binding.module.split(".");
3079
+ return null;
2691
3080
  }
2692
- function memberChainOf(node) {
2693
- const names = [];
2694
- let current = peelValue(node);
2695
- while (current.type === "attribute") {
2696
- const object = field(current, "object");
2697
- const attribute = field(current, "attribute");
2698
- if (object === null || attribute === null) {
2699
- return null;
3081
+ function declaredTypeName(name, from) {
3082
+ const fn = enclosingFunction(from);
3083
+ if (fn === null) {
3084
+ return null;
3085
+ }
3086
+ const params = field(fn, "parameters");
3087
+ for (const param of params === null ? [] : children(params)) {
3088
+ const info = parameterNameAndType(param);
3089
+ if (info?.name === name && info.typeNode !== null) {
3090
+ return typeNameOf(info.typeNode);
2700
3091
  }
2701
- names.unshift(attribute.text);
2702
- current = peelValue(object);
2703
3092
  }
2704
- if (current.type !== "identifier") {
3093
+ const visit = (node) => {
3094
+ const given = typeGivenBy(node, name);
3095
+ if (given !== null) {
3096
+ return given;
3097
+ }
3098
+ for (const child of children(node)) {
3099
+ if (child.type === "function_definition" || child.type === "lambda") {
3100
+ continue;
3101
+ }
3102
+ const found = visit(child);
3103
+ if (found !== null) {
3104
+ return found;
3105
+ }
3106
+ }
2705
3107
  return null;
2706
- }
2707
- return [current.text, ...names];
2708
- }
2709
- function scopeAt(node, module2) {
2710
- let current = node;
2711
- while (current !== null) {
2712
- const scope = module2.scopeFor.get(current.id);
2713
- if (scope !== void 0) {
2714
- return scope;
3108
+ };
3109
+ const body = field(fn, "body");
3110
+ for (const statement of body === null ? [] : bodyStatements(body)) {
3111
+ const found = visit(statement);
3112
+ if (found !== null) {
3113
+ return found;
2715
3114
  }
2716
- current = current.parent;
2717
3115
  }
2718
- return module2.moduleScope;
3116
+ return null;
2719
3117
  }
2720
-
2721
- // src/values/rows.ts
2722
- var import_values3 = require("@suss/values");
2723
- var operatorRows = [
2724
- {
2725
- kind: "operator",
2726
- operator: "+",
2727
- arity: 2,
2728
- apply: ([a, b]) => (0, import_values3.plus)((0, import_values3.operand)(a), (0, import_values3.operand)(b))
2729
- },
2730
- {
2731
- kind: "operator",
2732
- operator: "%",
2733
- arity: 2,
2734
- apply: ([a, b], contentOf) => (0, import_values3.percentFormatted)((0, import_values3.operand)(a), (0, import_values3.formatArguments)(contentOf((0, import_values3.operand)(b))))
2735
- },
2736
- {
2737
- kind: "operator",
2738
- operator: "==",
2739
- arity: 2,
2740
- apply: ([a, b]) => (0, import_values3.equals)((0, import_values3.operand)(a), (0, import_values3.operand)(b))
2741
- },
2742
- {
2743
- kind: "operator",
2744
- operator: "!=",
2745
- arity: 2,
2746
- apply: ([a, b]) => (0, import_values3.negated)((0, import_values3.equals)((0, import_values3.operand)(a), (0, import_values3.operand)(b)))
2747
- },
2748
- {
2749
- kind: "operator",
2750
- operator: "is",
2751
- arity: 2,
2752
- apply: ([a, b]) => (0, import_values3.equals)((0, import_values3.operand)(a), (0, import_values3.operand)(b))
2753
- },
2754
- {
2755
- kind: "operator",
2756
- operator: "is not",
2757
- arity: 2,
2758
- apply: ([a, b]) => (0, import_values3.negated)((0, import_values3.equals)((0, import_values3.operand)(a), (0, import_values3.operand)(b)))
2759
- },
2760
- {
2761
- kind: "operator",
2762
- operator: "not",
2763
- arity: 1,
2764
- apply: ([a]) => (0, import_values3.negated)((0, import_values3.operand)(a))
2765
- },
2766
- {
2767
- kind: "operator",
2768
- operator: "or",
2769
- arity: 2,
2770
- apply: ([a, b]) => (0, import_values3.readableFallback)((0, import_values3.operand)(a), (0, import_values3.operand)(b), import_values3.truthOf)
2771
- },
2772
- {
2773
- kind: "operator",
2774
- operator: "and",
2775
- arity: 2,
2776
- apply: ([a, b]) => (0, import_values3.fallback)((0, import_values3.operand)(a), (0, import_values3.operand)(b), (left) => {
2777
- const truth = (0, import_values3.truthOf)(left);
2778
- return truth === null ? null : !truth;
2779
- })
2780
- }
2781
- ];
2782
- var stringRows = [
2783
- {
2784
- kind: "method",
2785
- method: "join",
2786
- on: "string",
2787
- apply: ({ receiver, args, contentOf }) => ({
2788
- result: (0, import_values3.joined)(contentOf((0, import_values3.operand)(args[0])), (0, import_values3.operand)(receiver))
2789
- })
2790
- },
2791
- {
2792
- kind: "method",
2793
- method: "format",
2794
- on: "string",
2795
- apply: ({ receiver, args }) => ({
2796
- result: braceFormatted((0, import_values3.operand)(receiver), args)
2797
- })
2798
- },
2799
- {
2800
- kind: "method",
2801
- method: "strip",
2802
- on: "string",
2803
- apply: ({ receiver, args }) => ({
2804
- result: args.length === 0 ? (0, import_values3.stripped)((0, import_values3.operand)(receiver)) : (0, import_values3.hole)("value")
2805
- })
2806
- },
2807
- {
2808
- kind: "method",
2809
- method: "rstrip",
2810
- on: "string",
2811
- apply: ({ receiver, args }) => ({
2812
- result: args.length === 0 ? (0, import_values3.stripped)((0, import_values3.operand)(receiver), "end") : (0, import_values3.hole)("value")
2813
- })
2814
- },
2815
- {
2816
- kind: "method",
2817
- method: "lstrip",
2818
- on: "string",
2819
- apply: ({ receiver, args }) => ({
2820
- result: args.length === 0 ? (0, import_values3.stripped)((0, import_values3.operand)(receiver), "start") : (0, import_values3.hole)("value")
2821
- })
3118
+ function typedReceiverPattern(options, chain) {
3119
+ const receiver = receiverName(chain);
3120
+ if (receiver === null) {
3121
+ return void 0;
2822
3122
  }
2823
- ];
2824
- var sequenceRows = ["sequence", "unbounded"].flatMap((on) => [
2825
- {
2826
- kind: "method",
2827
- method: "append",
2828
- on,
2829
- apply: ({ receiver, args }) => ({
2830
- result: (0, import_values3.constant)(null),
2831
- receiver: (0, import_values3.appended)((0, import_values3.operand)(receiver), args)
2832
- })
2833
- },
2834
- {
2835
- kind: "method",
2836
- method: "extend",
2837
- on,
2838
- apply: ({ receiver, args, contentOf }) => ({
2839
- result: (0, import_values3.constant)(null),
2840
- receiver: (0, import_values3.extended)((0, import_values3.operand)(receiver), contentOf((0, import_values3.operand)(args[0])))
2841
- })
3123
+ const typeName = declaredTypeName(receiver, chain.root);
3124
+ if (typeName === null) {
3125
+ return void 0;
2842
3126
  }
2843
- ]);
2844
- var calleeRows = [
2845
- {
2846
- kind: "callee",
2847
- origin: { module: "builtins", name: "str" },
2848
- apply: ({ args }) => ({ result: (0, import_values3.concat)([(0, import_values3.operand)(args[0])]) })
2849
- },
2850
- {
2851
- kind: "callee",
2852
- origin: { module: "os.path", name: "join" },
2853
- apply: ({ args }) => ({ result: (0, import_values3.joinedPath)(args) })
2854
- },
2855
- {
2856
- kind: "callee",
2857
- origin: { module: "os", name: "getenv" },
2858
- apply: ({ args }) => ({ result: (0, import_values3.environmentRead)(args) })
2859
- },
2860
- {
2861
- kind: "callee",
2862
- origin: { module: "os.environ", name: "get" },
2863
- apply: ({ args }) => ({ result: (0, import_values3.environmentRead)(args) })
3127
+ const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${typeName}`);
3128
+ if (from === void 0) {
3129
+ return void 0;
2864
3130
  }
2865
- ];
2866
- var BRACE_PLACEHOLDER = /\{([^{}]*)\}/g;
2867
- function braceFormatted(receiver, args) {
2868
- const template = (0, import_values3.literalOf)(receiver);
2869
- if (template === null) {
2870
- return (0, import_values3.hole)("value");
3131
+ return options.patterns.find(
3132
+ (pattern) => String(from[1]) === pattern.module && pattern.queryTypes.includes(String(from[2]))
3133
+ );
3134
+ }
3135
+ function fieldsOf(chain, valueMethods) {
3136
+ const args = field(chain.root, "arguments");
3137
+ const named = [];
3138
+ for (const argument of args === null ? [] : children(args)) {
3139
+ if (argument.type === "keyword_argument") {
3140
+ const name = field(argument, "name");
3141
+ if (name !== null) {
3142
+ named.push(name.text);
3143
+ }
3144
+ continue;
3145
+ }
3146
+ if (argument.type === "attribute") {
3147
+ const property = field(argument, "attribute");
3148
+ if (property !== null) {
3149
+ named.push(property.text);
3150
+ }
3151
+ }
2871
3152
  }
2872
- const parts = [];
2873
- let last = 0;
2874
- let position = 0;
2875
- for (const match of template.matchAll(BRACE_PLACEHOLDER)) {
2876
- parts.push((0, import_values3.text)(template.slice(last, match.index)));
2877
- const argument = args[position];
2878
- const placeholder = match[1] ?? "";
2879
- parts.push(
2880
- argument === void 0 ? (0, import_values3.hole)(
2881
- placeholder === "" ? "value" : placeholder.split(/[!:]/)[0] ?? "value"
2882
- ) : (0, import_values3.concat)([argument])
2883
- );
2884
- position += 1;
2885
- last = match.index + match[0].length;
3153
+ for (const call of laterCalls(chain)) {
3154
+ if (valueMethods.includes(methodNameOf(call))) {
3155
+ named.push(...keywordNames(call));
3156
+ }
2886
3157
  }
2887
- parts.push((0, import_values3.text)(template.slice(last)));
2888
- return (0, import_values3.concat)(parts);
3158
+ return [...new Set(named)];
2889
3159
  }
2890
- var pythonRows = [
2891
- ...operatorRows,
2892
- ...stringRows,
2893
- ...sequenceRows,
2894
- ...calleeRows
2895
- ];
2896
-
2897
- // src/values/evaluator.ts
2898
- var evaluators = /* @__PURE__ */ new WeakMap();
2899
- var withoutFacts = /* @__PURE__ */ new WeakMap();
2900
- var modulesByTree = /* @__PURE__ */ new WeakMap();
2901
- function bindEvaluator(db, nodes) {
2902
- for (const entry of nodes.files) {
2903
- modulesByTree.set(entry.root.tree, entry.module);
3160
+ function laterCalls(chain) {
3161
+ return [...chain.between, chain.last].filter((call) => call !== chain.root);
3162
+ }
3163
+ function methodNameOf(call) {
3164
+ const callee = field(call, "function");
3165
+ return callee?.type === "attribute" ? field(callee, "attribute")?.text ?? "" : callee?.text ?? "";
3166
+ }
3167
+ function keywordNames(call) {
3168
+ const args = field(call, "arguments");
3169
+ const picked = [];
3170
+ for (const argument of args === null ? [] : children(args)) {
3171
+ if (argument.type === "keyword_argument") {
3172
+ const name = field(argument, "name");
3173
+ if (name !== null) {
3174
+ picked.push(name.text);
3175
+ }
3176
+ }
2904
3177
  }
2905
- evaluators.set(
2906
- db,
2907
- new import_values4.Evaluator(
2908
- pythonLowering({
2909
- context: contextOver(db, nodes),
2910
- originOf: calleeOrigin,
2911
- rows: pythonRows
2912
- })
3178
+ return picked;
3179
+ }
3180
+ function selectorOf(chain, valueMethods) {
3181
+ return [
3182
+ ...new Set(
3183
+ laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap(keywordNames)
2913
3184
  )
2914
- );
3185
+ ];
2915
3186
  }
2916
- function evaluatedValue(node, db) {
2917
- return (0, import_values4.force)(evaluatorFor(node, db).evaluate(node));
3187
+ function effectFor(pattern, chain, operation) {
3188
+ const valueMethods = pattern.valueMethods ?? [];
3189
+ const picked = selectorOf(chain, valueMethods);
3190
+ return {
3191
+ type: "interaction",
3192
+ binding: (0, import_ir_core2.storageBinding)({
3193
+ recognition: "python-storage",
3194
+ storageSystem: pattern.storageSystem,
3195
+ scope: pattern.module,
3196
+ container: chain.subject
3197
+ }),
3198
+ callee: chain.last.text,
3199
+ interaction: {
3200
+ class: "storage-access",
3201
+ kind: pattern.writes.includes(operation) ? "write" : "read",
3202
+ fields: fieldsOf(chain, valueMethods),
3203
+ operation,
3204
+ ...picked.length > 0 ? { selector: picked } : {}
3205
+ }
3206
+ };
2918
3207
  }
2919
- function stringValueOf(node, db) {
2920
- return (0, import_values4.literalOf)(evaluatedValue(node, db));
3208
+ function fileOf(key) {
3209
+ const at = key.lastIndexOf(":");
3210
+ return at === -1 ? key : key.slice(0, at);
2921
3211
  }
2922
- function evaluatorFor(node, db) {
2923
- const bound = db === void 0 ? void 0 : evaluators.get(db);
2924
- if (bound !== void 0) {
2925
- return bound;
3212
+ function returnTypeName(node) {
3213
+ if (node === void 0) {
3214
+ return null;
2926
3215
  }
2927
- const tree = node.tree;
2928
- let local = withoutFacts.get(tree);
2929
- if (local === void 0) {
2930
- local = new import_values4.Evaluator(
2931
- pythonLowering({
2932
- context: null,
2933
- originOf: calleeOrigin,
2934
- rows: pythonRows
2935
- })
2936
- );
2937
- withoutFacts.set(tree, local);
3216
+ const annotation = field(node, "return_type");
3217
+ if (annotation === null) {
3218
+ return null;
2938
3219
  }
2939
- return local;
3220
+ return annotation.type === "subscript" ? field(annotation, "value")?.text ?? null : annotation.text;
2940
3221
  }
2941
- function moduleOf(node) {
2942
- const tree = node.tree;
2943
- let module2 = modulesByTree.get(tree);
2944
- if (module2 === void 0) {
2945
- module2 = bindModule(tree.rootNode);
2946
- modulesByTree.set(tree, module2);
3222
+ function storageCallees(calls, filePath, couldMatch) {
3223
+ 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)));
3224
+ }
3225
+ function resolvedMethodPattern(options, callee) {
3226
+ const resolved = options.facts.facts("wantedResolves").filter(
3227
+ (row) => String(row[0]) === readKey(options.filePath, callee, enclosingFunction(callee))
3228
+ ).map((row) => String(row[1]));
3229
+ const settled = resolved.length === 1 ? resolved[0] : void 0;
3230
+ if (settled === void 0) {
3231
+ return void 0;
2947
3232
  }
2948
- return module2;
2949
- }
2950
- function calleeOrigin(callee) {
2951
- return originOf(callee, moduleOf(callee));
3233
+ const typeName = returnTypeName(options.definitionAt(settled));
3234
+ if (typeName === null) {
3235
+ return void 0;
3236
+ }
3237
+ return options.patterns.find(
3238
+ (candidate) => candidate.queryTypes.includes(typeName) && options.facts.facts("pyImport").some(
3239
+ (row) => String(row[0]) === fileOf(settled) && String(row[1]) === candidate.module
3240
+ )
3241
+ );
2952
3242
  }
2953
- function contextOver(db, nodes) {
2954
- const filesByRoot = /* @__PURE__ */ new Map();
2955
- const rootsByFile = /* @__PURE__ */ new Map();
2956
- for (const entry of nodes.files) {
2957
- filesByRoot.set(entry.root.id, entry);
2958
- rootsByFile.set(entry.file, entry.root);
3243
+ function matchedChains(calls, options) {
3244
+ if (options.patterns.length === 0) {
3245
+ return [];
2959
3246
  }
2960
- const keyOf2 = (node) => {
2961
- const entry = filesByRoot.get(node.tree.rootNode.id);
2962
- if (entry === void 0) {
2963
- return null;
3247
+ const named = new Set(
3248
+ options.patterns.flatMap((pattern) => pattern.queryFunctions ?? [])
3249
+ );
3250
+ const chains = chainsIn(calls).filter(
3251
+ (chain) => options.couldMatch.has(methodName(chain.root)) || named.has(methodName(chain.root)) || typedReceiverPattern(options, chain) !== void 0
3252
+ );
3253
+ if (chains.length === 0) {
3254
+ return [];
3255
+ }
3256
+ const alreadyAsked = new Set(
3257
+ options.facts.facts("wanted").map((row) => String(row[0]))
3258
+ );
3259
+ resolveCalls(
3260
+ options.facts,
3261
+ storageCallees(calls, options.filePath, options.couldMatch).filter(
3262
+ (key) => !alreadyAsked.has(key)
3263
+ )
3264
+ );
3265
+ const matched = [];
3266
+ for (const chain of chains) {
3267
+ const callee = field(chain.root, "function");
3268
+ if (callee === null) {
3269
+ continue;
2964
3270
  }
2965
- return readKey(entry.file, node, enclosingFunction(node));
2966
- };
2967
- return {
2968
- writtenTo: (node) => {
2969
- const key = keyOf2(node);
2970
- if (key === null) {
2971
- return null;
2972
- }
2973
- const answer = writtenValueOf(db, key);
2974
- return answer === null ? null : (0, import_resolution2.nodeOfKey)(rootsByFile, answer);
2975
- },
2976
- callable: (call) => {
2977
- const callee = field(call, "function");
2978
- const key = callee === null ? null : keyOf2(callee);
2979
- if (key === null) {
2980
- return null;
2981
- }
2982
- resolveCalls(db, [key]);
2983
- const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2984
- const settled = resolved.length === 1 ? resolved[0] : void 0;
2985
- return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
3271
+ const imported = importedQueryFunction(options, chain);
3272
+ if (imported !== void 0) {
3273
+ matched.push({
3274
+ chain,
3275
+ pattern: imported,
3276
+ operation: methodName(chain.root)
3277
+ });
3278
+ continue;
2986
3279
  }
2987
- };
3280
+ const typed = typedReceiverPattern(options, chain);
3281
+ if (typed !== void 0) {
3282
+ const silent = (typed.recordsNothing ?? []).includes(
3283
+ methodName(chain.root)
3284
+ );
3285
+ matched.push({
3286
+ chain,
3287
+ pattern: typed,
3288
+ operation: silent ? null : chain.operation
3289
+ });
3290
+ continue;
3291
+ }
3292
+ const pattern = resolvedMethodPattern(options, callee);
3293
+ if (pattern !== void 0) {
3294
+ matched.push({ chain, pattern, operation: chain.operation });
3295
+ }
3296
+ }
3297
+ return matched;
3298
+ }
3299
+ function storageCallIds(calls, options) {
3300
+ return new Set(
3301
+ matchedChains(calls, options).map(({ chain }) => chain.root.id)
3302
+ );
3303
+ }
3304
+ function storageEffects(calls, options) {
3305
+ return matchedChains(calls, options).flatMap(
3306
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation)]
3307
+ );
2988
3308
  }
2989
3309
 
2990
3310
  // src/discovery.ts
@@ -3167,7 +3487,7 @@ function readPathArgument(classification, options) {
3167
3487
  const first = classification.args[0];
3168
3488
  if (first !== void 0) {
3169
3489
  const value = evaluatedValue(first.node, options.facts);
3170
- const path6 = (0, import_values6.literalOf)(value) ?? (0, import_values6.pathOf)(value);
3490
+ const path6 = (0, import_values7.literalOf)(value) ?? (0, import_values7.pathOf)(value);
3171
3491
  if (path6 !== void 0) {
3172
3492
  return (0, import_extractor4.writtenReading)(path6, classification.range);
3173
3493
  }
@@ -3249,6 +3569,8 @@ function classRouteUnits(pattern, pack, classification, classNode, module2, opti
3249
3569
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
3250
3570
  requestBodyFromAnnotatedClass: pattern.annotatedClassIsRequestBody === true,
3251
3571
  readsReturnedStatus: pattern.statusFromReturnedTuple === true,
3572
+ responseStatusCalls: pattern.responseStatusCalls ?? [],
3573
+ facts: options.facts,
3252
3574
  injectedCallees: new Set(pattern.injectedParameterCallees ?? []),
3253
3575
  definitionNode: maybeMethod,
3254
3576
  enclosingScope: classScope,
@@ -3433,36 +3755,106 @@ function effectsReaching(effects, conditions) {
3433
3755
  (effect) => (0, import_extractor4.guardsHoldOn)(effect.preconditions, conditions)
3434
3756
  );
3435
3757
  }
3436
- function branchesFromReturns(readsReturnedStatus, definitionNode, effects, responseShape, declaredStatus) {
3437
- if (!readsReturnedStatus) {
3438
- return null;
3758
+ function returnStatusReading(statement, ctx) {
3759
+ if (!ctx.readsReturnedStatus) {
3760
+ return ctx.declaredStatus.reading;
3439
3761
  }
3440
- const body = field(definitionNode, "body");
3441
- const returns = returnStatements(body);
3442
- if (returns.length < 2) {
3762
+ const read = statusOfReturn(statement);
3763
+ const range = rangeOf(statement);
3764
+ if (read.kind === "status") {
3765
+ return (0, import_extractor4.writtenReading)(read.value, range);
3766
+ }
3767
+ if (read.kind === "none") {
3768
+ return import_extractor4.absentReading;
3769
+ }
3770
+ return (0, import_extractor4.unreadableReading)(
3771
+ "This return gives a status this reading cannot resolve to a number, so this outcome claims none",
3772
+ range
3773
+ );
3774
+ }
3775
+ function returnBranch(statement, ctx) {
3776
+ const range = rangeOf(statement);
3777
+ const writtenBody = returnedBodyShape(statement);
3778
+ return {
3779
+ terminal: {
3780
+ kind: "response",
3781
+ // These stay null here. The extractor fills them in from the two
3782
+ // readings below.
3783
+ statusCode: null,
3784
+ body: { typeText: null, shape: null },
3785
+ exceptionType: null,
3786
+ message: null,
3787
+ component: null,
3788
+ renderTree: null,
3789
+ delegateTarget: null,
3790
+ emitEvent: null,
3791
+ location: range
3792
+ },
3793
+ statusCodeReading: {
3794
+ reading: returnStatusReading(statement, ctx),
3795
+ ...ctx.declaredStatus.libraryDefault !== void 0 ? { libraryDefault: ctx.declaredStatus.libraryDefault } : {}
3796
+ },
3797
+ bodyShapeReading: {
3798
+ reading: writtenBody === null ? ctx.responseShape.reading : (0, import_extractor4.writtenReading)(writtenBody, range)
3799
+ },
3800
+ location: range
3801
+ };
3802
+ }
3803
+ function terminalBranchOf(found, ctx) {
3804
+ const table = {
3805
+ return: (variant) => returnBranch(variant.statement, ctx),
3806
+ raise: (variant) => ({
3807
+ terminal: variant.terminal,
3808
+ location: rangeOf(variant.statement)
3809
+ })
3810
+ };
3811
+ return (0, import_behavioral_ir2.dispatchByType)(table, found);
3812
+ }
3813
+ function routeTerminals(body, raised) {
3814
+ const found = [
3815
+ ...returnStatements(body).map(
3816
+ (statement) => ({ type: "return", statement })
3817
+ ),
3818
+ ...raised.map(
3819
+ (response) => ({
3820
+ type: "raise",
3821
+ statement: response.statement,
3822
+ terminal: response.terminal
3823
+ })
3824
+ )
3825
+ ];
3826
+ return found.sort((a, b) => a.statement.startIndex - b.statement.startIndex);
3827
+ }
3828
+ function branchesPerTerminal(options) {
3829
+ const body = field(options.definitionNode, "body");
3830
+ const raised = raisedResponses(body, {
3831
+ calls: options.responseStatusCalls,
3832
+ module: options.module,
3833
+ facts: options.facts
3834
+ });
3835
+ const terminals = routeTerminals(body, raised);
3836
+ if (terminals.length < 2) {
3443
3837
  return null;
3444
3838
  }
3445
- const lowered = lowerPythonBody(body, returns);
3839
+ const statements = terminals.map((found) => found.statement);
3840
+ const thrownByCall = new NodeSet(
3841
+ raised.filter((response) => response.thrownByCall).map((response) => response.statement)
3842
+ );
3843
+ const lowered = lowerPythonBody(body, statements, thrownByCall);
3446
3844
  const enumerated = (0, import_extractor4.enumerateOrDegrade)(
3447
3845
  {
3448
3846
  statements: lowered.statements,
3449
3847
  terminalsByStmt: lowered.terminalsByStmt
3450
3848
  },
3451
- returns
3849
+ statements
3452
3850
  );
3453
3851
  const branches = [];
3454
- for (const statement of returns) {
3455
- const paths = enumerated.byTerminal.get(statement);
3852
+ for (const found of terminals) {
3853
+ const paths = enumerated.byTerminal.get(found.statement);
3456
3854
  if (paths === void 0) {
3457
3855
  continue;
3458
3856
  }
3459
- const read = statusOfReturn(statement);
3460
- const range = rangeOf(statement);
3461
- const writtenBody = returnedBodyShape(statement);
3462
- const statusReading = read.kind === "status" ? (0, import_extractor4.writtenReading)(read.value, range) : read.kind === "none" ? import_extractor4.absentReading : (0, import_extractor4.unreadableReading)(
3463
- "This return gives a status this reading cannot resolve to a number, so this outcome claims none",
3464
- range
3465
- );
3857
+ const branch = terminalBranchOf(found, options.ctx);
3466
3858
  for (const path6 of paths) {
3467
3859
  const conditions = path6.map((condition) => ({
3468
3860
  sourceText: condition.sourceText,
@@ -3471,28 +3863,9 @@ function branchesFromReturns(readsReturnedStatus, definitionNode, effects, respo
3471
3863
  source: condition.source
3472
3864
  }));
3473
3865
  branches.push({
3866
+ ...branch,
3474
3867
  conditions,
3475
- terminal: {
3476
- kind: "response",
3477
- statusCode: null,
3478
- body: { typeText: null, shape: null },
3479
- exceptionType: null,
3480
- message: null,
3481
- component: null,
3482
- renderTree: null,
3483
- delegateTarget: null,
3484
- emitEvent: null,
3485
- location: range
3486
- },
3487
- statusCodeReading: {
3488
- reading: statusReading,
3489
- ...declaredStatus.libraryDefault !== void 0 ? { libraryDefault: declaredStatus.libraryDefault } : {}
3490
- },
3491
- bodyShapeReading: {
3492
- reading: writtenBody === null ? responseShape.reading : (0, import_extractor4.writtenReading)(writtenBody, range)
3493
- },
3494
- effects: effectsReaching(effects, conditions),
3495
- location: range,
3868
+ effects: effectsReaching(options.effects, conditions),
3496
3869
  isDefault: path6.length === 0
3497
3870
  });
3498
3871
  }
@@ -3597,6 +3970,8 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
3597
3970
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
3598
3971
  requestBodyFromAnnotatedClass: pattern.annotatedClassIsRequestBody === true,
3599
3972
  readsReturnedStatus: pattern.statusFromReturnedTuple === true,
3973
+ responseStatusCalls: pattern.responseStatusCalls ?? [],
3974
+ facts: options.facts,
3600
3975
  injectedCallees: new Set(pattern.injectedParameterCallees ?? []),
3601
3976
  definitionNode: functionNode,
3602
3977
  enclosingScope: module2.moduleScope,
@@ -3705,17 +4080,18 @@ function buildRouteUnit(options) {
3705
4080
  );
3706
4081
  const effects = invocationEffects(definitionNode);
3707
4082
  const extra = recognizedBodyEffects(definitionNode, module2, storageLookup);
3708
- const perReturn = branchesFromReturns(
3709
- readsReturnedStatus,
4083
+ const perTerminal = branchesPerTerminal({
3710
4084
  definitionNode,
3711
4085
  effects,
3712
- responseShape,
3713
- statusCode
3714
- );
3715
- const branches = (perReturn ?? []).map(
4086
+ module: module2,
4087
+ facts: options.facts,
4088
+ responseStatusCalls: options.responseStatusCalls,
4089
+ ctx: { readsReturnedStatus, responseShape, declaredStatus: statusCode }
4090
+ });
4091
+ const branches = (perTerminal ?? []).map(
3716
4092
  (branch) => extra.length === 0 ? branch : { ...branch, extraEffects: extra }
3717
4093
  );
3718
- if (perReturn === null && (responseShape.reading.kind !== "absent" || statusCode.reading.kind !== "absent" || extra.length > 0 || effects.length > 0)) {
4094
+ if (perTerminal === null && (responseShape.reading.kind !== "absent" || statusCode.reading.kind !== "absent" || extra.length > 0 || effects.length > 0)) {
3719
4095
  branches.push({
3720
4096
  conditions: [],
3721
4097
  terminal: {
@@ -3797,6 +4173,15 @@ function recognizedBodyEffects(definitionNode, module2, storageLookup) {
3797
4173
  ];
3798
4174
  return [...envReadEffects(definitionNode, module2), ...storage];
3799
4175
  }
4176
+ function recognizedCallIds(definitionNode, storageLookup) {
4177
+ if (storageLookup === void 0) {
4178
+ return /* @__PURE__ */ new Set();
4179
+ }
4180
+ return storageCallIds(bodyCalls(definitionNode), {
4181
+ ...storageLookup,
4182
+ filePath: storageLookup.factsPath
4183
+ });
4184
+ }
3800
4185
  function bodyContentOf(bodyNode) {
3801
4186
  for (const stmt of bodyStatements(bodyNode)) {
3802
4187
  if (stmt.type === "pass_statement") {
@@ -3911,26 +4296,6 @@ function isInjectedParameter(param, injectedCallees) {
3911
4296
  };
3912
4297
  return containsInjectorCall(field(param, "type"), 0);
3913
4298
  }
3914
- function parameterNameAndType(param) {
3915
- if (param.type === "identifier") {
3916
- return { name: param.text, typeNode: null };
3917
- }
3918
- if (param.type === "typed_parameter") {
3919
- const inner = param.namedChildren.find(
3920
- (child) => child !== null && child.type === "identifier"
3921
- );
3922
- return inner !== void 0 ? { name: inner.text, typeNode: field(param, "type") } : null;
3923
- }
3924
- if (param.type === "default_parameter") {
3925
- const nameNode = field(param, "name");
3926
- return nameNode?.type === "identifier" ? { name: nameNode.text, typeNode: null } : null;
3927
- }
3928
- if (param.type === "typed_default_parameter") {
3929
- const nameNode = field(param, "name");
3930
- return nameNode !== null ? { name: nameNode.text, typeNode: field(param, "type") } : null;
3931
- }
3932
- return null;
3933
- }
3934
4299
 
3935
4300
  // src/facts/why.ts
3936
4301
  var import_node_fs4 = __toESM(require("fs"), 1);
@@ -4441,7 +4806,11 @@ function reachedFunctions(seeds, options) {
4441
4806
  if (source === void 0) {
4442
4807
  continue;
4443
4808
  }
4444
- const scan = scanBody(source, ctx);
4809
+ const scan = scanBody(
4810
+ source,
4811
+ ctx,
4812
+ recognizedCallIds(source.node, options.storageFor(source.file))
4813
+ );
4445
4814
  if (scan.stops.length > 0) {
4446
4815
  stopsByKey.set(key, scan.stops);
4447
4816
  }
@@ -4506,7 +4875,7 @@ var EMPTY_SCAN = {
4506
4875
  parameterCalls: [],
4507
4876
  passedPositions: /* @__PURE__ */ new Set()
4508
4877
  };
4509
- function scanBody(source, ctx) {
4878
+ function scanBody(source, ctx, recognized) {
4510
4879
  const followed = [];
4511
4880
  const stops = [];
4512
4881
  const placements = new import_behavioral_ir3.TargetPlacements();
@@ -4571,7 +4940,7 @@ function scanBody(source, ctx) {
4571
4940
  );
4572
4941
  if (outcome.kind === "stopped") {
4573
4942
  const stopKey = `${outcome.reason}:${callee}`;
4574
- if (!seen.has(stopKey) && (0, import_behavioral_ir3.worthRecording)(outcome.reason)) {
4943
+ if (!seen.has(stopKey) && !recognized.has(call.id) && (0, import_behavioral_ir3.worthRecording)(outcome.reason)) {
4575
4944
  seen.add(stopKey);
4576
4945
  stops.push({ callee, reason: outcome.reason });
4577
4946
  }