@suss/adapter-python 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -24,6 +24,11 @@ function isType(node, ...types) {
24
24
  function isFunction(node) {
25
25
  return node.type === "function_definition" || node.type === "lambda";
26
26
  }
27
+ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([
28
+ "function_definition",
29
+ "lambda",
30
+ "class_definition"
31
+ ]);
27
32
  function enclosingFunction(node) {
28
33
  let current = node.parent;
29
34
  while (current !== null) {
@@ -93,6 +98,25 @@ function children(node) {
93
98
  function bodyStatements(body) {
94
99
  return children(body);
95
100
  }
101
+ function nestedStatements(stmt) {
102
+ const found = [];
103
+ for (const child of children(stmt)) {
104
+ if (child.type === "block") {
105
+ for (const inner of children(child)) {
106
+ if (inner.type.endsWith("_clause")) {
107
+ found.push(...nestedStatements(inner));
108
+ continue;
109
+ }
110
+ found.push(inner);
111
+ }
112
+ continue;
113
+ }
114
+ if (child.type.endsWith("_clause")) {
115
+ found.push(...nestedStatements(child));
116
+ }
117
+ }
118
+ return found;
119
+ }
96
120
  function stripDecorators(node) {
97
121
  if (node.type !== "decorated_definition") {
98
122
  return { definition: node, decorators: [] };
@@ -453,8 +477,36 @@ function resolveNonlocal(scope, name) {
453
477
  }
454
478
 
455
479
  // src/annotations.ts
456
- function createAnnotationContext(scopeFor) {
457
- return { scopeFor, definitions: /* @__PURE__ */ new Map() };
480
+ function createAnnotationContext(scopeFor, importedDefinition = null) {
481
+ return {
482
+ scopeMaps: [scopeFor],
483
+ definitions: /* @__PURE__ */ new Map(),
484
+ importedDefinition,
485
+ expanding: /* @__PURE__ */ new Set()
486
+ };
487
+ }
488
+ function scopeOfNode(ctx, node) {
489
+ for (const scopeFor of ctx.scopeMaps) {
490
+ const scope = scopeFor.get(node.id);
491
+ if (scope !== void 0) {
492
+ return scope;
493
+ }
494
+ }
495
+ return void 0;
496
+ }
497
+ function aliasValueOf(name, scope, ctx) {
498
+ const binding = resolveName(scope, name);
499
+ if (binding?.kind === "assignment") {
500
+ return binding.value === null ? null : { node: binding.value, scope };
501
+ }
502
+ if (binding?.kind !== "import" && binding?.kind !== "importFrom") {
503
+ return null;
504
+ }
505
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
506
+ if (imported === null || imported.node.type === "class_definition") {
507
+ return null;
508
+ }
509
+ return { node: imported.node, scope: imported.moduleScope };
458
510
  }
459
511
  function collectedDefinitions(ctx) {
460
512
  const out = {};
@@ -497,6 +549,7 @@ var EXPRESSION_SHAPERS = {
497
549
  identifier: (node, scope, ctx) => shapeFromName(node.text, scope, ctx),
498
550
  none: () => ({ type: "null" }),
499
551
  generic_type: shapeFromGenericType,
552
+ subscript: shapeFromSubscript,
500
553
  binary_operator: shapeFromBinaryOperator
501
554
  };
502
555
  function shapeFromExpression(node, scope, ctx) {
@@ -518,8 +571,46 @@ function shapeFromName(name, scope, ctx) {
518
571
  if (binding?.kind === "classDef") {
519
572
  return recordShapeRef(name, binding.node, ctx);
520
573
  }
574
+ if (binding?.kind === "import" || binding?.kind === "importFrom") {
575
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
576
+ if (imported?.node.type === "class_definition") {
577
+ if (!ctx.scopeMaps.includes(imported.scopeFor)) {
578
+ ctx.scopeMaps.push(imported.scopeFor);
579
+ }
580
+ return recordShapeRef(name, imported.node, ctx);
581
+ }
582
+ }
583
+ const alias = aliasValueOf(name, scope, ctx);
584
+ if (alias !== null) {
585
+ return shapeFromAliasValue(name, alias, ctx);
586
+ }
521
587
  return { type: "ref", name };
522
588
  }
589
+ function shapeFromAliasValue(name, alias, ctx) {
590
+ if (ctx.expanding.has(alias.node.id)) {
591
+ return { type: "ref", name };
592
+ }
593
+ ctx.expanding.add(alias.node.id);
594
+ try {
595
+ return shapeFromExpression(alias.node, alias.scope, ctx);
596
+ } finally {
597
+ ctx.expanding.delete(alias.node.id);
598
+ }
599
+ }
600
+ function shapeFromSubscript(node, scope, ctx) {
601
+ const base = field(node, "value");
602
+ const args = fields(node, "subscript");
603
+ const baseName = base?.type === "identifier" ? base.text : null;
604
+ if (baseName === "Annotated") {
605
+ const first = args[0];
606
+ return first === void 0 ? { type: "unknown" } : shapeFromExpression(first, scope, ctx);
607
+ }
608
+ return shapeFromGeneric(
609
+ baseName,
610
+ node.text,
611
+ args.map((arg) => shapeFromExpression(arg, scope, ctx))
612
+ );
613
+ }
523
614
  function genericTypeArgs(node) {
524
615
  const typeParameter = node.namedChildren.find(
525
616
  (child) => child !== null && child.type === "type_parameter"
@@ -530,14 +621,20 @@ function genericTypeArgs(node) {
530
621
  }
531
622
  function shapeFromGenericType(node, scope, ctx) {
532
623
  const base = node.namedChild(0);
533
- const args = genericTypeArgs(node);
534
- const shapeOf = (arg) => arg !== void 0 ? annotationToShape(arg, scope, ctx) : { type: "unknown" };
535
624
  const baseName = base?.type === "identifier" ? base.text : null;
625
+ return shapeFromGeneric(
626
+ baseName,
627
+ node.text,
628
+ genericTypeArgs(node).map((arg) => annotationToShape(arg, scope, ctx))
629
+ );
630
+ }
631
+ function shapeFromGeneric(baseName, text2, args) {
632
+ const shapeOf = (arg) => arg ?? { type: "unknown" };
536
633
  if (baseName === "Optional") {
537
634
  return { type: "union", variants: [shapeOf(args[0]), { type: "null" }] };
538
635
  }
539
636
  if (baseName === "Union") {
540
- return { type: "union", variants: args.map((arg) => shapeOf(arg)) };
637
+ return { type: "union", variants: args };
541
638
  }
542
639
  if (baseName !== null && LIST_NAMES.has(baseName)) {
543
640
  return { type: "array", items: shapeOf(args[0]) };
@@ -548,7 +645,7 @@ function shapeFromGenericType(node, scope, ctx) {
548
645
  if (baseName !== null && TUPLE_SET_NAMES.has(baseName)) {
549
646
  return { type: "array", items: shapeOf(args[0]) };
550
647
  }
551
- return { type: "ref", name: baseName ?? node.text };
648
+ return { type: "ref", name: baseName ?? text2 };
552
649
  }
553
650
  function shapeFromBinaryOperator(node, scope, ctx) {
554
651
  const operator = field(node, "operator");
@@ -573,7 +670,7 @@ function recordShapeRef(name, classNode, ctx) {
573
670
  }
574
671
  ctx.definitions.set(key, null);
575
672
  const bodyNode = field(classNode, "body");
576
- const classScope = ctx.scopeFor.get(classNode.id);
673
+ const classScope = scopeOfNode(ctx, classNode);
577
674
  ctx.definitions.set(
578
675
  key,
579
676
  bodyNode !== null && classScope !== void 0 ? recordShapeOf(bodyNode, classScope, ctx) : { type: "record", properties: {} }
@@ -1316,14 +1413,14 @@ function invocationEffects(definitionNode) {
1316
1413
  if (calls.length === 0) {
1317
1414
  return [];
1318
1415
  }
1319
- const statementOf2 = /* @__PURE__ */ new Map();
1416
+ const statementOf3 = /* @__PURE__ */ new Map();
1320
1417
  const statements = [];
1321
1418
  for (const call of calls) {
1322
1419
  const statement = enclosingStatement(call, body);
1323
1420
  if (statement === null) {
1324
1421
  continue;
1325
1422
  }
1326
- statementOf2.set(call.id, statement);
1423
+ statementOf3.set(call.id, statement);
1327
1424
  statements.push(statement);
1328
1425
  }
1329
1426
  const lowered = lowerPythonBody(body, statements);
@@ -1335,7 +1432,7 @@ function invocationEffects(definitionNode) {
1335
1432
  statements
1336
1433
  );
1337
1434
  return calls.map((call) => {
1338
- const statement = statementOf2.get(call.id);
1435
+ const statement = statementOf3.get(call.id);
1339
1436
  const conditions = sharedGatingConditions(
1340
1437
  statement === void 0 ? void 0 : enumerated.byTerminal.get(statement),
1341
1438
  predicateOf
@@ -1354,44 +1451,30 @@ function invocationEffects(definitionNode) {
1354
1451
  import { mountPathsOf } from "@suss/resolution";
1355
1452
 
1356
1453
  // src/facts/resolve.ts
1357
- import { deriveOnDemand, evaluate } from "@suss/datalog";
1358
1454
  import {
1359
- ANSWER_RELATIONS,
1455
+ askResolution,
1360
1456
  placeholderValues,
1361
- RESOLUTION_QUESTIONS,
1362
- RESOLUTION_RULES,
1363
1457
  writtenValueOf as sharedWrittenValueOf,
1364
1458
  singleAnswers
1365
1459
  } from "@suss/resolution";
1366
- var RESOLUTION_PROGRAM = deriveOnDemand(
1367
- [...RESOLUTION_RULES, ...RESOLUTION_QUESTIONS],
1368
- ANSWER_RELATIONS
1369
- );
1370
1460
  function resolveCalls(db, callKeys) {
1371
- const asked = new Set(db.facts("wanted").map((row) => String(row[0])));
1372
- const fresh = callKeys.filter((key) => !asked.has(key));
1373
- if (fresh.length === 0) {
1374
- return;
1375
- }
1376
- for (const callKey of fresh) {
1377
- db.add("wanted", [callKey]);
1378
- }
1379
- evaluate(db, RESOLUTION_PROGRAM.rules);
1461
+ askResolution(db, callKeys);
1380
1462
  }
1381
1463
  function writtenValueOf(db, key) {
1382
1464
  resolveCalls(db, [key]);
1383
1465
  return sharedWrittenValueOf(db, key, (keys) => resolveCalls(db, keys));
1384
1466
  }
1467
+ function originsOf(db, nameKey2) {
1468
+ askResolution(db, [nameKey2], "wantedOrigin");
1469
+ return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey2).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
1470
+ }
1385
1471
  function subjectConstructions(db, valueKeys) {
1386
1472
  const found = /* @__PURE__ */ new Map();
1387
1473
  if (valueKeys.length === 0) {
1388
1474
  return found;
1389
1475
  }
1390
1476
  const askSubjects = (keys) => {
1391
- for (const key of keys) {
1392
- db.add("wantedSubject", [key]);
1393
- }
1394
- evaluate(db, RESOLUTION_PROGRAM.rules);
1477
+ askResolution(db, keys, "wantedSubject");
1395
1478
  };
1396
1479
  askSubjects(valueKeys);
1397
1480
  const placeholders = placeholderValues(db);
@@ -1438,6 +1521,11 @@ function objectReturnedBy(db, callKey) {
1438
1521
  }
1439
1522
 
1440
1523
  // src/facts/values.ts
1524
+ import {
1525
+ startsAtName,
1526
+ valueLeftByWrites,
1527
+ writesRunInOrder
1528
+ } from "@suss/resolution";
1441
1529
  function nodeId(filePath, node) {
1442
1530
  return `${filePath}:${node.startIndex}-${node.endIndex}`;
1443
1531
  }
@@ -1448,10 +1536,10 @@ function readKey(filePath, node, enclosing) {
1448
1536
  if (node.type !== "identifier") {
1449
1537
  return nodeId(filePath, node);
1450
1538
  }
1451
- if (enclosing !== null && parameterNames(enclosing).has(node.text)) {
1452
- return `${nodeId(filePath, enclosing)}#${node.text}`;
1453
- }
1454
- return nameId(filePath, node.text);
1539
+ return nameKeyIn(filePath, enclosing, node.text);
1540
+ }
1541
+ function nameKeyIn(filePath, enclosing, name) {
1542
+ return nameKey(filePath, scopeChainOf(filePath, enclosing), name);
1455
1543
  }
1456
1544
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
1457
1545
  "string",
@@ -1489,9 +1577,6 @@ function parameterName2(param) {
1489
1577
  }
1490
1578
  return children(param).find((child) => child.type === "identifier") ?? null;
1491
1579
  }
1492
- function parameterList(fn) {
1493
- return parameterShapes(fn).map((parameter) => parameter.name);
1494
- }
1495
1580
  function parameterShapes(fn) {
1496
1581
  const params = field(fn, "parameters");
1497
1582
  const declared = [];
@@ -1506,24 +1591,52 @@ function parameterShapes(fn) {
1506
1591
  }
1507
1592
  return declared;
1508
1593
  }
1509
- function parameterNames(fn) {
1510
- return new Set(parameterList(fn));
1594
+ function boundParameterNames(fn) {
1595
+ const params = field(fn, "parameters");
1596
+ const names = [];
1597
+ for (const param of params === null ? [] : children(params)) {
1598
+ const name = parameterName2(param);
1599
+ if (name !== null) {
1600
+ names.push(name.text);
1601
+ }
1602
+ }
1603
+ return names;
1511
1604
  }
1512
1605
  function add(emitter, relation, ...tuple) {
1513
1606
  emitter.db.add(relation, tuple);
1514
1607
  }
1608
+ function nameKey(filePath, scope, name) {
1609
+ for (let current = scope; current !== null; current = current.parent) {
1610
+ if (current.globals.has(name)) {
1611
+ return nameId(filePath, name);
1612
+ }
1613
+ if (current.locals.has(name)) {
1614
+ return `${current.funcKey}#${name}`;
1615
+ }
1616
+ }
1617
+ return nameId(filePath, name);
1618
+ }
1619
+ function scopeChainOf(filePath, fn) {
1620
+ if (fn === null) {
1621
+ return null;
1622
+ }
1623
+ const reading = scopeReadingOf(fn);
1624
+ return {
1625
+ funcKey: nodeId(filePath, fn),
1626
+ locals: reading.locals,
1627
+ globals: reading.globals,
1628
+ parent: scopeChainOf(filePath, enclosingFunction(fn))
1629
+ };
1630
+ }
1515
1631
  function emitCall(emitter, call) {
1516
1632
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
1517
1633
  const callee = field(call, "function");
1518
- if (callee === null) {
1634
+ const args = field(call, "arguments");
1635
+ if (callee === null || args === null) {
1519
1636
  return;
1520
1637
  }
1521
1638
  const callKey = nodeId(emitter.filePath, call);
1522
1639
  add(emitter, "call", callKey, valueKey(emitter, callee));
1523
- const args = field(call, "arguments");
1524
- if (args === null) {
1525
- return;
1526
- }
1527
1640
  let position = 0;
1528
1641
  for (const argument of children(args)) {
1529
1642
  if (argument.type === "keyword_argument") {
@@ -1554,11 +1667,7 @@ function valueKey(emitter, value) {
1554
1667
  if (value.type !== "identifier") {
1555
1668
  return nodeId(emitter.filePath, value);
1556
1669
  }
1557
- const enclosing = emitter.enclosing;
1558
- if (enclosing !== null && enclosing.params.has(value.text)) {
1559
- return `${enclosing.funcKey}#${value.text}`;
1560
- }
1561
- return nameId(emitter.filePath, value.text);
1670
+ return nameKey(emitter.filePath, emitter.enclosing, value.text);
1562
1671
  }
1563
1672
  function emitSequence(emitter, sequence) {
1564
1673
  const objectKey = nodeId(emitter.filePath, sequence);
@@ -1648,7 +1757,6 @@ function emitFunctionFacts(emitter, fn, classKey) {
1648
1757
  const funcKey = nodeId(emitter.filePath, fn);
1649
1758
  add(emitter, "func", funcKey);
1650
1759
  const params = field(fn, "parameters");
1651
- const declared = parameterNames(fn);
1652
1760
  let position = classKey === void 0 ? 0 : -1;
1653
1761
  let byPosition = true;
1654
1762
  let receiver = null;
@@ -1671,24 +1779,22 @@ function emitFunctionFacts(emitter, fn, classKey) {
1671
1779
  }
1672
1780
  position += 1;
1673
1781
  }
1782
+ const reading = scopeReadingOf(fn);
1674
1783
  const inside = {
1675
1784
  ...emitter,
1676
- enclosing: { funcKey, params: declared }
1785
+ enclosing: {
1786
+ funcKey,
1787
+ locals: reading.locals,
1788
+ globals: reading.globals,
1789
+ parent: emitter.enclosing
1790
+ }
1677
1791
  };
1792
+ emitScopeWrites(inside, reading, false);
1678
1793
  const body = field(fn, "body");
1679
1794
  if (body === null) {
1680
1795
  return funcKey;
1681
1796
  }
1682
- const recordNested = (node) => {
1683
- for (const child of children(node)) {
1684
- if (isFunction(child)) {
1685
- add(emitter, "containsFn", funcKey, nodeId(emitter.filePath, child));
1686
- continue;
1687
- }
1688
- recordNested(child);
1689
- }
1690
- };
1691
- recordNested(body);
1797
+ emitNestedDefinitions(inside, body);
1692
1798
  walkExpressions(inside, body, (child) => {
1693
1799
  if (child.type === "return_statement") {
1694
1800
  const returned = child.namedChildren[0];
@@ -1699,29 +1805,17 @@ function emitFunctionFacts(emitter, fn, classKey) {
1699
1805
  if (child.type === "call") {
1700
1806
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
1701
1807
  }
1702
- if (child.type === "assignment") {
1703
- emitBinding(inside, child, receiver);
1808
+ if (child.type === "assignment" && receiver !== null) {
1809
+ emitReceiverProperty(inside, child, receiver);
1704
1810
  }
1705
1811
  emitExpressionFact(inside, child);
1706
1812
  });
1707
1813
  return funcKey;
1708
1814
  }
1709
- function emitBinding(emitter, assignment2, receiver) {
1815
+ function emitReceiverProperty(emitter, assignment2, receiver) {
1710
1816
  const left = field(assignment2, "left");
1711
1817
  const right = field(assignment2, "right");
1712
- if (left === null || right === null) {
1713
- return;
1714
- }
1715
- if (left.type === "identifier") {
1716
- add(
1717
- emitter,
1718
- "binds",
1719
- nameId(emitter.filePath, left.text),
1720
- valueKey(emitter, right)
1721
- );
1722
- return;
1723
- }
1724
- if (receiver === null || left.type !== "attribute") {
1818
+ if (left === null || right === null || left.type !== "attribute") {
1725
1819
  return;
1726
1820
  }
1727
1821
  const object = field(left, "object");
@@ -1737,20 +1831,13 @@ function emitBinding(emitter, assignment2, receiver) {
1737
1831
  valueKey(emitter, right)
1738
1832
  );
1739
1833
  }
1740
- function emitAssignment(emitter, assignment2) {
1741
- emitBinding(emitter, assignment2, null);
1742
- const left = field(assignment2, "left");
1743
- const right = field(assignment2, "right");
1744
- if (left === null || right === null || left.type !== "identifier") {
1745
- return;
1834
+ function assignedValue(right) {
1835
+ if (right.type !== "subscript") {
1836
+ return right;
1746
1837
  }
1747
- add(
1748
- emitter,
1749
- "exportsAs",
1750
- emitter.filePath,
1751
- left.text,
1752
- valueKey(emitter, right)
1753
- );
1838
+ const outer = field(right, "value");
1839
+ const first = fields(right, "subscript")[0];
1840
+ return outer?.text === "Annotated" && first !== void 0 ? first : right;
1754
1841
  }
1755
1842
  function declaredBy(statement) {
1756
1843
  if (statement.type === "decorated_definition") {
@@ -1794,40 +1881,361 @@ function emitClassFacts(emitter, cls) {
1794
1881
  }
1795
1882
  continue;
1796
1883
  }
1797
- const memberKey = declaredMemberKey(emitter, member, classKey);
1884
+ const memberKey2 = declaredMemberKey(emitter, member, classKey);
1798
1885
  const name = field(member, "name");
1799
- if (memberKey !== null && name !== null) {
1800
- add(emitter, "holdsProperty", classKey, name.text, memberKey);
1886
+ if (memberKey2 !== null && name !== null) {
1887
+ add(emitter, "holdsProperty", classKey, name.text, memberKey2);
1801
1888
  }
1802
1889
  }
1803
1890
  return classKey;
1804
1891
  }
1805
- function emitValueFacts(db, filePath, root) {
1806
- const emitter = { db, filePath, enclosing: null };
1807
- const declaresName = (child, key) => {
1808
- const name = field(child, "name");
1809
- if (name === null) {
1892
+ function emitNestedDefinitions(emitter, node) {
1893
+ for (const child of children(node)) {
1894
+ if (isFunction(child)) {
1895
+ emitFunctionFacts(emitter, child);
1896
+ if (emitter.enclosing !== null) {
1897
+ add(
1898
+ emitter,
1899
+ "containsFn",
1900
+ emitter.enclosing.funcKey,
1901
+ nodeId(emitter.filePath, child)
1902
+ );
1903
+ }
1904
+ continue;
1905
+ }
1906
+ if (child.type === "class_definition") {
1907
+ emitClassFacts(emitter, child);
1908
+ continue;
1909
+ }
1910
+ emitNestedDefinitions(emitter, child);
1911
+ }
1912
+ }
1913
+ var readingsByTree = /* @__PURE__ */ new WeakMap();
1914
+ function scopeReadingOf(fn) {
1915
+ const tree = fn.tree;
1916
+ let byNode = readingsByTree.get(tree);
1917
+ if (byNode === void 0) {
1918
+ byNode = /* @__PURE__ */ new Map();
1919
+ readingsByTree.set(tree, byNode);
1920
+ }
1921
+ const remembered = byNode.get(fn.id);
1922
+ if (remembered !== void 0) {
1923
+ return remembered;
1924
+ }
1925
+ const body = field(fn, "body");
1926
+ const reading = readScope(
1927
+ body !== null && body.type === "block" ? body : null,
1928
+ boundParameterNames(fn)
1929
+ );
1930
+ byNode.set(fn.id, reading);
1931
+ return reading;
1932
+ }
1933
+ var TARGET_PATTERN_TYPES = /* @__PURE__ */ new Set([
1934
+ "pattern_list",
1935
+ "tuple_pattern",
1936
+ "list_pattern"
1937
+ ]);
1938
+ function targetNames(target, found = []) {
1939
+ if (target.type === "identifier") {
1940
+ found.push(target);
1941
+ return found;
1942
+ }
1943
+ if (TARGET_PATTERN_TYPES.has(target.type)) {
1944
+ for (const child of children(target)) {
1945
+ targetNames(child, found);
1946
+ }
1947
+ }
1948
+ return found;
1949
+ }
1950
+ function readAssignment(node, sink) {
1951
+ const left = field(node, "left");
1952
+ const right = field(node, "right");
1953
+ if (left === null) {
1954
+ return;
1955
+ }
1956
+ const written = right === null ? null : assignedValue(right);
1957
+ const names = targetNames(left);
1958
+ for (const name of names) {
1959
+ sink.record(name.text, {
1960
+ // An unpacked target takes one piece of the value, and which piece
1961
+ // is not something the assignment says.
1962
+ value: names.length === 1 ? written : null,
1963
+ given: null,
1964
+ at: node,
1965
+ direct: isDirectStatement(node, sink.bodyOwner)
1966
+ });
1967
+ }
1968
+ }
1969
+ function readAugmentedAssignment(node, sink) {
1970
+ const left = field(node, "left");
1971
+ if (left === null || left.type !== "identifier") {
1972
+ return;
1973
+ }
1974
+ sink.record(left.text, {
1975
+ value: null,
1976
+ given: null,
1977
+ at: node,
1978
+ direct: isDirectStatement(node, sink.bodyOwner)
1979
+ });
1980
+ }
1981
+ function readIterationTarget(node, sink) {
1982
+ const left = field(node, "left");
1983
+ if (left === null) {
1984
+ return;
1985
+ }
1986
+ for (const name of targetNames(left)) {
1987
+ sink.record(name.text, {
1988
+ value: null,
1989
+ given: null,
1990
+ at: node,
1991
+ direct: false
1992
+ });
1993
+ }
1994
+ }
1995
+ function identifierOf(node) {
1996
+ if (node.type === "identifier") {
1997
+ return node;
1998
+ }
1999
+ return children(node).find((child) => child.type === "identifier") ?? null;
2000
+ }
2001
+ function readAsPattern(node, sink) {
2002
+ const target = field(node, "alias") ?? children(node)[1];
2003
+ const name = target === void 0 ? null : identifierOf(target);
2004
+ if (name === null) {
2005
+ return;
2006
+ }
2007
+ sink.record(name.text, {
2008
+ value: null,
2009
+ given: null,
2010
+ at: node,
2011
+ direct: false
2012
+ });
2013
+ }
2014
+ function readWalrus(node, sink) {
2015
+ const name = field(node, "name");
2016
+ if (name === null || name.type !== "identifier") {
2017
+ return;
2018
+ }
2019
+ sink.record(name.text, {
2020
+ value: field(node, "value"),
2021
+ given: null,
2022
+ at: node,
2023
+ direct: false
2024
+ });
2025
+ }
2026
+ function readGlobalStatement(node, sink) {
2027
+ for (const child of children(node)) {
2028
+ if (child.type === "identifier") {
2029
+ sink.globals.add(child.text);
2030
+ }
2031
+ }
2032
+ }
2033
+ function readNonlocalStatement(node, sink) {
2034
+ for (const child of children(node)) {
2035
+ if (child.type === "identifier") {
2036
+ sink.nonlocals.add(child.text);
2037
+ }
2038
+ }
2039
+ }
2040
+ var WRITE_READERS = {
2041
+ assignment: readAssignment,
2042
+ augmented_assignment: readAugmentedAssignment,
2043
+ for_statement: readIterationTarget,
2044
+ for_in_clause: readIterationTarget,
2045
+ as_pattern: readAsPattern,
2046
+ named_expression: readWalrus,
2047
+ global_statement: readGlobalStatement,
2048
+ nonlocal_statement: readNonlocalStatement
2049
+ };
2050
+ function readScope(bodyOwner, parameterNames) {
2051
+ const writes = /* @__PURE__ */ new Map();
2052
+ const globals = /* @__PURE__ */ new Set();
2053
+ const nonlocals = /* @__PURE__ */ new Set();
2054
+ const record = (name, write) => {
2055
+ const existing = writes.get(name);
2056
+ if (existing === void 0) {
2057
+ writes.set(name, [write]);
1810
2058
  return;
1811
2059
  }
1812
- add(emitter, "binds", nameId(filePath, name.text), key);
1813
- add(emitter, "exportsAs", filePath, name.text, key);
2060
+ existing.push(write);
1814
2061
  };
1815
- const walk = (node) => {
1816
- for (const child of children(node)) {
1817
- if (child.type === "class_definition") {
1818
- declaresName(child, emitClassFacts(emitter, child));
1819
- continue;
1820
- }
1821
- if (isFunction(child)) {
1822
- declaresName(child, emitFunctionFacts(emitter, child));
1823
- }
1824
- if (child.type === "assignment") {
1825
- emitAssignment(emitter, child);
2062
+ const sink = { bodyOwner, globals, nonlocals, record };
2063
+ const visit = (node) => {
2064
+ if (isFunction(node) || node.type === "class_definition") {
2065
+ const name = field(node, "name");
2066
+ if (name !== null) {
2067
+ record(name.text, {
2068
+ value: node,
2069
+ given: null,
2070
+ at: node,
2071
+ direct: isDirectStatement(node, bodyOwner)
2072
+ });
1826
2073
  }
1827
- walk(child);
2074
+ return;
2075
+ }
2076
+ WRITE_READERS[node.type]?.(node, sink);
2077
+ for (const child of children(node)) {
2078
+ visit(child);
1828
2079
  }
1829
2080
  };
1830
- walk(root);
2081
+ for (const statement of bodyOwner === null ? [] : children(bodyOwner)) {
2082
+ visit(statement);
2083
+ }
2084
+ const locals = new Set(writes.keys());
2085
+ for (const name of [...globals, ...nonlocals]) {
2086
+ locals.delete(name);
2087
+ }
2088
+ for (const name of parameterNames) {
2089
+ locals.add(name);
2090
+ addParameterWrite(writes.get(name), name);
2091
+ }
2092
+ return { bodyOwner, writes, locals, globals };
2093
+ }
2094
+ function addParameterWrite(later, name) {
2095
+ const first = later?.[0];
2096
+ if (later === void 0 || first === void 0) {
2097
+ return;
2098
+ }
2099
+ later.unshift({ value: null, given: name, at: first.at, direct: false });
2100
+ }
2101
+ function statementOf(node) {
2102
+ let current = node;
2103
+ while (current !== null) {
2104
+ const parent = current.parent;
2105
+ if (parent === null) {
2106
+ return null;
2107
+ }
2108
+ if (parent.type === "block" || parent.type === "module") {
2109
+ return current;
2110
+ }
2111
+ current = parent;
2112
+ }
2113
+ return null;
2114
+ }
2115
+ function isDirectStatement(node, bodyOwner) {
2116
+ if (bodyOwner === null) {
2117
+ return false;
2118
+ }
2119
+ const statement = statementOf(node);
2120
+ return statement !== null && statement.parent?.id === bodyOwner.id;
2121
+ }
2122
+ var UNREAD_PARENTS = /* @__PURE__ */ new Set([
2123
+ "as_pattern_target",
2124
+ "global_statement",
2125
+ "nonlocal_statement",
2126
+ "pattern_list",
2127
+ "tuple_pattern",
2128
+ "list_pattern",
2129
+ "dotted_name"
2130
+ ]);
2131
+ var UNREAD_FIELDS = {
2132
+ assignment: "left",
2133
+ augmented_assignment: "left",
2134
+ for_statement: "left",
2135
+ for_in_clause: "left",
2136
+ named_expression: "name",
2137
+ function_definition: "name",
2138
+ class_definition: "name",
2139
+ attribute: "attribute",
2140
+ keyword_argument: "name"
2141
+ };
2142
+ function isNameRead(name) {
2143
+ const parent = name.parent;
2144
+ if (parent === null) {
2145
+ return true;
2146
+ }
2147
+ if (UNREAD_PARENTS.has(parent.type)) {
2148
+ return false;
2149
+ }
2150
+ const fieldName = UNREAD_FIELDS[parent.type];
2151
+ return fieldName === void 0 || field(parent, fieldName)?.id !== name.id;
2152
+ }
2153
+ var READ_FIRST = {
2154
+ call: "function",
2155
+ attribute: "object"
2156
+ };
2157
+ function readFirst(node) {
2158
+ if (node.type === "parenthesized_expression") {
2159
+ return children(node)[0] ?? null;
2160
+ }
2161
+ const fieldName = READ_FIRST[node.type];
2162
+ return fieldName === void 0 ? null : field(node, fieldName);
2163
+ }
2164
+ var NAME_READS = {
2165
+ nameTypes: /* @__PURE__ */ new Set(["identifier"]),
2166
+ laterBodies: LATER_BODY_TYPES,
2167
+ childrenOf: children,
2168
+ isRead: isNameRead,
2169
+ readFirst
2170
+ };
2171
+ var CONSTRUCTION_TYPES = /* @__PURE__ */ new Set([
2172
+ "call",
2173
+ "list",
2174
+ "dictionary",
2175
+ "set",
2176
+ "tuple",
2177
+ "list_comprehension",
2178
+ "dictionary_comprehension",
2179
+ "set_comprehension",
2180
+ "generator_expression"
2181
+ ]);
2182
+ function describeWrite(emitter, write, name) {
2183
+ if (write.value === null) {
2184
+ return {
2185
+ value: write.given,
2186
+ placeholder: false,
2187
+ construction: null,
2188
+ narrowsName: false
2189
+ };
2190
+ }
2191
+ return {
2192
+ value: valueKey(emitter, write.value),
2193
+ placeholder: write.value.type === "none",
2194
+ construction: CONSTRUCTION_TYPES.has(write.value.type) ? write.value.text.replace(/\s+/g, " ").trim() : null,
2195
+ narrowsName: write.value.type === "call" && startsAtName(write.value, name, NAME_READS)
2196
+ };
2197
+ }
2198
+ function settledValue(emitter, reading, name, writes) {
2199
+ const only = writes.length === 1 ? writes[0] : void 0;
2200
+ if (only !== void 0) {
2201
+ return only.value === null ? only.given : valueKey(emitter, only.value);
2202
+ }
2203
+ return valueLeftByWrites(
2204
+ writes.map((write) => describeWrite(emitter, write, name)),
2205
+ writesRunInOrder(reading.bodyOwner, name, writes, NAME_READS)
2206
+ );
2207
+ }
2208
+ function emitScopeWrites(emitter, reading, atModule) {
2209
+ for (const [name, writes] of reading.writes) {
2210
+ const settled = settledValue(emitter, reading, name, writes);
2211
+ const key = nameKey(emitter.filePath, emitter.enclosing, name);
2212
+ if (settled === null) {
2213
+ emitCandidates(emitter, key, name, writes);
2214
+ continue;
2215
+ }
2216
+ add(emitter, writes.length === 1 ? "binds" : "endsHolding", key, settled);
2217
+ if (atModule) {
2218
+ add(emitter, "exportsAs", emitter.filePath, name, settled);
2219
+ }
2220
+ }
2221
+ }
2222
+ function emitCandidates(emitter, key, name, writes) {
2223
+ for (const write of writes) {
2224
+ if (write.value === null) {
2225
+ if (write.given === null) {
2226
+ add(emitter, "writesUnstated", key);
2227
+ }
2228
+ continue;
2229
+ }
2230
+ if (!describeWrite(emitter, write, name).narrowsName) {
2231
+ add(emitter, "mayHold", key, valueKey(emitter, write.value));
2232
+ }
2233
+ }
2234
+ }
2235
+ function emitValueFacts(db, filePath, root) {
2236
+ const emitter = { db, filePath, enclosing: null };
2237
+ emitNestedDefinitions(emitter, root);
2238
+ emitScopeWrites(emitter, readScope(root, []), true);
1831
2239
  emitExpressionFacts(emitter, root);
1832
2240
  }
1833
2241
 
@@ -1940,7 +2348,7 @@ function pythonLowering(options) {
1940
2348
  return {
1941
2349
  idOf: (node) => node.id,
1942
2350
  expression: (node) => expressionOf(node, originOf2),
1943
- statement: statementOf,
2351
+ statement: statementOf2,
1944
2352
  siteOf,
1945
2353
  functionOf,
1946
2354
  writtenTo: (node) => context === null ? null : context.writtenTo(node),
@@ -2204,7 +2612,7 @@ var STATEMENT_TYPES = {
2204
2612
  body: blockStatements2(field(node, "body"))
2205
2613
  })
2206
2614
  };
2207
- function statementOf(node) {
2615
+ function statementOf2(node) {
2208
2616
  const lower = STATEMENT_TYPES[node.type];
2209
2617
  return lower === void 0 ? { kind: "opaque" } : lower(node);
2210
2618
  }
@@ -3274,9 +3682,13 @@ function constructionThroughFacts(name, scope, scan) {
3274
3682
  if (binding.kind !== "assignment" && binding.kind !== "importFrom") {
3275
3683
  return null;
3276
3684
  }
3277
- const nameKey = `${scan.bound.file}#${name}`;
3278
- const constructionKey2 = subjectConstructions(facts, [nameKey]).get(
3279
- nameKey
3685
+ const nameKey2 = nameKeyIn(
3686
+ scan.bound.file,
3687
+ scope.kind === "function" ? scope.node : enclosingFunction(scope.node),
3688
+ name
3689
+ );
3690
+ const constructionKey2 = subjectConstructions(facts, [nameKey2]).get(
3691
+ nameKey2
3280
3692
  )?.constructionKey;
3281
3693
  return constructionKey2 === void 0 ? null : scan.index.byValueKey.get(constructionKey2) ?? null;
3282
3694
  }
@@ -3347,11 +3759,15 @@ var WALK_DESCENTS = {
3347
3759
  scan,
3348
3760
  visit
3349
3761
  );
3350
- }
3762
+ },
3763
+ if_statement: descendInPlace,
3764
+ try_statement: descendInPlace,
3765
+ while_statement: descendInPlace,
3766
+ with_statement: descendInPlace,
3767
+ match_statement: descendInPlace
3351
3768
  };
3352
- function nestedStatements(stmt) {
3353
- const body = field(stmt, "body");
3354
- return body === null ? [] : bodyStatements(body);
3769
+ function descendInPlace(stmt, position, scan, visit) {
3770
+ walkStatements(nestedStatements(stmt), position, scan, visit);
3355
3771
  }
3356
3772
  function walkStatements(statements, position, scan, visit) {
3357
3773
  for (const stmt of statements) {
@@ -3363,11 +3779,11 @@ function walkStatements(statements, position, scan, visit) {
3363
3779
  visit(stmt, position, scan);
3364
3780
  }
3365
3781
  }
3366
- function targetNames(node) {
3782
+ function targetNames2(node) {
3367
3783
  if (node.type === "identifier") {
3368
3784
  return [node.text];
3369
3785
  }
3370
- return bodyStatements(node).flatMap(targetNames);
3786
+ return bodyStatements(node).flatMap(targetNames2);
3371
3787
  }
3372
3788
  function sequenceElementNames(node) {
3373
3789
  if (node === null || node.type !== "list" && node.type !== "tuple") {
@@ -3404,7 +3820,7 @@ function loopBindingsOf(stmt, position, scan) {
3404
3820
  });
3405
3821
  return bindings;
3406
3822
  }
3407
- for (const name of targetNames(left)) {
3823
+ for (const name of targetNames2(left)) {
3408
3824
  bindings.set(name, { kind: "unenumerable", location });
3409
3825
  }
3410
3826
  return bindings;
@@ -4432,7 +4848,7 @@ function rawSqlEffects(calls, options) {
4432
4848
  binding: storageBinding({
4433
4849
  recognition: `python-${pattern.module}`,
4434
4850
  storageSystem: pattern.storageSystem,
4435
- scope: pattern.module,
4851
+ scope: "default",
4436
4852
  container: access.table
4437
4853
  }),
4438
4854
  callee: field(call, "function")?.text ?? "",
@@ -4576,16 +4992,23 @@ function typeNameOf(annotation) {
4576
4992
  if (quoted !== null) {
4577
4993
  return quoted;
4578
4994
  }
4995
+ if (annotation.type === "binary_operator") {
4996
+ const named = [field(annotation, "left"), field(annotation, "right")].find(
4997
+ (side) => side !== null && side.type !== "none"
4998
+ );
4999
+ return named === void 0 || named === null ? null : typeNameOf(named);
5000
+ }
4579
5001
  if (annotation.type === "generic_type") {
4580
5002
  const outer = annotation.namedChildren[0];
4581
5003
  const first = genericTypeArgs(annotation)[0];
4582
5004
  if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4583
5005
  return typeNameOf(first);
4584
5006
  }
5007
+ return outer?.type === "identifier" ? outer.text : null;
4585
5008
  }
4586
5009
  return null;
4587
5010
  }
4588
- function typeGivenBy(statement, name) {
5011
+ function typeGivenBy(statement, name, options) {
4589
5012
  if (statement.type === "assignment") {
4590
5013
  if (field(statement, "left")?.text !== name) {
4591
5014
  return null;
@@ -4595,8 +5018,7 @@ function typeGivenBy(statement, name) {
4595
5018
  return typeNameOf(annotation);
4596
5019
  }
4597
5020
  const right = field(statement, "right");
4598
- const callee = right?.type === "call" ? field(right, "function") : null;
4599
- return callee?.type === "identifier" ? callee.text : null;
5021
+ return right?.type === "call" ? builtType(right, options) : null;
4600
5022
  }
4601
5023
  if (statement.type === "as_pattern") {
4602
5024
  const alias = field(statement, "alias");
@@ -4606,7 +5028,34 @@ function typeGivenBy(statement, name) {
4606
5028
  }
4607
5029
  return null;
4608
5030
  }
4609
- function declaredTypeName(name, from) {
5031
+ function builtType(call, options) {
5032
+ const root = rootOf(call);
5033
+ const callee = field(root, "function");
5034
+ if (callee?.type === "identifier") {
5035
+ return isClassName(callee.text) ? callee.text : resolvedReturnType(options, callee);
5036
+ }
5037
+ const object = callee?.type === "attribute" ? field(callee, "object") : null;
5038
+ if (object?.type === "identifier" && isClassName(object.text)) {
5039
+ return object.text;
5040
+ }
5041
+ const args = field(root, "arguments");
5042
+ const first = (args === null ? [] : children(args))[0];
5043
+ if (first?.type === "identifier" && isClassName(first.text)) {
5044
+ return first.text;
5045
+ }
5046
+ return callee === null ? null : resolvedReturnType(options, callee);
5047
+ }
5048
+ function resolvedReturnType(options, callee) {
5049
+ const settled = settledCallee(options, callee);
5050
+ return settled === void 0 ? null : returnTypeName(options.definitionAt(settled));
5051
+ }
5052
+ function settledCallee(options, callee) {
5053
+ const key = readKey(options.filePath, callee, enclosingFunction(callee));
5054
+ resolveCalls(options.facts, [key]);
5055
+ const resolved = options.facts.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
5056
+ return resolved.length === 1 ? resolved[0] : void 0;
5057
+ }
5058
+ function declaredTypeName(name, from, options) {
4610
5059
  const fn = enclosingFunction(from);
4611
5060
  if (fn === null) {
4612
5061
  return null;
@@ -4619,7 +5068,7 @@ function declaredTypeName(name, from) {
4619
5068
  }
4620
5069
  }
4621
5070
  const visit = (node) => {
4622
- const given = typeGivenBy(node, name);
5071
+ const given = typeGivenBy(node, name, options);
4623
5072
  if (given !== null) {
4624
5073
  return given;
4625
5074
  }
@@ -4648,16 +5097,27 @@ function typedReceiverPattern(options, chain) {
4648
5097
  if (receiver === null) {
4649
5098
  return void 0;
4650
5099
  }
4651
- const typeName = declaredTypeName(receiver, chain.root);
5100
+ const typeName = declaredTypeName(receiver, chain.root, options);
4652
5101
  if (typeName === null) {
4653
5102
  return void 0;
4654
5103
  }
4655
- const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${typeName}`);
4656
- if (from === void 0) {
4657
- return void 0;
5104
+ const typeKey = `${options.filePath}#${typeName}`;
5105
+ const imported = options.facts.facts("pyImportedName").find((row) => String(row[0]) === typeKey);
5106
+ const direct = imported === void 0 ? void 0 : queryTypePattern(options, String(imported[1]), String(imported[2]));
5107
+ if (direct !== void 0) {
5108
+ return direct;
5109
+ }
5110
+ for (const origin of originsOf(options.facts, typeKey)) {
5111
+ const pattern = queryTypePattern(options, origin.module, origin.name);
5112
+ if (pattern !== void 0) {
5113
+ return pattern;
5114
+ }
4658
5115
  }
5116
+ return void 0;
5117
+ }
5118
+ function queryTypePattern(options, module, name) {
4659
5119
  return options.patterns.find(
4660
- (pattern) => String(from[1]) === pattern.module && pattern.queryTypes.includes(String(from[2]))
5120
+ (pattern) => module === pattern.module && pattern.queryTypes.includes(name)
4661
5121
  );
4662
5122
  }
4663
5123
  function fieldsOf(chain, valueMethods) {
@@ -4703,16 +5163,80 @@ function keywordNames(call) {
4703
5163
  }
4704
5164
  }
4705
5165
  }
4706
- return picked;
5166
+ return picked;
5167
+ }
5168
+ function selectorOf(chain, valueMethods) {
5169
+ return [
5170
+ ...new Set(
5171
+ laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap(keywordNames)
5172
+ )
5173
+ ];
5174
+ }
5175
+ var TABLE_METHODS = ["select_from", "join"];
5176
+ function modelOf(chain, options) {
5177
+ const fromRoot = modelArgument(chain.root, chain.root, options);
5178
+ if (fromRoot !== null) {
5179
+ return fromRoot;
5180
+ }
5181
+ for (const call of laterCalls(chain)) {
5182
+ if (TABLE_METHODS.includes(methodNameOf(call))) {
5183
+ const named = modelArgument(call, chain.root, options);
5184
+ if (named !== null) {
5185
+ return named;
5186
+ }
5187
+ }
5188
+ }
5189
+ return isClassName(chain.subject) ? chain.subject : null;
5190
+ }
5191
+ function modelArgument(call, from, options) {
5192
+ const args = field(call, "arguments");
5193
+ const first = (args === null ? [] : children(args)).find(
5194
+ (argument) => argument.type !== "keyword_argument"
5195
+ );
5196
+ return first === void 0 ? null : modelNamed(first, from, options);
5197
+ }
5198
+ function modelNamed(node, from, options) {
5199
+ if (node.type === "identifier") {
5200
+ if (isClassName(node.text)) {
5201
+ return node.text;
5202
+ }
5203
+ const declared = declaredTypeName(node.text, from, options);
5204
+ return declared === null ? null : classBehind(declared, options);
5205
+ }
5206
+ if (node.type === "attribute") {
5207
+ const property = field(node, "attribute");
5208
+ if (property !== null && isClassName(property.text)) {
5209
+ return property.text;
5210
+ }
5211
+ const object = field(node, "object");
5212
+ return object === null ? null : modelNamed(object, from, options);
5213
+ }
5214
+ if (node.type === "call") {
5215
+ const callee = field(node, "function");
5216
+ const built = callee === null ? null : modelNamed(callee, from, options);
5217
+ return built ?? modelArgument(node, from, options);
5218
+ }
5219
+ return null;
4707
5220
  }
4708
- function selectorOf(chain, valueMethods) {
4709
- return [
4710
- ...new Set(
4711
- laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap(keywordNames)
4712
- )
4713
- ];
5221
+ function classBehind(typeName, options) {
5222
+ const key = `${options.filePath}#${typeName}`;
5223
+ originsOf(options.facts, key);
5224
+ const reached = new Set(
5225
+ options.facts.facts("wantedComesTo").filter((row) => String(row[0]) === key).map((row) => String(row[1]))
5226
+ );
5227
+ const classes = new Set(
5228
+ options.facts.facts("objectValue").map((row) => String(row[0])).filter((value) => reached.has(value))
5229
+ );
5230
+ const exported = options.facts.facts("exportsAs").find((row) => classes.has(String(row[2])));
5231
+ if (exported !== void 0) {
5232
+ return String(exported[1]);
5233
+ }
5234
+ return isClassName(typeName) ? typeName : null;
4714
5235
  }
4715
- function effectFor(pattern, chain, operation) {
5236
+ function isClassName(name) {
5237
+ return /^[A-Z][A-Za-z0-9_]*$/.test(name) && name !== name.toUpperCase();
5238
+ }
5239
+ function effectFor(pattern, chain, operation, options) {
4716
5240
  const valueMethods = pattern.valueMethods ?? [];
4717
5241
  const picked = selectorOf(chain, valueMethods);
4718
5242
  return {
@@ -4720,8 +5244,8 @@ function effectFor(pattern, chain, operation) {
4720
5244
  binding: storageBinding2({
4721
5245
  recognition: "python-storage",
4722
5246
  storageSystem: pattern.storageSystem,
4723
- scope: pattern.module,
4724
- container: chain.subject
5247
+ scope: "default",
5248
+ container: modelOf(chain, options)
4725
5249
  }),
4726
5250
  callee: chain.last.text,
4727
5251
  interaction: {
@@ -4738,23 +5262,14 @@ function fileOf(key) {
4738
5262
  return at === -1 ? key : key.slice(0, at);
4739
5263
  }
4740
5264
  function returnTypeName(node) {
4741
- if (node === void 0) {
4742
- return null;
4743
- }
4744
- const annotation = field(node, "return_type");
4745
- if (annotation === null) {
4746
- return null;
4747
- }
4748
- return annotation.type === "subscript" ? field(annotation, "value")?.text ?? null : annotation.text;
5265
+ const annotation = node === void 0 ? null : field(node, "return_type");
5266
+ return annotation === null ? null : typeNameOf(annotation);
4749
5267
  }
4750
5268
  function storageCallees(calls, filePath, couldMatch) {
4751
5269
  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)));
4752
5270
  }
4753
5271
  function resolvedMethodPattern(options, callee) {
4754
- const resolved = options.facts.facts("wantedResolves").filter(
4755
- (row) => String(row[0]) === readKey(options.filePath, callee, enclosingFunction(callee))
4756
- ).map((row) => String(row[1]));
4757
- const settled = resolved.length === 1 ? resolved[0] : void 0;
5272
+ const settled = settledCallee(options, callee);
4758
5273
  if (settled === void 0) {
4759
5274
  return void 0;
4760
5275
  }
@@ -4781,14 +5296,9 @@ function matchedChains(calls, options) {
4781
5296
  if (chains.length === 0) {
4782
5297
  return [];
4783
5298
  }
4784
- const alreadyAsked = new Set(
4785
- options.facts.facts("wanted").map((row) => String(row[0]))
4786
- );
4787
5299
  resolveCalls(
4788
5300
  options.facts,
4789
- storageCallees(calls, options.filePath, options.couldMatch).filter(
4790
- (key) => !alreadyAsked.has(key)
4791
- )
5301
+ storageCallees(calls, options.filePath, options.couldMatch)
4792
5302
  );
4793
5303
  const matched = [];
4794
5304
  for (const chain of chains) {
@@ -4831,7 +5341,7 @@ function storageCallIds(calls, options) {
4831
5341
  }
4832
5342
  function storageEffects(calls, options) {
4833
5343
  return matchedChains(calls, options).flatMap(
4834
- ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation)]
5344
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation, options)]
4835
5345
  );
4836
5346
  }
4837
5347
 
@@ -5127,6 +5637,7 @@ function classRouteUnits(pattern, pack, classification, classNode, module, optio
5127
5637
  pattern
5128
5638
  ),
5129
5639
  storage: options.storage,
5640
+ importedDefinition: options.importedDefinition,
5130
5641
  wrappers: wrappersAround(
5131
5642
  pattern,
5132
5643
  pack,
@@ -5456,7 +5967,10 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
5456
5967
  if (functionName === void 0) {
5457
5968
  return [];
5458
5969
  }
5459
- const ctx = createAnnotationContext(module.scopeFor);
5970
+ const ctx = createAnnotationContext(
5971
+ module.scopeFor,
5972
+ options.importedDefinition ?? null
5973
+ );
5460
5974
  return readRoutePaths(
5461
5975
  pattern,
5462
5976
  readPathArgument(classification, options),
@@ -5495,6 +6009,7 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
5495
6009
  ),
5496
6010
  definitionsCtx: ctx,
5497
6011
  storage: options.storage,
6012
+ importedDefinition: options.importedDefinition,
5498
6013
  wrappers: wrappersAround(
5499
6014
  pattern,
5500
6015
  pack,
@@ -5592,7 +6107,10 @@ function buildRouteUnit(options) {
5592
6107
  storage: storageLookup
5593
6108
  } = options;
5594
6109
  const template = valueToReadFurtherFrom(routePath);
5595
- const ctx = definitionsCtx ?? createAnnotationContext(module.scopeFor);
6110
+ const ctx = definitionsCtx ?? createAnnotationContext(
6111
+ module.scopeFor,
6112
+ options.importedDefinition ?? null
6113
+ );
5596
6114
  const parameters = readParameters(
5597
6115
  definitionNode,
5598
6116
  enclosingScope,
@@ -5673,7 +6191,7 @@ function buildRouteUnit(options) {
5673
6191
  declaredContract: null,
5674
6192
  readings: [
5675
6193
  routePath,
5676
- ...unreadRoleReadings(parameters, rangeOf(definitionNode)),
6194
+ ...template === null ? unreadRoleReadings(parameters, rangeOf(definitionNode)) : [],
5677
6195
  ...responseShape.passedOver
5678
6196
  ],
5679
6197
  ...collectedDefinitions(ctx) !== null ? { definitions: collectedDefinitions(ctx) } : {},
@@ -5768,7 +6286,7 @@ function readParameter(param, scope, ctx, pathParamNames, requestBodyFromAnnotat
5768
6286
  }
5769
6287
  const { name, typeNode } = info;
5770
6288
  const shape = typeNode !== null ? annotationToShape(typeNode, scope, ctx) : null;
5771
- const role = isInjectedParameter(param, injectedCallees) ? null : roleOf(name, shape, pathParamNames, requestBodyFromAnnotatedClass);
6289
+ const role = isInjectedParameter(param, injectedCallees, scope, ctx) ? null : roleOf(name, shape, pathParamNames, requestBodyFromAnnotatedClass);
5772
6290
  return {
5773
6291
  name,
5774
6292
  position,
@@ -5802,7 +6320,7 @@ function calleeName2(call) {
5802
6320
  }
5803
6321
  return null;
5804
6322
  }
5805
- function isInjectedParameter(param, injectedCallees) {
6323
+ function isInjectedParameter(param, injectedCallees, scope, ctx) {
5806
6324
  if (injectedCallees.size === 0) {
5807
6325
  return false;
5808
6326
  }
@@ -5816,25 +6334,33 @@ function isInjectedParameter(param, injectedCallees) {
5816
6334
  if (callsAnInjector(field(param, "value"))) {
5817
6335
  return true;
5818
6336
  }
5819
- const containsInjectorCall = (node, depth) => {
6337
+ const followed = /* @__PURE__ */ new Set();
6338
+ const containsInjectorCall = (node, nodeScope, depth) => {
5820
6339
  if (node === null || depth > MAX_ANNOTATION_DEPTH) {
5821
6340
  return false;
5822
6341
  }
5823
6342
  if (callsAnInjector(node)) {
5824
6343
  return true;
5825
6344
  }
6345
+ if (node.type === "identifier" && !followed.has(node.id)) {
6346
+ followed.add(node.id);
6347
+ const alias = aliasValueOf(node.text, nodeScope, ctx);
6348
+ if (alias !== null && containsInjectorCall(alias.node, alias.scope, 0)) {
6349
+ return true;
6350
+ }
6351
+ }
5826
6352
  return node.namedChildren.some(
5827
- (child) => containsInjectorCall(child, depth + 1)
6353
+ (child) => containsInjectorCall(child, nodeScope, depth + 1)
5828
6354
  );
5829
6355
  };
5830
- return containsInjectorCall(field(param, "type"), 0);
6356
+ return containsInjectorCall(field(param, "type"), scope, 0);
5831
6357
  }
5832
6358
 
5833
6359
  // src/facts/why.ts
5834
6360
  import fs4 from "fs";
5835
6361
  import path4 from "path";
5836
6362
  import { Database as Database3 } from "@suss/datalog";
5837
- import { explainResolvedKey, RESOLUTION_RULES as RESOLUTION_RULES2 } from "@suss/resolution";
6363
+ import { explainResolvedKey, RESOLUTION_RULES } from "@suss/resolution";
5838
6364
 
5839
6365
  // src/facts.ts
5840
6366
  function unitKey(filePath, range, name) {
@@ -5852,7 +6378,7 @@ function emitModuleImportFacts(db, filePath, module, resolverOptions) {
5852
6378
  if (binding.kind !== "import" && binding.kind !== "importFrom") {
5853
6379
  continue;
5854
6380
  }
5855
- const nameKey = `${filePath}#${localName}`;
6381
+ const nameKey2 = `${filePath}#${localName}`;
5856
6382
  const moduleText = importedModuleText(
5857
6383
  binding.module,
5858
6384
  binding.relativeLevel
@@ -5868,16 +6394,16 @@ function emitModuleImportFacts(db, filePath, module, resolverOptions) {
5868
6394
  resolution.status === "resolved" ? "resolved" : resolution.reason
5869
6395
  ]);
5870
6396
  const importedName = binding.kind === "import" ? binding.localName : binding.importedName;
5871
- db.add("pyImportedName", [nameKey, moduleText, importedName]);
6397
+ db.add("pyImportedName", [nameKey2, moduleText, importedName]);
5872
6398
  if (scope.kind === "module") {
5873
- db.add("exportsAs", [filePath, localName, nameKey]);
6399
+ db.add("exportsAs", [filePath, localName, nameKey2]);
5874
6400
  }
5875
6401
  if (resolution.status === "resolved") {
5876
6402
  db.add("pyImportResolved", [filePath, moduleText, resolution.file]);
5877
- db.add("imports", [nameKey, resolution.file, importedName]);
6403
+ db.add("imports", [nameKey2, resolution.file, importedName]);
5878
6404
  continue;
5879
6405
  }
5880
- db.add("imports", [nameKey, moduleText, importedName]);
6406
+ db.add("imports", [nameKey2, moduleText, importedName]);
5881
6407
  }
5882
6408
  }
5883
6409
  for (const openModule of module.openImports) {
@@ -5985,6 +6511,58 @@ function buildPythonExtractionReport(args) {
5985
6511
  });
5986
6512
  }
5987
6513
 
6514
+ // src/importedDefinitions.ts
6515
+ function importedDefinitionLookup(db, files) {
6516
+ const fileOfModuleScope = /* @__PURE__ */ new Map();
6517
+ const byFile = /* @__PURE__ */ new Map();
6518
+ for (const file of files) {
6519
+ fileOfModuleScope.set(file.module.moduleScope.node.id, file.file);
6520
+ byFile.set(file.file, file);
6521
+ }
6522
+ const found = /* @__PURE__ */ new Map();
6523
+ return (scope, name) => {
6524
+ const fromFile = fileOfModuleScope.get(moduleScopeOf(scope).node.id);
6525
+ if (fromFile === void 0) {
6526
+ return null;
6527
+ }
6528
+ const key = `${fromFile}#${name}`;
6529
+ const known = found.get(key);
6530
+ if (known !== void 0) {
6531
+ return known;
6532
+ }
6533
+ const definition = definitionAmong(originsOf(db, key), byFile);
6534
+ found.set(key, definition);
6535
+ return definition;
6536
+ };
6537
+ }
6538
+ function definitionAmong(origins, byFile) {
6539
+ for (const origin of origins) {
6540
+ const file = byFile.get(origin.module);
6541
+ const binding = file?.module.moduleScope.bindings.get(origin.name);
6542
+ if (file === void 0 || binding === void 0) {
6543
+ continue;
6544
+ }
6545
+ const node = definedNodeOf(binding);
6546
+ if (node !== null) {
6547
+ return {
6548
+ node,
6549
+ moduleScope: file.module.moduleScope,
6550
+ scopeFor: file.module.scopeFor
6551
+ };
6552
+ }
6553
+ }
6554
+ return null;
6555
+ }
6556
+ function definedNodeOf(binding) {
6557
+ if (binding.kind === "classDef") {
6558
+ return binding.node;
6559
+ }
6560
+ if (binding.kind === "assignment") {
6561
+ return binding.value;
6562
+ }
6563
+ return null;
6564
+ }
6565
+
5988
6566
  // src/reach/closure.ts
5989
6567
  import {
5990
6568
  functionCallBinding,
@@ -5996,7 +6574,7 @@ import {
5996
6574
  unfollowedCallGap,
5997
6575
  worthRecording
5998
6576
  } from "@suss/behavioral-ir";
5999
- import { Database, evaluate as evaluate2, lit, rule, variable as v } from "@suss/datalog";
6577
+ import { Database, evaluate, lit, rule, variable as v } from "@suss/datalog";
6000
6578
  import {
6001
6579
  assembleSummary,
6002
6580
  SKIP_CHILDREN as SKIP_CHILDREN2,
@@ -6004,248 +6582,227 @@ import {
6004
6582
  } from "@suss/extractor";
6005
6583
 
6006
6584
  // src/reach/resolveCallee.ts
6007
- var stop = (reason) => ({ kind: "stop", reason });
6008
- var MODULE_STOP = {
6009
- ambiguous: "multipleSources",
6010
- outsideRoots: "outsideRun"
6585
+ import {
6586
+ calleeOutcomeOf,
6587
+ calleeOutcomes,
6588
+ writtenSourcesOf
6589
+ } from "@suss/resolution";
6590
+ var STOP_FOR = {
6591
+ severalSources: "multipleSources",
6592
+ outsideRun: "outsideRun",
6593
+ unsettled: "unsettledValue",
6594
+ undeclared: "noDeclaration"
6011
6595
  };
6012
- function resolveCallee2(call, site, ctx) {
6013
- const callee = field(call, "function");
6014
- if (callee === null) {
6015
- return { kind: "stopped", reason: "noDeclaration" };
6596
+ var NO_DECLARATION = {
6597
+ kind: "stopped",
6598
+ reason: "noDeclaration"
6599
+ };
6600
+ function calleeSpellings(calls, ctx) {
6601
+ const spellingOf = /* @__PURE__ */ new Map();
6602
+ const keys = /* @__PURE__ */ new Set();
6603
+ for (const { call, site } of calls) {
6604
+ const spelling = spellingFor(call, site, ctx);
6605
+ spellingOf.set(call.id, spelling);
6606
+ if (spelling.kind === "key") {
6607
+ keys.add(spelling.key);
6608
+ }
6016
6609
  }
6017
- return asCallee(resolveExpression(callee, site, ctx), ctx);
6610
+ return { spellingOf, outcomes: calleeOutcomes(ctx.facts, [...keys]) };
6018
6611
  }
6019
- function resolveNamedFunctionArgument(name, site, ctx) {
6020
- if (site.rebound.has(name)) {
6021
- return null;
6612
+ function resolveCallee2(call, site, ctx, read) {
6613
+ const spelling = read?.spellingOf.get(call.id) ?? spellingFor(call, site, ctx);
6614
+ if (spelling.kind === "stopped") {
6615
+ return spelling;
6022
6616
  }
6023
- const binding = resolveName(site.scope, name);
6024
- if (binding !== null && binding.kind === "assignment") {
6617
+ const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key);
6618
+ return asCallee(outcome, site.owner, ctx);
6619
+ }
6620
+ function functionNamed(nameKey2, ctx) {
6621
+ if (assignedElsewhere(ctx.facts, nameKey2)) {
6025
6622
  return null;
6026
6623
  }
6027
- const resolved = binding === null ? throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set()) : resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
6028
- return resolved.kind === "function" ? resolved.target : null;
6624
+ const outcome = calleeOutcomeOf(ctx.facts, nameKey2);
6625
+ return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
6029
6626
  }
6030
- function asCallee(resolved, ctx) {
6031
- if (resolved.kind === "function") {
6032
- return { kind: "followed", target: resolved.target };
6033
- }
6034
- if (resolved.kind === "class") {
6035
- return asCallee(
6036
- memberOfClass(resolved.file, resolved.node, "__init__", ctx),
6037
- ctx
6038
- );
6039
- }
6040
- if (resolved.kind === "stop") {
6041
- return { kind: "stopped", reason: resolved.reason };
6042
- }
6043
- if (resolved.kind === "package") {
6044
- return { kind: "stopped", reason: "outsideRun" };
6627
+ function assignedElsewhere(facts, nameKey2) {
6628
+ return writtenSourcesOf(facts, nameKey2).some(
6629
+ (source) => !facts.has("func", [source])
6630
+ );
6631
+ }
6632
+ function readThrough(node) {
6633
+ if (node?.type !== "parenthesized_expression") {
6634
+ return node;
6045
6635
  }
6046
- return { kind: "stopped", reason: "noDeclaration" };
6636
+ return readThrough(children(node)[0] ?? null);
6047
6637
  }
6048
- var EXPRESSION_RESOLVERS = {
6049
- identifier: (node, site, ctx) => resolveIdentifier(node.text, site, ctx),
6050
- attribute: (node, site, ctx) => {
6051
- const object = field(node, "object");
6052
- const attribute = field(node, "attribute");
6053
- if (object === null || attribute === null) {
6054
- return stop("noDeclaration");
6055
- }
6056
- return memberOf(resolveExpression(object, site, ctx), attribute.text, ctx);
6057
- },
6058
- // `Service().run()` runs a method on whatever the constructor built.
6059
- call: (node, site, ctx) => {
6060
- const callee = field(node, "function");
6061
- if (callee === null) {
6062
- return stop("noDeclaration");
6063
- }
6064
- const built = resolveExpression(callee, site, ctx);
6065
- return built.kind === "class" ? built : stop("noDeclaration");
6066
- },
6067
- parenthesized_expression: (node, site, ctx) => {
6068
- const inner = node.namedChildren[0];
6069
- return inner === null || inner === void 0 ? stop("noDeclaration") : resolveExpression(inner, site, ctx);
6638
+ function spellingFor(call, site, ctx) {
6639
+ const callee = readThrough(field(call, "function"));
6640
+ if (callee === null) {
6641
+ return { kind: "stopped", reason: "noDeclaration" };
6070
6642
  }
6071
- };
6072
- function resolveExpression(node, site, ctx) {
6073
- const resolver = EXPRESSION_RESOLVERS[node.type];
6074
- return resolver === void 0 ? stop("noDeclaration") : resolver(node, site, ctx);
6643
+ return throughModules(callee, site, ctx) ?? {
6644
+ kind: "key",
6645
+ key: readKey(site.file.file, callee, enclosingFunction(callee))
6646
+ };
6075
6647
  }
6076
- function resolveIdentifier(name, site, ctx) {
6077
- if (site.rebound.has(name)) {
6078
- return stop("unsettledValue");
6648
+ function asCallee(outcome, owner, ctx) {
6649
+ if (outcome.kind === "function") {
6650
+ return functionCallee(outcome.key, ctx);
6079
6651
  }
6080
- if (isReceiver(name, site.scope)) {
6081
- return receiverClass(site);
6652
+ if (outcome.kind === "object") {
6653
+ return constructorOf(outcome.key, ctx);
6082
6654
  }
6083
- const binding = resolveName(site.scope, name);
6084
- if (binding === null) {
6085
- return throughOpenImports(site.file, name, ctx, /* @__PURE__ */ new Set());
6655
+ if (outcome.kind === "callerSupplied") {
6656
+ return outcome.key.startsWith(`${owner}#`) ? { kind: "stopped", reason: "callerSupplied" } : { kind: "stopped", reason: "unsettledValue" };
6086
6657
  }
6087
- return resolveBinding(binding, site.file, site.scope, ctx, /* @__PURE__ */ new Set());
6088
- }
6089
- function isReceiver(name, scope) {
6090
- return (name === "self" || name === "cls") && scope.kind === "function" && scope.parent?.kind === "class";
6658
+ return {
6659
+ kind: "stopped",
6660
+ reason: STOP_FOR[outcome.kind] ?? "noDeclaration"
6661
+ };
6091
6662
  }
6092
- function receiverClass(site) {
6093
- const classScope = site.scope.parent;
6094
- if (classScope === null || classScope === void 0) {
6095
- return stop("noDeclaration");
6663
+ function functionCallee(key, ctx) {
6664
+ if (ctx.definitions.get(key)?.type === "lambda") {
6665
+ return { kind: "stopped", reason: "unsettledValue" };
6096
6666
  }
6097
- return { kind: "class", file: site.file, node: classScope.node };
6098
- }
6099
- var BINDING_RESOLVERS = {
6100
- functionDef: (binding, file) => ({
6101
- kind: "function",
6102
- target: reachedFunction(file, binding.node)
6103
- }),
6104
- classDef: (binding, file) => ({ kind: "class", file, node: binding.node }),
6105
- parameter: () => stop("callerSupplied"),
6106
- // `import a.b.c` binds `a`, and `import a.b.c as m` binds `m` to `a.b.c`.
6107
- import: (binding, file, _scope, ctx) => moduleNamed(
6108
- file,
6109
- binding.localName === binding.module.split(".")[0] ? binding.localName : binding.module,
6110
- 0,
6111
- ctx
6112
- ),
6113
- importFrom: (binding, file, _scope, ctx, visited) => memberOf(
6114
- moduleNamed(file, binding.module, binding.relativeLevel, ctx),
6115
- binding.importedName,
6116
- ctx,
6117
- visited
6118
- ),
6119
- // `run = load` is an alias worth one hop; anything else is a value.
6120
- assignment: (binding, file, scope, ctx, visited) => aliasedValue(binding.value, file, scope, ctx, visited),
6121
- global: () => stop("unsettledValue"),
6122
- nonlocal: () => stop("unsettledValue")
6123
- };
6124
- function resolveBinding(binding, file, scope, ctx, visited) {
6125
- const resolver = BINDING_RESOLVERS[binding.kind];
6126
- return resolver(binding, file, scope, ctx, visited);
6667
+ const target = functionAt(key, ctx);
6668
+ return target === null ? NO_DECLARATION : { kind: "followed", target };
6127
6669
  }
6128
- function aliasedValue(value, file, scope, ctx, visited) {
6129
- if (value === null) {
6130
- return stop("unsettledValue");
6131
- }
6132
- if (value.type === "identifier") {
6133
- const binding = resolveName(scope, value.text);
6134
- if (binding === null || binding.kind === "assignment") {
6135
- return stop("unsettledValue");
6136
- }
6137
- return resolveBinding(binding, file, scope, ctx, visited);
6138
- }
6139
- if (value.type === "call") {
6140
- const callee = field(value, "function");
6141
- const built = callee === null ? stop("unsettledValue") : aliasedValue(callee, file, scope, ctx, visited);
6142
- return built.kind === "class" ? built : stop("unsettledValue");
6670
+ function constructorOf(classKey, ctx) {
6671
+ const declared = ctx.facts.lookup("holdsProperty", 0, classKey).find((row) => String(row[1]) === "__init__");
6672
+ if (declared === void 0) {
6673
+ return NO_DECLARATION;
6143
6674
  }
6144
- if (value.type === "attribute") {
6145
- const object = field(value, "object");
6146
- const attribute = field(value, "attribute");
6147
- if (object === null || attribute === null) {
6148
- return stop("unsettledValue");
6149
- }
6150
- return memberOf(
6151
- aliasedValue(object, file, scope, ctx, visited),
6152
- attribute.text,
6153
- ctx
6154
- );
6155
- }
6156
- return stop("unsettledValue");
6675
+ return functionCallee(String(declared[2]), ctx);
6157
6676
  }
6158
- function reachedFunction(file, node, owner) {
6677
+ function functionAt(key, ctx) {
6678
+ const node = ctx.definitions.get(key);
6679
+ const file = ctx.filesByPath.get(key.slice(0, key.lastIndexOf(":")));
6680
+ if (node === void 0 || file === void 0) {
6681
+ return null;
6682
+ }
6159
6683
  const name = field(node, "name")?.text ?? "<anon>";
6160
- const ownerName = owner === void 0 ? null : field(owner, "name")?.text;
6684
+ const owner = ownerClassOf(node);
6685
+ const ownerName = owner === null ? void 0 : field(owner, "name")?.text;
6161
6686
  return {
6162
6687
  file,
6163
6688
  node,
6164
6689
  name,
6165
- exportPath: ownerName === null || ownerName === void 0 ? [name] : [ownerName, name]
6690
+ exportPath: ownerName === void 0 ? [name] : [ownerName, name]
6166
6691
  };
6167
6692
  }
6168
- function moduleNamed(from, module, relativeLevel, ctx) {
6169
- const resolution = resolveModule(
6170
- from.file,
6171
- { module, relativeLevel },
6172
- { roots: ctx.roots }
6173
- );
6174
- if (resolution.status !== "resolved") {
6175
- return resolution.reason === "external" ? { kind: "package", dotted: module, relativeLevel, from } : stop(MODULE_STOP[resolution.reason]);
6693
+ function ownerClassOf(node) {
6694
+ for (let up = node.parent; up !== null; up = up.parent) {
6695
+ if (isFunction(up)) {
6696
+ return null;
6697
+ }
6698
+ if (up.type === "class_definition") {
6699
+ return up;
6700
+ }
6176
6701
  }
6177
- const file = ctx.filesByPath.get(resolution.file);
6178
- return file === void 0 ? stop("outsideRun") : { kind: "module", file };
6702
+ return null;
6179
6703
  }
6180
- function memberOf(base, name, ctx, visited = /* @__PURE__ */ new Set()) {
6181
- if (base.kind === "module") {
6182
- return memberOfModule(base.file, name, ctx, visited);
6704
+ function throughModules(callee, site, ctx) {
6705
+ if (callee.type === "identifier") {
6706
+ return resolveName(site.scope, callee.text) === null ? openImportMember(site.file, callee.text, ctx, /* @__PURE__ */ new Set()) : null;
6183
6707
  }
6184
- if (base.kind === "package") {
6185
- return moduleNamed(
6186
- base.from,
6187
- base.dotted === "" ? name : `${base.dotted}.${name}`,
6188
- base.relativeLevel,
6189
- ctx
6190
- );
6708
+ if (callee.type !== "attribute") {
6709
+ return null;
6710
+ }
6711
+ const object = field(callee, "object");
6712
+ const attribute = field(callee, "attribute");
6713
+ if (object === null || attribute === null) {
6714
+ return null;
6191
6715
  }
6192
- if (base.kind === "class") {
6193
- return memberOfClass(base.file, base.node, name, ctx);
6716
+ const base = moduleAt(object, site, ctx);
6717
+ return base === null ? null : memberKey(base, attribute.text, ctx);
6718
+ }
6719
+ function openImportMember(file, name, ctx, visited) {
6720
+ if (visited.has(file.file)) {
6721
+ return null;
6194
6722
  }
6195
- if (base.kind === "stop") {
6196
- return base;
6723
+ visited.add(file.file);
6724
+ const found = /* @__PURE__ */ new Set();
6725
+ for (const spec of file.module.openImports) {
6726
+ const dots = spec.length - spec.replace(/^\.+/, "").length;
6727
+ const opened = moduleNamed(file, spec.slice(dots), dots, ctx);
6728
+ if (opened === null || opened.kind !== "module") {
6729
+ continue;
6730
+ }
6731
+ const member = opened.file.module.moduleScope.bindings.has(name) ? { kind: "key", key: `${opened.file.file}#${name}` } : openImportMember(opened.file, name, ctx, visited);
6732
+ if (member?.kind === "key") {
6733
+ found.add(member.key);
6734
+ }
6197
6735
  }
6198
- return stop("noDeclaration");
6736
+ if (found.size > 1) {
6737
+ return { kind: "stopped", reason: "multipleSources" };
6738
+ }
6739
+ const only = [...found][0];
6740
+ return only === void 0 ? null : { kind: "key", key: only };
6199
6741
  }
6200
- function memberOfClass(file, classNode, name, ctx) {
6201
- const classScope = file.module.scopeFor.get(classNode.id);
6202
- const binding = classScope?.bindings.get(name);
6203
- if (binding === void 0) {
6204
- return stop("noDeclaration");
6742
+ function moduleAt(node, site, ctx) {
6743
+ if (node.type === "identifier") {
6744
+ return importedModule(node.text, site, ctx);
6205
6745
  }
6206
- if (binding.kind === "functionDef") {
6207
- return {
6208
- kind: "function",
6209
- target: reachedFunction(file, binding.node, classNode)
6210
- };
6746
+ if (node.type !== "attribute") {
6747
+ return null;
6748
+ }
6749
+ const object = field(node, "object");
6750
+ const attribute = field(node, "attribute");
6751
+ if (object === null || attribute === null) {
6752
+ return null;
6211
6753
  }
6212
- return resolveBinding(binding, file, classScope, ctx, /* @__PURE__ */ new Set());
6754
+ const base = moduleAt(object, site, ctx);
6755
+ return base === null ? null : submoduleOf(base, attribute.text, ctx);
6213
6756
  }
6214
- function memberOfModule(file, name, ctx, visited) {
6215
- const key = `${file.file}#${name}`;
6216
- if (visited.has(key)) {
6217
- return stop("unsettledValue");
6757
+ function importedModule(name, site, ctx) {
6758
+ const binding = resolveName(site.scope, name);
6759
+ if (binding?.kind === "import") {
6760
+ const head = binding.module.split(".")[0];
6761
+ const dotted = binding.localName === head ? binding.localName : binding.module;
6762
+ return moduleNamed(site.file, dotted, 0, ctx);
6218
6763
  }
6219
- visited.add(key);
6220
- const binding = file.module.moduleScope.bindings.get(name);
6221
- if (binding !== void 0) {
6222
- return resolveBinding(binding, file, file.module.moduleScope, ctx, visited);
6764
+ if (binding?.kind !== "importFrom") {
6765
+ return null;
6223
6766
  }
6224
- if (file.file.endsWith("__init__.py")) {
6225
- const submodule = moduleNamed(file, name, 1, ctx);
6226
- if (submodule.kind === "module") {
6227
- return submodule;
6228
- }
6767
+ const from = moduleNamed(
6768
+ site.file,
6769
+ binding.module,
6770
+ binding.relativeLevel,
6771
+ ctx
6772
+ );
6773
+ return from === null ? null : submoduleOf(from, binding.importedName, ctx);
6774
+ }
6775
+ function submoduleOf(base, name, ctx) {
6776
+ if (base.kind === "package") {
6777
+ return moduleNamed(
6778
+ base.from,
6779
+ base.dotted === "" ? name : `${base.dotted}.${name}`,
6780
+ base.relativeLevel,
6781
+ ctx
6782
+ );
6229
6783
  }
6230
- return throughOpenImports(file, name, ctx, visited);
6784
+ return base.file.file.endsWith("__init__.py") ? moduleNamed(base.file, name, 1, ctx) : null;
6231
6785
  }
6232
- function throughOpenImports(file, name, ctx, visited) {
6233
- const found = [];
6234
- for (const spec of file.module.openImports) {
6235
- const dots = spec.length - spec.replace(/^\.+/, "").length;
6236
- const opened = moduleNamed(file, spec.slice(dots), dots, ctx);
6237
- if (opened.kind !== "module") {
6238
- continue;
6239
- }
6240
- const member = memberOfModule(opened.file, name, ctx, visited);
6241
- if (member.kind !== "stop") {
6242
- found.push(member);
6243
- }
6786
+ function memberKey(base, name, ctx) {
6787
+ if (base.kind === "package") {
6788
+ return null;
6244
6789
  }
6245
- if (found.length > 1) {
6246
- return stop("multipleSources");
6790
+ if (base.file.module.moduleScope.bindings.has(name)) {
6791
+ return { kind: "key", key: `${base.file.file}#${name}` };
6792
+ }
6793
+ return openImportMember(base.file, name, ctx, /* @__PURE__ */ new Set());
6794
+ }
6795
+ function moduleNamed(from, module, relativeLevel, ctx) {
6796
+ const resolution = resolveModule(
6797
+ from.file,
6798
+ { module, relativeLevel },
6799
+ { roots: ctx.roots }
6800
+ );
6801
+ if (resolution.status !== "resolved") {
6802
+ return resolution.reason === "external" ? { kind: "package", dotted: module, relativeLevel, from } : null;
6247
6803
  }
6248
- return found[0] ?? stop("noDeclaration");
6804
+ const file = ctx.filesByPath.get(resolution.file);
6805
+ return file === void 0 ? null : { kind: "module", file };
6249
6806
  }
6250
6807
 
6251
6808
  // src/reach/closure.ts
@@ -6260,7 +6817,9 @@ var REACHABLE_RULES = [
6260
6817
  function reachedFunctions(seeds, options) {
6261
6818
  const ctx = {
6262
6819
  filesByPath: new Map(options.files.map((file) => [file.file, file])),
6263
- roots: options.roots
6820
+ roots: options.roots,
6821
+ facts: options.facts,
6822
+ definitions: options.definitions
6264
6823
  };
6265
6824
  const db = new Database();
6266
6825
  const functionByKey = /* @__PURE__ */ new Map();
@@ -6282,21 +6841,26 @@ function reachedFunctions(seeds, options) {
6282
6841
  db.add("entry", [seed.key]);
6283
6842
  }
6284
6843
  for (; ; ) {
6285
- evaluate2(db, REACHABLE_RULES);
6844
+ evaluate(db, REACHABLE_RULES);
6286
6845
  const frontier = db.facts("reachable").map(([key]) => String(key)).filter((key) => !scanned.has(key));
6287
6846
  if (frontier.length === 0) {
6288
6847
  break;
6289
6848
  }
6290
- for (const key of frontier) {
6849
+ const bodies = frontier.flatMap((key) => {
6291
6850
  scanned.add(key);
6292
6851
  const source = functionByKey.get(key);
6293
- if (source === void 0) {
6294
- continue;
6295
- }
6852
+ return source === void 0 ? [] : [{ key, source, ...bodyOf(source) }];
6853
+ });
6854
+ const spellings = calleeSpellings(
6855
+ bodies.flatMap((body) => body.written),
6856
+ ctx
6857
+ );
6858
+ for (const { key, source, body, written } of bodies) {
6296
6859
  const scan = scanBody(
6297
6860
  source,
6298
6861
  ctx,
6299
- recognizedCallIds(source.node, options.storageFor(source.file))
6862
+ recognizedCallIds(source.node, options.storageFor(source.file)),
6863
+ { body, written, spellings }
6300
6864
  );
6301
6865
  if (scan.stops.length > 0) {
6302
6866
  stopsByKey.set(key, scan.stops);
@@ -6362,7 +6926,18 @@ var EMPTY_SCAN = {
6362
6926
  parameterCalls: [],
6363
6927
  passedPositions: /* @__PURE__ */ new Set()
6364
6928
  };
6365
- function scanBody(source, ctx, recognized) {
6929
+ function bodyOf(source) {
6930
+ const { file, node } = source;
6931
+ const body = field(node, "body");
6932
+ if (body === null) {
6933
+ return { body, written: [] };
6934
+ }
6935
+ return {
6936
+ body,
6937
+ written: callsWritten(file, body, scopeAt2(file, node), keyOf(source))
6938
+ };
6939
+ }
6940
+ function scanBody(source, ctx, recognized, read) {
6366
6941
  const followed = [];
6367
6942
  const stops = [];
6368
6943
  const placements = new TargetPlacements();
@@ -6371,12 +6946,12 @@ function scanBody(source, ctx, recognized) {
6371
6946
  const seen = /* @__PURE__ */ new Set();
6372
6947
  const parameterCallsSeen = /* @__PURE__ */ new Set();
6373
6948
  const { file, node } = source;
6374
- const body = field(node, "body");
6375
- if (body === null) {
6949
+ const { written, spellings } = read;
6950
+ if (read.body === null) {
6376
6951
  return EMPTY_SCAN;
6377
6952
  }
6378
6953
  const ownParameters = callParameterNames(node, source.exportPath.length > 1);
6379
- const recordPassedArgs = (call, callee, calleeKey, where) => {
6954
+ const recordPassedArgs = (call, callee, calleeKey) => {
6380
6955
  const args = field(call, "arguments");
6381
6956
  if (args === null) {
6382
6957
  return;
@@ -6385,9 +6960,8 @@ function scanBody(source, ctx, recognized) {
6385
6960
  if (arg === null || arg.type !== "identifier") {
6386
6961
  return;
6387
6962
  }
6388
- const resolved = resolveNamedFunctionArgument(
6389
- arg.text,
6390
- { file, scope: where.scope, rebound: where.rebound },
6963
+ const resolved = functionNamed(
6964
+ readKey(file.file, arg, enclosingFunction(arg)),
6391
6965
  ctx
6392
6966
  );
6393
6967
  if (resolved === null) {
@@ -6407,13 +6981,9 @@ function scanBody(source, ctx, recognized) {
6407
6981
  }
6408
6982
  });
6409
6983
  };
6410
- const record = (call, where) => {
6984
+ const record = (call, site) => {
6411
6985
  const callee = calleeText(call);
6412
- const outcome = resolveCallee2(
6413
- call,
6414
- { file, scope: where.scope, rebound: where.rebound },
6415
- ctx
6416
- );
6986
+ const outcome = resolveCallee2(call, site, ctx, spellings);
6417
6987
  const placed = outcome.kind === "followed" ? {
6418
6988
  file: outcome.target.file.displayPath,
6419
6989
  span: spanOf(outcome.target.node)
@@ -6422,8 +6992,7 @@ function scanBody(source, ctx, recognized) {
6422
6992
  recordPassedArgs(
6423
6993
  call,
6424
6994
  callee,
6425
- outcome.kind === "followed" ? keyOf(outcome.target) : null,
6426
- where
6995
+ outcome.kind === "followed" ? keyOf(outcome.target) : null
6427
6996
  );
6428
6997
  if (outcome.kind === "stopped") {
6429
6998
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6446,32 +7015,9 @@ function scanBody(source, ctx, recognized) {
6446
7015
  followed.push(outcome.target);
6447
7016
  }
6448
7017
  };
6449
- walkDescendants3(
6450
- body,
6451
- { scope: scopeAt2(file, node), rebound: reboundNames(body) },
6452
- {
6453
- at: (child, where) => {
6454
- if (child.type === "call") {
6455
- record(child, where);
6456
- }
6457
- },
6458
- into: (child, where) => {
6459
- if (child.type === "function_definition") {
6460
- return SKIP_CHILDREN2;
6461
- }
6462
- if (child.type === "lambda") {
6463
- return {
6464
- scope: where.scope,
6465
- rebound: /* @__PURE__ */ new Set([...where.rebound, ...lambdaParameters(child)])
6466
- };
6467
- }
6468
- return {
6469
- scope: file.module.scopeFor.get(child.id) ?? where.scope,
6470
- rebound: where.rebound
6471
- };
6472
- }
6473
- }
6474
- );
7018
+ for (const { call, site } of written) {
7019
+ record(call, site);
7020
+ }
6475
7021
  return {
6476
7022
  followed,
6477
7023
  stops,
@@ -6481,6 +7027,18 @@ function scanBody(source, ctx, recognized) {
6481
7027
  passedPositions
6482
7028
  };
6483
7029
  }
7030
+ function callsWritten(file, body, outer, owner) {
7031
+ const found = [];
7032
+ walkDescendants3(body, outer, {
7033
+ at: (child, scope) => {
7034
+ if (child.type === "call") {
7035
+ found.push({ call: child, site: { file, scope, owner } });
7036
+ }
7037
+ },
7038
+ into: (child, scope) => child.type === "function_definition" ? SKIP_CHILDREN2 : file.module.scopeFor.get(child.id) ?? scope
7039
+ });
7040
+ return found;
7041
+ }
6484
7042
  function scopeAt2(file, node) {
6485
7043
  let current = node;
6486
7044
  while (current !== null) {
@@ -6492,40 +7050,6 @@ function scopeAt2(file, node) {
6492
7050
  }
6493
7051
  return file.module.moduleScope;
6494
7052
  }
6495
- var REBINDING_FIELDS = {
6496
- assignment: "left",
6497
- augmented_assignment: "left",
6498
- named_expression: "name",
6499
- for_statement: "left",
6500
- for_in_clause: "left",
6501
- as_pattern: "alias"
6502
- };
6503
- function reboundNames(body) {
6504
- const names = /* @__PURE__ */ new Set();
6505
- walkDescendants3(body, null, {
6506
- at: (node) => {
6507
- const target = REBINDING_FIELDS[node.type];
6508
- if (target === void 0 || boundByBinder(node, body)) {
6509
- return;
6510
- }
6511
- const written = field(node, target);
6512
- if (written !== null) {
6513
- for (const name of identifiersUnder(written)) {
6514
- names.add(name);
6515
- }
6516
- }
6517
- },
6518
- into: (node) => node.type === "function_definition" ? SKIP_CHILDREN2 : null
6519
- });
6520
- return names;
6521
- }
6522
- function boundByBinder(node, body) {
6523
- if (node.type !== "assignment") {
6524
- return false;
6525
- }
6526
- const statement = node.parent;
6527
- return field(node, "left")?.type === "identifier" && statement?.type === "expression_statement" && statement.parent?.id === body.id;
6528
- }
6529
7053
  function identifiersUnder(node, found = []) {
6530
7054
  if (node.type === "identifier") {
6531
7055
  found.push(node.text);
@@ -6538,10 +7062,6 @@ function identifiersUnder(node, found = []) {
6538
7062
  }
6539
7063
  return found;
6540
7064
  }
6541
- function lambdaParameters(lambda) {
6542
- const parameters = field(lambda, "parameters");
6543
- return parameters === null ? [] : identifiersUnder(parameters);
6544
- }
6545
7065
  function libraryUnit(target, options) {
6546
7066
  const { file, node, name, exportPath } = target;
6547
7067
  const body = field(node, "body");
@@ -6692,6 +7212,18 @@ var PythonWrapperIndex = class {
6692
7212
  ownRoutes = /* @__PURE__ */ new Map();
6693
7213
  unitsByKey = /* @__PURE__ */ new Map();
6694
7214
  unitsByFile = /* @__PURE__ */ new Map();
7215
+ /** The project function a module-level name in this file refers to, or null for anything else. */
7216
+ functionCalled(file, name) {
7217
+ if (this.options.facts === void 0) {
7218
+ return null;
7219
+ }
7220
+ return functionNamed(nameKeyIn(file, null, name), {
7221
+ filesByPath: this.filesByPath,
7222
+ roots: this.options.roots,
7223
+ facts: this.options.facts,
7224
+ definitions: this.options.definitions
7225
+ });
7226
+ }
6695
7227
  register(covers, target, declared) {
6696
7228
  const registered = this.registered(target, declared);
6697
7229
  const table = covers.kind === "everyRoute" ? this.everyRoute : this.ownRoutes;
@@ -6738,20 +7270,12 @@ var PythonWrapperIndex = class {
6738
7270
  if (declared.form.type !== "dependency") {
6739
7271
  continue;
6740
7272
  }
6741
- const site = {
6742
- file,
6743
- scope: query.module.moduleScope,
6744
- rebound: /* @__PURE__ */ new Set()
6745
- };
6746
7273
  const targets = [
6747
7274
  ...decoratorDependencies(query.classification, declared.form),
6748
7275
  ...parameterDependencies(query.definitionNode, declared.form)
6749
7276
  ];
6750
7277
  for (const name of targets) {
6751
- const target = resolveNamedFunctionArgument(name, site, {
6752
- filesByPath: this.filesByPath,
6753
- roots: this.options.roots
6754
- });
7278
+ const target = this.functionCalled(file.file, name);
6755
7279
  if (target !== null) {
6756
7280
  found.push(this.registered(target, declared));
6757
7281
  }
@@ -6893,12 +7417,8 @@ function registerConstructorDependencies(file, declared, form, index) {
6893
7417
  if (listed === void 0) {
6894
7418
  continue;
6895
7419
  }
6896
- const site = { file, scope, rebound: /* @__PURE__ */ new Set() };
6897
7420
  for (const name of dependencyNamesIn(listed.node, form)) {
6898
- const target = resolveNamedFunctionArgument(name, site, {
6899
- filesByPath: index.filesByPath,
6900
- roots: index.options.roots
6901
- });
7421
+ const target = index.functionCalled(file.file, name);
6902
7422
  if (target !== null) {
6903
7423
  index.register(
6904
7424
  coverageOf(match, file, declared.pack),
@@ -7226,9 +7746,11 @@ async function extractPythonProject(options) {
7226
7746
  packs: options.packs,
7227
7747
  roots: options.roots,
7228
7748
  facts: needsValues ? db : void 0,
7749
+ definitions,
7229
7750
  storageFor
7230
7751
  })
7231
7752
  );
7753
+ const importedDefinition = importedDefinitionLookup(db, bound);
7232
7754
  const discovered = /* @__PURE__ */ new Map();
7233
7755
  for (const boundFile of bound) {
7234
7756
  const { file, root, module: moduleBinding } = boundFile;
@@ -7242,6 +7764,7 @@ async function extractPythonProject(options) {
7242
7764
  routerIndex,
7243
7765
  gapHandling,
7244
7766
  wrappers: wrapperIndex,
7767
+ importedDefinition,
7245
7768
  ...needsValues ? { facts: db } : {},
7246
7769
  ...storage === void 0 ? {} : { storage }
7247
7770
  })
@@ -7307,7 +7830,9 @@ async function extractPythonProject(options) {
7307
7830
  files: bound,
7308
7831
  roots: options.roots,
7309
7832
  gapHandling,
7310
- storageFor
7833
+ storageFor,
7834
+ facts: db,
7835
+ definitions
7311
7836
  })
7312
7837
  );
7313
7838
  for (const [key, owners] of summariesBySeed) {
@@ -7569,7 +8094,7 @@ var PythonWhySession = class {
7569
8094
  explain(value, options = {}) {
7570
8095
  return explainResolvedKey({
7571
8096
  db: this.db,
7572
- rules: RESOLUTION_RULES2,
8097
+ rules: RESOLUTION_RULES,
7573
8098
  key: readKey(value.file, value.node, enclosingFunction(value.node)),
7574
8099
  locate: (key) => this.locate(key),
7575
8100
  displayPath: (key) => this.displayPath(key),
@@ -7699,6 +8224,7 @@ export {
7699
8224
  emitValueFacts,
7700
8225
  extractPythonProject,
7701
8226
  findPythonFiles,
8227
+ nameKeyIn,
7702
8228
  nodeId,
7703
8229
  objectReturnedBy,
7704
8230
  parsePython,