@suss/adapter-python 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1048,6 +1048,8 @@ var CONVERTING_BUILTINS = /* @__PURE__ */ new Set([
1048
1048
  "str",
1049
1049
  "float"
1050
1050
  ]);
1051
+ var ATTRIBUTE_GETTER = "getattr";
1052
+ var POSITION_COUNTER = "enumerate";
1051
1053
 
1052
1054
  // src/facts/values.ts
1053
1055
  function nodeId(filePath, node) {
@@ -1219,8 +1221,7 @@ function emitConversion(emitter, call, callKey) {
1219
1221
  return;
1220
1222
  }
1221
1223
  const name = callee.text;
1222
- const builtin = nameKey(emitter.filePath, emitter.enclosing, name) === nameId(emitter.filePath, name) && !emitter.moduleNames().has(name);
1223
- if (builtin) {
1224
+ if (isBuiltin(emitter, name)) {
1224
1225
  add(emitter, "converts", callKey, valueKey(emitter, first.node), name);
1225
1226
  }
1226
1227
  }
@@ -1269,6 +1270,7 @@ function emitSequence(emitter, sequence) {
1269
1270
  const objectKey = nodeId(emitter.filePath, sequence);
1270
1271
  add(emitter, "objectValue", objectKey);
1271
1272
  let position = 0;
1273
+ const entries = [];
1272
1274
  for (const element of children(sequence)) {
1273
1275
  add(
1274
1276
  emitter,
@@ -1278,12 +1280,21 @@ function emitSequence(emitter, sequence) {
1278
1280
  valueKey(emitter, element)
1279
1281
  );
1280
1282
  position += 1;
1283
+ if (element.type !== "comment") {
1284
+ entries.push(writtenEntry(emitter, element));
1285
+ }
1281
1286
  }
1287
+ (0, import_resolution3.addEntryRows)(rowsOf(emitter), objectKey, entries);
1282
1288
  }
1283
1289
  function emitDictionary(emitter, dictionary) {
1284
1290
  const objectKey = nodeId(emitter.filePath, dictionary);
1285
1291
  add(emitter, "objectValue", objectKey);
1292
+ const entries = [];
1286
1293
  for (const pair of children(dictionary)) {
1294
+ if (SPREAD_TYPES.has(pair.type)) {
1295
+ entries.push(writtenEntry(emitter, pair));
1296
+ continue;
1297
+ }
1287
1298
  if (pair.type !== "pair") {
1288
1299
  continue;
1289
1300
  }
@@ -1292,6 +1303,7 @@ function emitDictionary(emitter, dictionary) {
1292
1303
  if (key === null || value === null) {
1293
1304
  continue;
1294
1305
  }
1306
+ entries.push(writtenEntry(emitter, value));
1295
1307
  if (key.type !== "string") {
1296
1308
  add(emitter, "holdsUnderKey", objectKey, valueKey(emitter, value));
1297
1309
  continue;
@@ -1304,6 +1316,15 @@ function emitDictionary(emitter, dictionary) {
1304
1316
  valueKey(emitter, value)
1305
1317
  );
1306
1318
  }
1319
+ (0, import_resolution3.addEntryRows)(rowsOf(emitter), objectKey, entries);
1320
+ }
1321
+ var SPREAD_TYPES = /* @__PURE__ */ new Set(["list_splat", "dictionary_splat"]);
1322
+ function writtenEntry(emitter, entry) {
1323
+ const unpacked = SPREAD_TYPES.has(entry.type) ? children(entry)[0] : void 0;
1324
+ return unpacked === void 0 ? { key: valueKey(emitter, entry), form: import_resolution3.VALUE_ENTRY } : { key: valueKey(emitter, unpacked), form: import_resolution3.SPREAD_ENTRY };
1325
+ }
1326
+ function rowsOf(emitter) {
1327
+ return (relation, row) => add(emitter, relation, ...row);
1307
1328
  }
1308
1329
  function emitAttribute(emitter, attribute) {
1309
1330
  const object = field(attribute, "object");
@@ -1368,13 +1389,50 @@ function emitKeyedRead(emitter, site, container, key) {
1368
1389
  if (WRITTEN_KEY_TYPES.has(key.type)) {
1369
1390
  return;
1370
1391
  }
1371
- add(
1372
- emitter,
1373
- "readsKeyed",
1374
- nodeId(emitter.filePath, site),
1375
- valueKey(emitter, container),
1376
- valueKey(emitter, key)
1392
+ const siteKey = nodeId(emitter.filePath, site);
1393
+ const containerKey = valueKey(emitter, container);
1394
+ add(emitter, "readsKeyed", siteKey, containerKey, valueKey(emitter, key));
1395
+ add(emitter, "choosesEntry", siteKey, containerKey);
1396
+ }
1397
+ function emitAttributeChoice(emitter, call) {
1398
+ const callee = field(call, "function");
1399
+ const [target, name] = callArguments(call);
1400
+ if (callee?.type !== "identifier" || callee.text !== ATTRIBUTE_GETTER || !isBuiltin(emitter, callee.text) || target?.kind !== "positional" || name?.kind !== "positional") {
1401
+ return;
1402
+ }
1403
+ const siteKey = nodeId(emitter.filePath, call);
1404
+ const targetKey = valueKey(emitter, target.node);
1405
+ const written = plainStringOf(name.node);
1406
+ if (written !== null) {
1407
+ add(emitter, "choosesNamedEntry", siteKey, targetKey, written);
1408
+ return;
1409
+ }
1410
+ const ends = writtenEnds(name.node);
1411
+ if (ends === null) {
1412
+ add(emitter, "choosesEntry", siteKey, targetKey);
1413
+ return;
1414
+ }
1415
+ add(emitter, "choosesEntryLike", siteKey, targetKey, ends.start, ends.end);
1416
+ }
1417
+ function writtenEnds(node) {
1418
+ if (node.type !== "string") {
1419
+ return null;
1420
+ }
1421
+ const parts = children(node).filter(
1422
+ (part) => part.type !== "string_start" && part.type !== "string_end"
1377
1423
  );
1424
+ const written = (part) => part.type === "string_content";
1425
+ const firstComputed = parts.findIndex((part) => !written(part));
1426
+ if (firstComputed === -1) {
1427
+ return null;
1428
+ }
1429
+ const lastComputed = parts.length - 1 - [...parts].reverse().findIndex((part) => !written(part));
1430
+ const start = parts.slice(0, firstComputed).map((part) => part.text).join("");
1431
+ const end = parts.slice(lastComputed + 1).map((part) => part.text).join("");
1432
+ return start === "" && end === "" ? null : { start, end };
1433
+ }
1434
+ function isBuiltin(emitter, name) {
1435
+ return nameKey(emitter.filePath, emitter.enclosing, name) === nameId(emitter.filePath, name) && !emitter.moduleNames().has(name);
1378
1436
  }
1379
1437
  function emitWrittenKeyRead(emitter, site, container, key) {
1380
1438
  const name = plainStringOf(key);
@@ -1510,6 +1568,86 @@ function emitComprehensionElement(emitter, comprehension) {
1510
1568
  );
1511
1569
  }
1512
1570
  }
1571
+ var PAIR_TARGET_TYPES = /* @__PURE__ */ new Set(["pattern_list", "tuple_pattern"]);
1572
+ function emitElementVariables(emitter, clause) {
1573
+ const target = field(clause, "left");
1574
+ const over = field(clause, "right");
1575
+ if (target === null || over === null) {
1576
+ return;
1577
+ }
1578
+ const names = PAIR_TARGET_TYPES.has(target.type) ? children(target) : [];
1579
+ const [position, element] = names;
1580
+ const counted = countedCollection(emitter, over);
1581
+ if (counted !== null && names.length === 2 && position?.type === "identifier" && element !== void 0) {
1582
+ emitElementTaken(emitter, element, valueKey(emitter, counted), position);
1583
+ return;
1584
+ }
1585
+ emitElementTaken(emitter, target, valueKey(emitter, over), null);
1586
+ }
1587
+ function emitElementTaken(emitter, target, overKey, position) {
1588
+ const variables = target.type === "identifier" ? [{ node: target, part: null }] : partsTakenApart(target);
1589
+ for (const variable of variables) {
1590
+ const key = valueKey(emitter, variable.node);
1591
+ if (variable.part === null) {
1592
+ add(emitter, "elementOf", key, overKey);
1593
+ continue;
1594
+ }
1595
+ add(emitter, "elementPartOf", key, overKey, variable.part);
1596
+ }
1597
+ const [first] = variables;
1598
+ if (position !== null && first !== void 0) {
1599
+ add(
1600
+ emitter,
1601
+ "positionOf",
1602
+ valueKey(emitter, position),
1603
+ valueKey(emitter, first.node)
1604
+ );
1605
+ }
1606
+ }
1607
+ function partsTakenApart(target) {
1608
+ const parts = [];
1609
+ if (!PAIR_TARGET_TYPES.has(target.type)) {
1610
+ return parts;
1611
+ }
1612
+ for (const [position, part] of children(target).entries()) {
1613
+ if (part.type === "list_splat_pattern") {
1614
+ break;
1615
+ }
1616
+ if (part.type === "identifier") {
1617
+ parts.push({ node: part, part: String(position) });
1618
+ }
1619
+ }
1620
+ return parts;
1621
+ }
1622
+ function countedCollection(emitter, over) {
1623
+ const callee = over.type === "call" ? field(over, "function") : null;
1624
+ const args = field(over, "arguments");
1625
+ const [first] = callArguments(over);
1626
+ if (callee?.type !== "identifier" || callee.text !== POSITION_COUNTER || !isBuiltin(emitter, POSITION_COUNTER) || args === null || writtenArgumentCount(args) !== 1 || first?.kind !== "positional") {
1627
+ return null;
1628
+ }
1629
+ return first.node;
1630
+ }
1631
+ function interpolatedPieces(emitter, string2) {
1632
+ const parts = children(string2);
1633
+ if (!parts.some((part) => part.type === "interpolation")) {
1634
+ return [];
1635
+ }
1636
+ return parts.flatMap((part) => {
1637
+ if (part.type === "string_content") {
1638
+ return [{ text: stringContentValue(part) }];
1639
+ }
1640
+ const expression = part.type === "interpolation" ? field(part, "expression") ?? children(part)[0] : void 0;
1641
+ return expression === void 0 ? [] : [{ hole: valueKey(emitter, expression) }];
1642
+ });
1643
+ }
1644
+ function adjacentPieces(emitter, joined2) {
1645
+ return children(joined2).filter((part) => part.type !== "comment").map((part) => ({ hole: valueKey(emitter, part) }));
1646
+ }
1647
+ var TEMPLATE_PIECES = {
1648
+ string: interpolatedPieces,
1649
+ concatenated_string: adjacentPieces
1650
+ };
1513
1651
  function emitExpressionFact(emitter, child, type) {
1514
1652
  if (type === "call") {
1515
1653
  emitCall(emitter, child);
@@ -1517,6 +1655,7 @@ function emitExpressionFact(emitter, child, type) {
1517
1655
  if (mapping !== null) {
1518
1656
  emitKeyedRead(emitter, child, mapping.container, mapping.key);
1519
1657
  }
1658
+ emitAttributeChoice(emitter, child);
1520
1659
  }
1521
1660
  if (type === "subscript") {
1522
1661
  const index = singleSubscript(child);
@@ -1546,6 +1685,13 @@ function emitExpressionFact(emitter, child, type) {
1546
1685
  if (COMPREHENSION_TYPES.has(type)) {
1547
1686
  emitComprehensionElement(emitter, child);
1548
1687
  }
1688
+ if (type === "for_in_clause") {
1689
+ emitElementVariables(emitter, child);
1690
+ }
1691
+ const pieces = TEMPLATE_PIECES[type]?.(emitter, child) ?? [];
1692
+ if (pieces.length > 0) {
1693
+ (0, import_resolution3.addTemplateRows)(rowsOf(emitter), nodeId(emitter.filePath, child), pieces);
1694
+ }
1549
1695
  const branches = fallbackBranchesOf(child, type);
1550
1696
  if (branches !== null) {
1551
1697
  for (const branch of branches) {
@@ -2064,6 +2210,9 @@ function readUnderName(emitter, statement, funcKey, accessed) {
2064
2210
  function emitNestedDefinitions(emitter, node) {
2065
2211
  for (const child of children(node)) {
2066
2212
  const type = child.type;
2213
+ if (type === "decorated_definition") {
2214
+ emitDecoratorCalls(emitter, child);
2215
+ }
2067
2216
  if (FUNCTION_TYPES.has(type)) {
2068
2217
  emitFunctionFacts(emitter, child);
2069
2218
  if (emitter.enclosing !== null) {
@@ -2083,6 +2232,20 @@ function emitNestedDefinitions(emitter, node) {
2083
2232
  emitNestedDefinitions(emitter, child);
2084
2233
  }
2085
2234
  }
2235
+ function emitDecoratorCalls(emitter, decorated) {
2236
+ const { definition, decorators } = stripDecorators(decorated);
2237
+ let applied = nodeId(emitter.filePath, definition);
2238
+ for (const decorator of [...decorators].reverse()) {
2239
+ const [expression] = children(decorator);
2240
+ if (expression === void 0) {
2241
+ continue;
2242
+ }
2243
+ const callKey = nodeId(emitter.filePath, decorator);
2244
+ add(emitter, "call", callKey, valueKey(emitter, expression));
2245
+ add(emitter, "callArg", callKey, "0", applied);
2246
+ applied = callKey;
2247
+ }
2248
+ }
2086
2249
  var readingsByTree = /* @__PURE__ */ new WeakMap();
2087
2250
  function scopeReadingOf(fn, given) {
2088
2251
  const tree = fn.tree;
@@ -4783,13 +4946,37 @@ function modelColumn(node) {
4783
4946
  }
4784
4947
  return field(node, "attribute")?.text ?? null;
4785
4948
  }
4786
- function selectorOf(chain, valueMethods) {
4949
+ function selectorOf(chain, valueMethods, model) {
4950
+ const picking = laterCalls(chain).filter(
4951
+ (call) => !valueMethods.includes(methodNameOf(call))
4952
+ );
4787
4953
  return [
4788
- ...new Set(
4789
- laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap((call) => pickedBy(call).map((one) => one.name))
4790
- )
4954
+ .../* @__PURE__ */ new Set([
4955
+ ...picking.flatMap((call) => pickedBy(call).map((one) => one.name)),
4956
+ ...model === null ? [] : picking.flatMap((call) => columnsComparedBy(call, model))
4957
+ ])
4791
4958
  ];
4792
4959
  }
4960
+ var COMBINED_TESTS = ["and_", "or_", "not_"];
4961
+ function columnsComparedBy(call, model) {
4962
+ const args = field(call, "arguments");
4963
+ return (args === null ? [] : children(args)).flatMap(
4964
+ (argument) => columnsCompared(argument, model)
4965
+ );
4966
+ }
4967
+ function columnsCompared(node, model) {
4968
+ if (node.type === "call" && COMBINED_TESTS.includes(methodNameOf(node))) {
4969
+ return columnsComparedBy(node, model);
4970
+ }
4971
+ if (node.type !== "comparison_operator") {
4972
+ return [];
4973
+ }
4974
+ return children(node).flatMap((side) => {
4975
+ const object = side.type === "attribute" ? field(side, "object") : null;
4976
+ const column = object?.text === model ? field(side, "attribute") : null;
4977
+ return column === null ? [] : [column.text];
4978
+ });
4979
+ }
4793
4980
  var TABLE_METHODS = ["select_from", "join"];
4794
4981
  function modelOf(chain, options) {
4795
4982
  const fromRoot = modelArgument(chain.root, chain.root, options);
@@ -4854,14 +5041,15 @@ function isClassName(name) {
4854
5041
  }
4855
5042
  function effectFor2(pattern, chain, operation, options) {
4856
5043
  const valueMethods = pattern.valueMethods ?? [];
4857
- const picked = selectorOf(chain, valueMethods);
5044
+ const model = modelOf(chain, options);
5045
+ const picked = selectorOf(chain, valueMethods, model);
4858
5046
  const effect = {
4859
5047
  type: "interaction",
4860
5048
  binding: (0, import_ir_core2.storageBinding)({
4861
5049
  recognition: "python-storage",
4862
5050
  storageSystem: pattern.storageSystem,
4863
5051
  scope: "default",
4864
- container: modelOf(chain, options)
5052
+ container: model
4865
5053
  }),
4866
5054
  callee: chain.last.text,
4867
5055
  interaction: {
@@ -6592,7 +6780,7 @@ function isStdlibModule(node, scope, module2) {
6592
6780
  const binding = resolveName(scope, node.text);
6593
6781
  return binding?.kind === "import" && binding.module === module2 && binding.relativeLevel === 0;
6594
6782
  }
6595
- function isBuiltin(node, scope, name) {
6783
+ function isBuiltin2(node, scope, name) {
6596
6784
  return node.type === "identifier" && node.text === name && resolveName(scope, name) === null;
6597
6785
  }
6598
6786
  function isStdlibMember(node, scope, module2, name) {
@@ -6640,7 +6828,7 @@ function streamWriteAt(call, scope) {
6640
6828
  }
6641
6829
  const written = callArguments(call);
6642
6830
  const positional = written.filter((argument) => argument.kind === "positional").map((argument) => argument.node);
6643
- if (callee.type === "identifier" && isBuiltin(callee, scope, "print")) {
6831
+ if (callee.type === "identifier" && isBuiltin2(callee, scope, "print")) {
6644
6832
  const file = written.find(
6645
6833
  (argument) => argument.kind === "keyword" && argument.name === "file"
6646
6834
  );
@@ -8129,12 +8317,12 @@ function exitSiteAt(statement, scope) {
8129
8317
  return endsTheProcess(callee, scope) ? { statement, code, raised: false } : null;
8130
8318
  }
8131
8319
  if (statement.type === "raise_statement") {
8132
- return isBuiltin(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
8320
+ return isBuiltin2(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
8133
8321
  }
8134
8322
  return null;
8135
8323
  }
8136
8324
  function endsTheProcess(callee, scope) {
8137
- return isStdlibMember(callee, scope, "sys", "exit") || isBuiltin(callee, scope, "exit") || isBuiltin(callee, scope, "quit");
8325
+ return isStdlibMember(callee, scope, "sys", "exit") || isBuiltin2(callee, scope, "exit") || isBuiltin2(callee, scope, "quit");
8138
8326
  }
8139
8327
  function firstArgument(call) {
8140
8328
  return callArguments(call).find(
@@ -8438,6 +8626,40 @@ function instancesHaveAttribute(facts, classKey, name) {
8438
8626
  function namedOutcomes(keys, ctx) {
8439
8627
  return (0, import_resolution11.calleeOutcomes)(ctx.facts, [...new Set(keys)]);
8440
8628
  }
8629
+ function dispatchedCalls(calls, ctx) {
8630
+ const byKey = new Map(
8631
+ calls.map(({ call, site }) => [nodeId(site.file.file, call), call])
8632
+ );
8633
+ const found = /* @__PURE__ */ new Map();
8634
+ for (const [key, outcome] of (0, import_resolution11.dispatchOutcomes)(ctx.facts, [...byKey.keys()])) {
8635
+ const call = byKey.get(key);
8636
+ if (call === void 0) {
8637
+ continue;
8638
+ }
8639
+ const targets = /* @__PURE__ */ new Map();
8640
+ for (const fn of outcome.functions) {
8641
+ for (const target of reachedThrough(fn, outcome.through, ctx)) {
8642
+ targets.set(nodeId(target.file.file, target.node), target);
8643
+ }
8644
+ }
8645
+ found.set(call.id, {
8646
+ targets: [...targets.values()],
8647
+ certain: outcome.certain && outcome.functions.every((fn) => !isLambda(fn, ctx)),
8648
+ unresolved: outcome.unresolved
8649
+ });
8650
+ }
8651
+ return found;
8652
+ }
8653
+ function reachedThrough(key, through, ctx) {
8654
+ const keys = isLambda(key, ctx) ? through.get(key) ?? [] : [key];
8655
+ return keys.flatMap((one) => {
8656
+ const target = isLambda(one, ctx) ? null : functionAt(one, ctx);
8657
+ return target === null ? [] : [target];
8658
+ });
8659
+ }
8660
+ function isLambda(key, ctx) {
8661
+ return ctx.definitions.get(key)?.type === "lambda";
8662
+ }
8441
8663
  function functionNamed(nameKey2, ctx, read) {
8442
8664
  if (assignedElsewhere(ctx.facts, nameKey2)) {
8443
8665
  return null;
@@ -8656,6 +8878,7 @@ function reachedFunctions(seeds, options) {
8656
8878
  const targetsByKey = /* @__PURE__ */ new Map();
8657
8879
  const stopsByKey = /* @__PURE__ */ new Map();
8658
8880
  const argTargetsByKey = /* @__PURE__ */ new Map();
8881
+ const possibleTargetsByKey = /* @__PURE__ */ new Map();
8659
8882
  const parameterCallsByKey = /* @__PURE__ */ new Map();
8660
8883
  const callsByKey = /* @__PURE__ */ new Map();
8661
8884
  const passedPositions = /* @__PURE__ */ new Set();
@@ -8718,6 +8941,7 @@ function reachedFunctions(seeds, options) {
8718
8941
  }
8719
8942
  targetsByKey.set(key, scan.targets);
8720
8943
  argTargetsByKey.set(key, scan.argTargets);
8944
+ possibleTargetsByKey.set(key, scan.possibleTargets);
8721
8945
  if (scan.parameterCalls.length > 0) {
8722
8946
  parameterCallsByKey.set(key, scan.parameterCalls);
8723
8947
  }
@@ -8768,6 +8992,7 @@ function reachedFunctions(seeds, options) {
8768
8992
  }
8769
8993
  (0, import_behavioral_ir7.placeCalls)(summary2, targetsByKey.get(key));
8770
8994
  (0, import_behavioral_ir7.placeArgTargets)(summary2, argTargetsByKey.get(key));
8995
+ (0, import_behavioral_ir7.placePossibleCalls)(summary2, possibleTargetsByKey.get(key));
8771
8996
  (0, import_behavioral_ir7.placeCalleeParameters)(summary2, parameterCallsByKey.get(key));
8772
8997
  return summary2;
8773
8998
  };
@@ -8791,6 +9016,7 @@ function reachedFunctions(seeds, options) {
8791
9016
  targetsByKey,
8792
9017
  stopsByKey,
8793
9018
  argTargetsByKey,
9019
+ possibleTargetsByKey,
8794
9020
  parameterCallsByKey,
8795
9021
  callsByKey,
8796
9022
  passedPositions,
@@ -8814,24 +9040,42 @@ function chargeOf(charges, key) {
8814
9040
  }
8815
9041
  return charge;
8816
9042
  }
9043
+ function dispatchedAmong(written, spellings, ctx) {
9044
+ return dispatchedCalls(
9045
+ written.filter(({ call, site }) => {
9046
+ if (!ENTRY_CALLEE_TYPES.has(field(call, "function")?.type ?? "")) {
9047
+ return false;
9048
+ }
9049
+ const outcome = resolveCallee2(call, site, ctx, spellings);
9050
+ return outcome.kind === "stopped" && outcome.reason !== "callerSupplied";
9051
+ }),
9052
+ ctx
9053
+ );
9054
+ }
9055
+ var ENTRY_CALLEE_TYPES = /* @__PURE__ */ new Set(["subscript", "call", "identifier"]);
8817
9056
  function batchedReads(round, ctx) {
8818
9057
  const bodies = round.flatMap(
8819
9058
  (entry) => "source" in entry ? [{ key: entry.key, source: entry.source, ...bodyOf(entry.source) }] : []
8820
9059
  );
8821
- const spellings = calleeSpellings(
8822
- bodies.flatMap((body) => body.written),
8823
- ctx
8824
- );
9060
+ const written = bodies.flatMap((body) => body.written);
9061
+ const spellings = calleeSpellings(written, ctx);
8825
9062
  const passed = namedOutcomes(
8826
9063
  bodies.flatMap(
8827
- ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
9064
+ ({ source, written: written2 }) => written2.flatMap(({ call }) => passedNameKeys(source.file, call))
8828
9065
  ),
8829
9066
  ctx
8830
9067
  );
9068
+ const dispatched = dispatchedAmong(written, spellings, ctx);
8831
9069
  return new Map(
8832
9070
  bodies.map((body) => [
8833
9071
  body.key,
8834
- { body: body.body, written: body.written, spellings, passed }
9072
+ {
9073
+ body: body.body,
9074
+ written: body.written,
9075
+ spellings,
9076
+ passed,
9077
+ dispatched
9078
+ }
8835
9079
  ])
8836
9080
  );
8837
9081
  }
@@ -8862,13 +9106,15 @@ function scanOnce(source, key, ctx, options, read, charges) {
8862
9106
  }
8863
9107
  function ownReads(source, ctx) {
8864
9108
  const body = bodyOf(source);
9109
+ const spellings = calleeSpellings(body.written, ctx);
8865
9110
  return {
8866
9111
  ...body,
8867
- spellings: calleeSpellings(body.written, ctx),
9112
+ spellings,
8868
9113
  passed: namedOutcomes(
8869
9114
  body.written.flatMap(({ call }) => passedNameKeys(source.file, call)),
8870
9115
  ctx
8871
- )
9116
+ ),
9117
+ dispatched: dispatchedAmong(body.written, spellings, ctx)
8872
9118
  };
8873
9119
  }
8874
9120
  function storedScan(scan) {
@@ -8884,6 +9130,10 @@ function storedScan(scan) {
8884
9130
  callee,
8885
9131
  [...byPosition]
8886
9132
  ]),
9133
+ possibleTargets: [...scan.possibleTargets].map(([callee, places]) => [
9134
+ callee,
9135
+ [...places]
9136
+ ]),
8887
9137
  parameterCalls: [...scan.parameterCalls],
8888
9138
  passedPositions: [...scan.passedPositions]
8889
9139
  };
@@ -8899,6 +9149,7 @@ function scanFromStored(stored, functionFor) {
8899
9149
  new Map(byPosition)
8900
9150
  ])
8901
9151
  ),
9152
+ possibleTargets: new Map(stored.possibleTargets),
8902
9153
  parameterCalls: stored.parameterCalls,
8903
9154
  passedPositions: new Set(stored.passedPositions)
8904
9155
  };
@@ -8927,6 +9178,7 @@ var EMPTY_SCAN = {
8927
9178
  stops: [],
8928
9179
  targets: /* @__PURE__ */ new Map(),
8929
9180
  argTargets: /* @__PURE__ */ new Map(),
9181
+ possibleTargets: /* @__PURE__ */ new Map(),
8930
9182
  parameterCalls: [],
8931
9183
  passedPositions: /* @__PURE__ */ new Set()
8932
9184
  };
@@ -9003,12 +9255,25 @@ function scanBody(source, ctx, recognized, read, implied) {
9003
9255
  return EMPTY_SCAN;
9004
9256
  }
9005
9257
  const ownParameters = callParameterNames(node, source.exportPath.length > 1);
9258
+ const follow = (target) => {
9259
+ const key = keyOf2(target);
9260
+ if (!seen.has(key)) {
9261
+ seen.add(key);
9262
+ followed.push(target);
9263
+ }
9264
+ };
9006
9265
  const recordPassedArgs = (call, callee, calleeKey2) => {
9007
9266
  const args = field(call, "arguments");
9008
9267
  if (args === null) {
9009
9268
  return;
9010
9269
  }
9011
9270
  children(args).forEach((arg, position) => {
9271
+ if (arg.type === "lambda") {
9272
+ if (calleeKey2 !== null) {
9273
+ passedPositions.add(`${calleeKey2}#${position}`);
9274
+ }
9275
+ return;
9276
+ }
9012
9277
  const nameKey2 = passedNameKeyOf(file, arg);
9013
9278
  if (nameKey2 === null) {
9014
9279
  return;
@@ -9017,11 +9282,7 @@ function scanBody(source, ctx, recognized, read, implied) {
9017
9282
  if (resolved === null) {
9018
9283
  return;
9019
9284
  }
9020
- const key = keyOf2(resolved);
9021
- if (!seen.has(key)) {
9022
- seen.add(key);
9023
- followed.push(resolved);
9024
- }
9285
+ follow(resolved);
9025
9286
  placements.placeArg(callee, position, {
9026
9287
  file: resolved.file.displayPath,
9027
9288
  span: spanOf(resolved.node)
@@ -9031,9 +9292,30 @@ function scanBody(source, ctx, recognized, read, implied) {
9031
9292
  }
9032
9293
  });
9033
9294
  };
9295
+ const recordPossible = (call, callee, dispatched) => {
9296
+ dispatched.targets.forEach(follow);
9297
+ placements.placePossible(
9298
+ callee,
9299
+ dispatched.targets.map((target) => ({
9300
+ file: target.file.displayPath,
9301
+ span: spanOf(target.node)
9302
+ }))
9303
+ );
9304
+ recordPassedArgs(call, callee, null);
9305
+ if (dispatched.unresolved > 0 && !recognized.has(call.id)) {
9306
+ stops.add({ callee, reason: "unsettledValue" });
9307
+ }
9308
+ };
9034
9309
  const record = (call, site) => {
9035
9310
  const callee = calleeText(call);
9036
- const outcome = resolveCallee2(call, site, ctx, spellings);
9311
+ const settled = resolveCallee2(call, site, ctx, spellings);
9312
+ const dispatched = settled.kind === "stopped" ? read.dispatched.get(call.id) : void 0;
9313
+ const [only] = dispatched?.targets ?? [];
9314
+ if (dispatched !== void 0 && only !== void 0 && !dispatched.certain) {
9315
+ recordPossible(call, callee, dispatched);
9316
+ return;
9317
+ }
9318
+ const outcome = only === void 0 ? settled : { kind: "followed", target: only };
9037
9319
  placeCallee(placements, callee, outcome, call, file);
9038
9320
  recordPassedArgs(
9039
9321
  call,
@@ -9057,11 +9339,7 @@ function scanBody(source, ctx, recognized, read, implied) {
9057
9339
  }
9058
9340
  return;
9059
9341
  }
9060
- const key = keyOf2(outcome.target);
9061
- if (!seen.has(key)) {
9062
- seen.add(key);
9063
- followed.push(outcome.target);
9064
- }
9342
+ follow(outcome.target);
9065
9343
  };
9066
9344
  for (const { call, site } of written) {
9067
9345
  record(call, site);
@@ -9071,17 +9349,14 @@ function scanBody(source, ctx, recognized, read, implied) {
9071
9349
  file: target.file.displayPath,
9072
9350
  span: spanOf(target.node)
9073
9351
  });
9074
- const key = keyOf2(target);
9075
- if (!seen.has(key)) {
9076
- seen.add(key);
9077
- followed.push(target);
9078
- }
9352
+ follow(target);
9079
9353
  }
9080
9354
  return {
9081
9355
  followed,
9082
9356
  stops: stops.stops,
9083
9357
  targets: placements.targets,
9084
9358
  argTargets: placements.argTargets,
9359
+ possibleTargets: placements.possibleTargets,
9085
9360
  parameterCalls,
9086
9361
  passedPositions
9087
9362
  };
@@ -9405,6 +9680,10 @@ var PackPositions = class {
9405
9680
  const [packAt, patternAt] = id.split("/").map(Number);
9406
9681
  return this.packs[packAt ?? -1]?.discovery[patternAt ?? -1];
9407
9682
  }
9683
+ packOf(id) {
9684
+ const [packAt] = id.split("/").map(Number);
9685
+ return this.packs[packAt ?? -1];
9686
+ }
9408
9687
  formAt(at) {
9409
9688
  const pack = this.packs[at[0]];
9410
9689
  const pattern = pack?.discovery[at[1]];
@@ -11499,7 +11778,10 @@ var PythonWrapperIndex = class {
11499
11778
  /** By wrapper reference: the parameters a dependency function declares, and its file. */
11500
11779
  dependencyParameterTable = /* @__PURE__ */ new Map();
11501
11780
  unitsByFile = /* @__PURE__ */ new Map();
11502
- /** Told about every registration, so a cache can repeat the ones a file's discovery made. */
11781
+ /**
11782
+ * Told about every registration, a pack's form or a project decorator's
11783
+ * closure, so a cache can repeat the ones a file's discovery made.
11784
+ */
11503
11785
  onRegistered = null;
11504
11786
  /**
11505
11787
  * The same lookup `wrappersFor` noted under `id`, made again over this
@@ -11580,6 +11862,25 @@ var PythonWrapperIndex = class {
11580
11862
  }
11581
11863
  return { reference, form: declared.form };
11582
11864
  }
11865
+ /**
11866
+ * The unit for the closure a project's decorator hands back, built once
11867
+ * however many routes the decorator is written on. Its name is what
11868
+ * calling the decorator writes, since the decorator may have a summary
11869
+ * of its own under its name.
11870
+ */
11871
+ registeredClosure(target, closure) {
11872
+ this.onRegistered?.(target, closure);
11873
+ const span = spanOf(target.node);
11874
+ const key = `${target.file.file}:${span.start}-${span.end}`;
11875
+ if (!this.unitsByKey.has(key)) {
11876
+ const unit = closureUnit(target, closure, this.options);
11877
+ this.unitsByKey.set(key, unit);
11878
+ const inFile = this.unitsByFile.get(target.file.file) ?? [];
11879
+ inFile.push(unit);
11880
+ this.unitsByFile.set(target.file.file, inFile);
11881
+ }
11882
+ return { file: target.file.displayPath, name: target.name };
11883
+ }
11583
11884
  /**
11584
11885
  * The parameters the dependencies among these wrappers declare, and
11585
11886
  * whether one is declared through a parameter source such as `Query()`.
@@ -11611,9 +11912,13 @@ var PythonWrapperIndex = class {
11611
11912
  pattern: query.pattern,
11612
11913
  form
11613
11914
  }));
11614
- if (file === void 0 || forms.length === 0) {
11915
+ if (file === void 0) {
11615
11916
  return [];
11616
11917
  }
11918
+ const decorators = this.decoratorLayers(query, file);
11919
+ if (forms.length === 0) {
11920
+ return decorators;
11921
+ }
11617
11922
  const own = this.ownRoutesOf(query);
11618
11923
  const found = [
11619
11924
  ...this.noted(
@@ -11651,7 +11956,68 @@ var PythonWrapperIndex = class {
11651
11956
  seen.add(key);
11652
11957
  ordered.push(wrapper.reference);
11653
11958
  }
11654
- return ordered;
11959
+ return [
11960
+ ...ordered.filter((one) => one.onThrow !== true),
11961
+ ...decorators,
11962
+ ...ordered.filter((one) => one.onThrow === true)
11963
+ ];
11964
+ }
11965
+ /**
11966
+ * The decorators written on the route's function below the one that
11967
+ * registers it, outermost first. Each wraps the function the library
11968
+ * calls. None has a summary: a library's has no body in the run, and a
11969
+ * project's own is not composed into the route yet, so a project
11970
+ * decorator goes by what calling it writes, `require_admin(fn)`, which
11971
+ * no summary is named. The decorators the packs read, such as the
11972
+ * route's own and the namespace's `@ns.doc`, are left out.
11973
+ */
11974
+ decoratorLayers(query, file) {
11975
+ const decorated = query.definitionNode.parent;
11976
+ if (decorated === null || decorated.type !== "decorated_definition") {
11977
+ return [];
11978
+ }
11979
+ const written = stripDecorators(decorated).decorators;
11980
+ const routeAt = written.findIndex(
11981
+ (decorator) => sameRange(rangeOf(decorator), query.classification.range)
11982
+ );
11983
+ return written.slice(routeAt + 1).flatMap((decorator) => this.decoratorLayer(decorator, query, file));
11984
+ }
11985
+ decoratorLayer(decorator, query, file) {
11986
+ const facts = this.options.facts;
11987
+ const direct = classifyDecorator(decorator, query.module, facts);
11988
+ const written = decoratorCallee(decorator);
11989
+ if (written === null || direct.module !== null && readByAPack(direct.module, this.options) || direct.objectName !== null && direct.objectName === query.classification.objectName || unwrapDecorator(decorator, facts) !== null) {
11990
+ return [];
11991
+ }
11992
+ const project = written.type === "identifier" ? this.functionCalled(file.file, written.text) : null;
11993
+ if (project !== null) {
11994
+ if (facts !== void 0) {
11995
+ (0, import_resolution17.noteKeyRead)(facts, project.file.file);
11996
+ }
11997
+ return [this.projectDecoratorLayer(project, query)];
11998
+ }
11999
+ return [{ file: direct.module ?? file.displayPath, name: written.text }];
12000
+ }
12001
+ /**
12002
+ * The layer a project's own decorator puts around the route. When it
12003
+ * hands back a closure written inside it that returns what calling the
12004
+ * function it was given returns, the closure is summarized and
12005
+ * composed into the route. Any other decorator, including one whose
12006
+ * closure keeps going after that call, goes by the same name with no
12007
+ * summary behind it.
12008
+ */
12009
+ projectDecoratorLayer(decorator, query) {
12010
+ const continuation = parameterNamesOf(decorator.node)[0] ?? "";
12011
+ const name = `${decorator.name}(${continuation})`;
12012
+ const closure = closureReturnedBy(decorator.node);
12013
+ const handsOn = closure === null || continuation === "" ? [] : callsThrough(closure, continuation);
12014
+ if (closure === null || handsOn.length === 0 || !handsOn.every((statement) => returnsCallTo(statement, continuation))) {
12015
+ return { file: decorator.file.displayPath, name };
12016
+ }
12017
+ return this.registeredClosure(
12018
+ { file: decorator.file, node: closure, name, exportPath: [] },
12019
+ { pack: query.pack, pattern: query.pattern, continuation }
12020
+ );
11655
12021
  }
11656
12022
  /**
11657
12023
  * What the mounts above the route's router register on the way down:
@@ -11812,6 +12178,21 @@ var PythonWrapperIndex = class {
11812
12178
  function referenceKey(reference) {
11813
12179
  return `${reference.file}::${reference.name}`;
11814
12180
  }
12181
+ function readByAPack(module2, options) {
12182
+ return options.packs.some(
12183
+ (pack) => pack.discovery.some((pattern) => pattern.importModule.includes(module2))
12184
+ );
12185
+ }
12186
+ function decoratorCallee(decorator) {
12187
+ const written = decorator.namedChild(0);
12188
+ if (written === null) {
12189
+ return null;
12190
+ }
12191
+ return written.type === "call" ? field(written, "function") : written;
12192
+ }
12193
+ function sameRange(a, b) {
12194
+ return a.start === b.start && a.end === b.end;
12195
+ }
11815
12196
  function parameterNamesOf(definition) {
11816
12197
  return children(field(definition, "parameters")).flatMap((parameter) => {
11817
12198
  const named = parameterNameAndType(parameter);
@@ -11999,16 +12380,42 @@ function parameterDependencies(definitionNode, form) {
11999
12380
  return names;
12000
12381
  }
12001
12382
  function wrapperUnit(target, declared, options) {
12383
+ return unitAround(
12384
+ target,
12385
+ declared.pattern,
12386
+ {
12387
+ continuations: continuationStatements(target.node, declared.form),
12388
+ continuationName: null,
12389
+ form: declared.form,
12390
+ nameKind: "binding"
12391
+ },
12392
+ options
12393
+ );
12394
+ }
12395
+ function closureUnit(target, closure, options) {
12396
+ return unitAround(
12397
+ target,
12398
+ closure.pattern,
12399
+ {
12400
+ continuations: callsThrough(target.node, closure.continuation),
12401
+ continuationName: closure.continuation,
12402
+ form: null,
12403
+ nameKind: "label"
12404
+ },
12405
+ options
12406
+ );
12407
+ }
12408
+ function unitAround(target, pattern, reading, options) {
12002
12409
  const { file, node, name, exportPath } = target;
12003
12410
  const body = field(node, "body");
12004
12411
  const range = rangeOf(node);
12005
12412
  const raised = raisedResponses(body, {
12006
- calls: declared.pattern.responseStatusCalls ?? [],
12413
+ calls: pattern.responseStatusCalls ?? [],
12007
12414
  module: file.module,
12008
12415
  facts: options.facts,
12009
- constants: declared.pattern.statusCodeConstants ?? {}
12416
+ constants: pattern.statusCodeConstants ?? {}
12010
12417
  });
12011
- const continuations = continuationStatements(node, declared.form);
12418
+ const continuations = reading.continuations;
12012
12419
  const terminals = bodyTerminals(body, raised, continuations);
12013
12420
  const effects = invocationEffects(node, options.facts);
12014
12421
  const extra = recognizedBodyEffects(
@@ -12017,7 +12424,7 @@ function wrapperUnit(target, declared, options) {
12017
12424
  options.storageFor(file),
12018
12425
  options.facts
12019
12426
  );
12020
- const delegate = (at) => ({
12427
+ const delegate = (at, to = null) => ({
12021
12428
  terminal: {
12022
12429
  kind: "delegate",
12023
12430
  statusCode: null,
@@ -12026,18 +12433,18 @@ function wrapperUnit(target, declared, options) {
12026
12433
  message: null,
12027
12434
  component: null,
12028
12435
  renderTree: null,
12029
- delegateTarget: null,
12436
+ delegateTarget: to,
12030
12437
  emitEvent: null,
12031
12438
  location: rangeOf(at)
12032
12439
  },
12033
12440
  location: rangeOf(at)
12034
12441
  });
12035
12442
  const respond = (statement) => returnBranch(statement, {
12036
- pattern: declared.pattern,
12443
+ pattern,
12037
12444
  responseShape: { reading: import_extractor17.absentReading },
12038
12445
  declaredStatus: {
12039
12446
  reading: import_extractor17.absentReading,
12040
- ...declared.pattern.defaultStatusCode !== void 0 ? { libraryDefault: declared.pattern.defaultStatusCode } : {}
12447
+ ...pattern.defaultStatusCode !== void 0 ? { libraryDefault: pattern.defaultStatusCode } : {}
12041
12448
  },
12042
12449
  module: file.module,
12043
12450
  facts: options.facts
@@ -12049,7 +12456,10 @@ function wrapperUnit(target, declared, options) {
12049
12456
  location: rangeOf(terminal.statement)
12050
12457
  };
12051
12458
  }
12052
- if (terminal.type === "continuation" || returnHandsOn(terminal.statement, declared.form)) {
12459
+ if (terminal.type === "continuation") {
12460
+ return delegate(terminal.statement, reading.continuationName);
12461
+ }
12462
+ if (reading.form !== null && returnHandsOn(terminal.statement, reading.form)) {
12053
12463
  return delegate(terminal.statement);
12054
12464
  }
12055
12465
  return respond(terminal.statement);
@@ -12066,7 +12476,7 @@ function wrapperUnit(target, declared, options) {
12066
12476
  return {
12067
12477
  identity: {
12068
12478
  name,
12069
- nameKind: "binding",
12479
+ nameKind: reading.nameKind,
12070
12480
  kind: "middleware",
12071
12481
  file: file.displayPath,
12072
12482
  range,
@@ -12102,23 +12512,45 @@ function continuationStatements(node, form) {
12102
12512
  const params = children(field(node, "parameters"));
12103
12513
  const param = params[form.continuationParam];
12104
12514
  const continuation = param === void 0 ? null : field(param, "name")?.text ?? (param.type === "identifier" ? param.text : null);
12105
- if (continuation === null) {
12106
- return [];
12107
- }
12515
+ return continuation === null ? [] : callsThrough(node, continuation);
12516
+ }
12517
+ function callsThrough(node, callee) {
12108
12518
  const body = field(node, "body");
12109
12519
  if (body === null) {
12110
12520
  return [];
12111
12521
  }
12112
12522
  const found = [];
12113
12523
  for (const call of bodyCalls(body)) {
12114
- const callee = field(call, "function");
12115
- const statement = callee?.type === "identifier" && callee.text === continuation ? enclosingStatement(call, body) : null;
12524
+ const called = field(call, "function");
12525
+ const statement = called?.type === "identifier" && called.text === callee ? enclosingStatement(call, body) : null;
12116
12526
  if (statement !== null) {
12117
12527
  found.push(statement);
12118
12528
  }
12119
12529
  }
12120
12530
  return found;
12121
12531
  }
12532
+ function returnsCallTo(statement, callee) {
12533
+ if (statement.type !== "return_statement") {
12534
+ return false;
12535
+ }
12536
+ const value = statement.namedChild(0);
12537
+ const call = value?.type === "await" ? value.namedChild(0) : value;
12538
+ return call?.type === "call" && field(call, "function")?.text === callee;
12539
+ }
12540
+ function closureReturnedBy(decorator) {
12541
+ const statements = children(field(decorator, "body"));
12542
+ const returned = statements.find((statement) => statement.type === "return_statement")?.namedChild(0);
12543
+ if (returned?.type !== "identifier") {
12544
+ return null;
12545
+ }
12546
+ for (const statement of statements) {
12547
+ const { definition } = stripDecorators(statement);
12548
+ if (definition.type === "function_definition" && field(definition, "name")?.text === returned.text) {
12549
+ return definition;
12550
+ }
12551
+ }
12552
+ return null;
12553
+ }
12122
12554
 
12123
12555
  // src/project.ts
12124
12556
  function keptPythonParses() {
@@ -12213,7 +12645,7 @@ async function extractPythonProject(options) {
12213
12645
  const { roots, unreadManifests } = rootsOfRun(options);
12214
12646
  const timer = options.onTiming !== void 0 ? (0, import_extractor18.createTimer)() : (0, import_extractor18.noopTimer)();
12215
12647
  const cacheDir = adapterStamp.declineWhenRunFromSource(
12216
- options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? import_node_path5.default.join(options.projectRoot, ".suss", "cache") : null)
12648
+ options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? (0, import_extractor18.extractionCacheDirFor)(options.projectRoot) : null)
12217
12649
  );
12218
12650
  const layer = (0, import_extractor18.createCacheLayer)(cacheDir);
12219
12651
  const packsDigest = `${adapterStamp.packsDigest(
@@ -12236,11 +12668,14 @@ async function extractPythonProject(options) {
12236
12668
  "cache.lookup",
12237
12669
  () => layer.lookup(input)
12238
12670
  );
12671
+ const composed = (stored) => (0, import_extractor18.composeWrappers)(stored, {
12672
+ gapHandling: options.gapHandling ?? "permissive"
12673
+ });
12239
12674
  if (lookup.kind === "hit") {
12240
12675
  options.onCacheDiagnostic?.(lookup.diagnostic);
12241
12676
  options.onTiming?.(timer.report());
12242
12677
  return {
12243
- summaries: lookup.summaries,
12678
+ summaries: composed(lookup.summaries),
12244
12679
  facts: new import_datalog2.Database(),
12245
12680
  roots,
12246
12681
  unreadManifests
@@ -12255,7 +12690,7 @@ async function extractPythonProject(options) {
12255
12690
  options.onCacheDiagnostic?.({ kind: "hit" });
12256
12691
  options.onTiming?.(timer.report());
12257
12692
  return {
12258
- summaries: plan.allSummaries(),
12693
+ summaries: composed(plan.allSummaries()),
12259
12694
  facts: new import_datalog2.Database(),
12260
12695
  roots,
12261
12696
  unreadManifests
@@ -12437,15 +12872,10 @@ async function runPython(options, roots, cache, previous) {
12437
12872
  });
12438
12873
  let registering = null;
12439
12874
  if (ledger !== null) {
12440
- wrapperIndex.onRegistered = (target, declared) => {
12441
- const form = positions.positionOf(declared);
12442
- if (registering !== null && form !== void 0) {
12443
- registering.push({
12444
- key: nodeId(target.file.file, target.node),
12445
- name: target.name,
12446
- exportPath: target.exportPath,
12447
- form
12448
- });
12875
+ wrapperIndex.onRegistered = (target, how) => {
12876
+ const stored = storedRegistration(target, how, positions);
12877
+ if (registering !== null && stored !== null) {
12878
+ registering.push(stored);
12449
12879
  }
12450
12880
  };
12451
12881
  }
@@ -12458,12 +12888,11 @@ async function runPython(options, roots, cache, previous) {
12458
12888
  if (record !== void 0) {
12459
12889
  discoveries.set(file, record);
12460
12890
  for (const registration of record.registrations) {
12461
- const declared = positions.formAt(registration.form);
12462
- const target = storedFunction(registration, definitions, filesByPath);
12463
- if (declared === void 0 || target === null) {
12464
- throw new ReplayFailed(registration.key);
12465
- }
12466
- wrapperIndex.registered(target, declared);
12891
+ registerAgain(registration, {
12892
+ positions,
12893
+ wrapperIndex,
12894
+ target: storedFunction(registration, definitions, filesByPath)
12895
+ });
12467
12896
  }
12468
12897
  continue;
12469
12898
  }
@@ -12632,6 +13061,7 @@ async function runPython(options, roots, cache, previous) {
12632
13061
  }
12633
13062
  (0, import_behavioral_ir10.placeCalls)(summary, reached.targetsByKey.get(key));
12634
13063
  (0, import_behavioral_ir10.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
13064
+ (0, import_behavioral_ir10.placePossibleCalls)(summary, reached.possibleTargetsByKey.get(key));
12635
13065
  (0, import_behavioral_ir10.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
12636
13066
  };
12637
13067
  for (const [key, owners] of summariesBySeed) {
@@ -12721,16 +13151,20 @@ async function runPython(options, roots, cache, previous) {
12721
13151
  markExitCodeFunctions(summaries, exits, db, options.workspaceRoot);
12722
13152
  return (0, import_extractor18.composeWrappers)(summaries, { gapHandling });
12723
13153
  });
13154
+ const walkedFiles = new Map(
13155
+ bound.map(({ file, displayPath }) => [displayPath, file])
13156
+ );
12724
13157
  await timer.timeAsync("cache.write", async () => {
12725
- if (cacheDir === null || composed.length === 0) {
13158
+ if (cacheDir === null || summaries.length === 0) {
12726
13159
  return;
12727
13160
  }
12728
13161
  try {
12729
13162
  await cache.layer.write(
12730
13163
  cache.input,
12731
- composed,
13164
+ summaries,
12732
13165
  log === null || ledger === null || overBudget ? void 0 : pythonAttribution({
12733
- composed,
13166
+ summaries,
13167
+ walkedFileOf: (locationFile) => walkedFiles.get(locationFile),
12734
13168
  discoveries,
12735
13169
  charges,
12736
13170
  unitDrafts,
@@ -12785,6 +13219,39 @@ async function runPython(options, roots, cache, previous) {
12785
13219
  options.onTiming?.(timer.report());
12786
13220
  return { summaries: composed, facts: db };
12787
13221
  }
13222
+ function storedRegistration(target, how, positions) {
13223
+ const at = {
13224
+ key: nodeId(target.file.file, target.node),
13225
+ name: target.name,
13226
+ exportPath: target.exportPath
13227
+ };
13228
+ if ("continuation" in how) {
13229
+ const pattern = positions.idOf(how.pattern);
13230
+ return pattern === void 0 ? null : { ...at, pattern, continuation: how.continuation };
13231
+ }
13232
+ const form = positions.positionOf(how);
13233
+ return form === void 0 ? null : { ...at, form };
13234
+ }
13235
+ function registerAgain(registration, context) {
13236
+ const { positions, wrapperIndex, target } = context;
13237
+ const how = howRegistered(registration, positions);
13238
+ if (target === null || how === null) {
13239
+ throw new ReplayFailed(registration.key);
13240
+ }
13241
+ if ("continuation" in how) {
13242
+ wrapperIndex.registeredClosure(target, how);
13243
+ return;
13244
+ }
13245
+ wrapperIndex.registered(target, how);
13246
+ }
13247
+ function howRegistered(registration, positions) {
13248
+ if (!("continuation" in registration)) {
13249
+ return positions.formAt(registration.form) ?? null;
13250
+ }
13251
+ const pack = positions.packOf(registration.pattern);
13252
+ const pattern = positions.patternOf(registration.pattern);
13253
+ return pack === void 0 || pattern === void 0 ? null : { pack, pattern, continuation: registration.continuation };
13254
+ }
12788
13255
  function pythonAttribution(args) {
12789
13256
  const walkedByFile = (0, import_resolution18.walkRecordsByFile)({
12790
13257
  scans: args.reached.scans,
@@ -12813,7 +13280,10 @@ function pythonAttribution(args) {
12813
13280
  }
12814
13281
  };
12815
13282
  });
12816
- return { roots, owners: args.composed.map(() => []) };
13283
+ return {
13284
+ roots,
13285
+ owners: (0, import_extractor18.ownersWhereWritten)(args.summaries, args.walkedFileOf)
13286
+ };
12817
13287
  }
12818
13288
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
12819
13289
  "__pycache__",