@suss/adapter-python 0.34.1 → 0.35.1

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
@@ -985,10 +985,14 @@ function objectReturnedBy(db, callKey) {
985
985
 
986
986
  // src/facts/values.ts
987
987
  import {
988
+ addEntryRows,
989
+ addTemplateRows,
988
990
  NAMED_STORE_NAME,
989
991
  RECEIVER_STORE_NAME,
992
+ SPREAD_ENTRY,
990
993
  startsAtName,
991
994
  UNPLACED_STORE_NAME,
995
+ VALUE_ENTRY,
992
996
  valueLeftByWrites,
993
997
  writesRunInOrder
994
998
  } from "@suss/resolution";
@@ -999,6 +1003,8 @@ var CONVERTING_BUILTINS = /* @__PURE__ */ new Set([
999
1003
  "str",
1000
1004
  "float"
1001
1005
  ]);
1006
+ var ATTRIBUTE_GETTER = "getattr";
1007
+ var POSITION_COUNTER = "enumerate";
1002
1008
 
1003
1009
  // src/facts/values.ts
1004
1010
  function nodeId(filePath, node) {
@@ -1170,8 +1176,7 @@ function emitConversion(emitter, call, callKey) {
1170
1176
  return;
1171
1177
  }
1172
1178
  const name = callee.text;
1173
- const builtin = nameKey(emitter.filePath, emitter.enclosing, name) === nameId(emitter.filePath, name) && !emitter.moduleNames().has(name);
1174
- if (builtin) {
1179
+ if (isBuiltin(emitter, name)) {
1175
1180
  add(emitter, "converts", callKey, valueKey(emitter, first.node), name);
1176
1181
  }
1177
1182
  }
@@ -1220,6 +1225,7 @@ function emitSequence(emitter, sequence) {
1220
1225
  const objectKey = nodeId(emitter.filePath, sequence);
1221
1226
  add(emitter, "objectValue", objectKey);
1222
1227
  let position = 0;
1228
+ const entries = [];
1223
1229
  for (const element of children(sequence)) {
1224
1230
  add(
1225
1231
  emitter,
@@ -1229,12 +1235,21 @@ function emitSequence(emitter, sequence) {
1229
1235
  valueKey(emitter, element)
1230
1236
  );
1231
1237
  position += 1;
1238
+ if (element.type !== "comment") {
1239
+ entries.push(writtenEntry(emitter, element));
1240
+ }
1232
1241
  }
1242
+ addEntryRows(rowsOf(emitter), objectKey, entries);
1233
1243
  }
1234
1244
  function emitDictionary(emitter, dictionary) {
1235
1245
  const objectKey = nodeId(emitter.filePath, dictionary);
1236
1246
  add(emitter, "objectValue", objectKey);
1247
+ const entries = [];
1237
1248
  for (const pair of children(dictionary)) {
1249
+ if (SPREAD_TYPES.has(pair.type)) {
1250
+ entries.push(writtenEntry(emitter, pair));
1251
+ continue;
1252
+ }
1238
1253
  if (pair.type !== "pair") {
1239
1254
  continue;
1240
1255
  }
@@ -1243,6 +1258,7 @@ function emitDictionary(emitter, dictionary) {
1243
1258
  if (key === null || value === null) {
1244
1259
  continue;
1245
1260
  }
1261
+ entries.push(writtenEntry(emitter, value));
1246
1262
  if (key.type !== "string") {
1247
1263
  add(emitter, "holdsUnderKey", objectKey, valueKey(emitter, value));
1248
1264
  continue;
@@ -1255,6 +1271,15 @@ function emitDictionary(emitter, dictionary) {
1255
1271
  valueKey(emitter, value)
1256
1272
  );
1257
1273
  }
1274
+ addEntryRows(rowsOf(emitter), objectKey, entries);
1275
+ }
1276
+ var SPREAD_TYPES = /* @__PURE__ */ new Set(["list_splat", "dictionary_splat"]);
1277
+ function writtenEntry(emitter, entry) {
1278
+ const unpacked = SPREAD_TYPES.has(entry.type) ? children(entry)[0] : void 0;
1279
+ return unpacked === void 0 ? { key: valueKey(emitter, entry), form: VALUE_ENTRY } : { key: valueKey(emitter, unpacked), form: SPREAD_ENTRY };
1280
+ }
1281
+ function rowsOf(emitter) {
1282
+ return (relation, row) => add(emitter, relation, ...row);
1258
1283
  }
1259
1284
  function emitAttribute(emitter, attribute) {
1260
1285
  const object = field(attribute, "object");
@@ -1319,13 +1344,50 @@ function emitKeyedRead(emitter, site, container, key) {
1319
1344
  if (WRITTEN_KEY_TYPES.has(key.type)) {
1320
1345
  return;
1321
1346
  }
1322
- add(
1323
- emitter,
1324
- "readsKeyed",
1325
- nodeId(emitter.filePath, site),
1326
- valueKey(emitter, container),
1327
- valueKey(emitter, key)
1347
+ const siteKey = nodeId(emitter.filePath, site);
1348
+ const containerKey = valueKey(emitter, container);
1349
+ add(emitter, "readsKeyed", siteKey, containerKey, valueKey(emitter, key));
1350
+ add(emitter, "choosesEntry", siteKey, containerKey);
1351
+ }
1352
+ function emitAttributeChoice(emitter, call) {
1353
+ const callee = field(call, "function");
1354
+ const [target, name] = callArguments(call);
1355
+ if (callee?.type !== "identifier" || callee.text !== ATTRIBUTE_GETTER || !isBuiltin(emitter, callee.text) || target?.kind !== "positional" || name?.kind !== "positional") {
1356
+ return;
1357
+ }
1358
+ const siteKey = nodeId(emitter.filePath, call);
1359
+ const targetKey = valueKey(emitter, target.node);
1360
+ const written = plainStringOf(name.node);
1361
+ if (written !== null) {
1362
+ add(emitter, "choosesNamedEntry", siteKey, targetKey, written);
1363
+ return;
1364
+ }
1365
+ const ends = writtenEnds(name.node);
1366
+ if (ends === null) {
1367
+ add(emitter, "choosesEntry", siteKey, targetKey);
1368
+ return;
1369
+ }
1370
+ add(emitter, "choosesEntryLike", siteKey, targetKey, ends.start, ends.end);
1371
+ }
1372
+ function writtenEnds(node) {
1373
+ if (node.type !== "string") {
1374
+ return null;
1375
+ }
1376
+ const parts = children(node).filter(
1377
+ (part) => part.type !== "string_start" && part.type !== "string_end"
1328
1378
  );
1379
+ const written = (part) => part.type === "string_content";
1380
+ const firstComputed = parts.findIndex((part) => !written(part));
1381
+ if (firstComputed === -1) {
1382
+ return null;
1383
+ }
1384
+ const lastComputed = parts.length - 1 - [...parts].reverse().findIndex((part) => !written(part));
1385
+ const start = parts.slice(0, firstComputed).map((part) => part.text).join("");
1386
+ const end = parts.slice(lastComputed + 1).map((part) => part.text).join("");
1387
+ return start === "" && end === "" ? null : { start, end };
1388
+ }
1389
+ function isBuiltin(emitter, name) {
1390
+ return nameKey(emitter.filePath, emitter.enclosing, name) === nameId(emitter.filePath, name) && !emitter.moduleNames().has(name);
1329
1391
  }
1330
1392
  function emitWrittenKeyRead(emitter, site, container, key) {
1331
1393
  const name = plainStringOf(key);
@@ -1461,6 +1523,86 @@ function emitComprehensionElement(emitter, comprehension) {
1461
1523
  );
1462
1524
  }
1463
1525
  }
1526
+ var PAIR_TARGET_TYPES = /* @__PURE__ */ new Set(["pattern_list", "tuple_pattern"]);
1527
+ function emitElementVariables(emitter, clause) {
1528
+ const target = field(clause, "left");
1529
+ const over = field(clause, "right");
1530
+ if (target === null || over === null) {
1531
+ return;
1532
+ }
1533
+ const names = PAIR_TARGET_TYPES.has(target.type) ? children(target) : [];
1534
+ const [position, element] = names;
1535
+ const counted = countedCollection(emitter, over);
1536
+ if (counted !== null && names.length === 2 && position?.type === "identifier" && element !== void 0) {
1537
+ emitElementTaken(emitter, element, valueKey(emitter, counted), position);
1538
+ return;
1539
+ }
1540
+ emitElementTaken(emitter, target, valueKey(emitter, over), null);
1541
+ }
1542
+ function emitElementTaken(emitter, target, overKey, position) {
1543
+ const variables = target.type === "identifier" ? [{ node: target, part: null }] : partsTakenApart(target);
1544
+ for (const variable of variables) {
1545
+ const key = valueKey(emitter, variable.node);
1546
+ if (variable.part === null) {
1547
+ add(emitter, "elementOf", key, overKey);
1548
+ continue;
1549
+ }
1550
+ add(emitter, "elementPartOf", key, overKey, variable.part);
1551
+ }
1552
+ const [first] = variables;
1553
+ if (position !== null && first !== void 0) {
1554
+ add(
1555
+ emitter,
1556
+ "positionOf",
1557
+ valueKey(emitter, position),
1558
+ valueKey(emitter, first.node)
1559
+ );
1560
+ }
1561
+ }
1562
+ function partsTakenApart(target) {
1563
+ const parts = [];
1564
+ if (!PAIR_TARGET_TYPES.has(target.type)) {
1565
+ return parts;
1566
+ }
1567
+ for (const [position, part] of children(target).entries()) {
1568
+ if (part.type === "list_splat_pattern") {
1569
+ break;
1570
+ }
1571
+ if (part.type === "identifier") {
1572
+ parts.push({ node: part, part: String(position) });
1573
+ }
1574
+ }
1575
+ return parts;
1576
+ }
1577
+ function countedCollection(emitter, over) {
1578
+ const callee = over.type === "call" ? field(over, "function") : null;
1579
+ const args = field(over, "arguments");
1580
+ const [first] = callArguments(over);
1581
+ if (callee?.type !== "identifier" || callee.text !== POSITION_COUNTER || !isBuiltin(emitter, POSITION_COUNTER) || args === null || writtenArgumentCount(args) !== 1 || first?.kind !== "positional") {
1582
+ return null;
1583
+ }
1584
+ return first.node;
1585
+ }
1586
+ function interpolatedPieces(emitter, string2) {
1587
+ const parts = children(string2);
1588
+ if (!parts.some((part) => part.type === "interpolation")) {
1589
+ return [];
1590
+ }
1591
+ return parts.flatMap((part) => {
1592
+ if (part.type === "string_content") {
1593
+ return [{ text: stringContentValue(part) }];
1594
+ }
1595
+ const expression = part.type === "interpolation" ? field(part, "expression") ?? children(part)[0] : void 0;
1596
+ return expression === void 0 ? [] : [{ hole: valueKey(emitter, expression) }];
1597
+ });
1598
+ }
1599
+ function adjacentPieces(emitter, joined2) {
1600
+ return children(joined2).filter((part) => part.type !== "comment").map((part) => ({ hole: valueKey(emitter, part) }));
1601
+ }
1602
+ var TEMPLATE_PIECES = {
1603
+ string: interpolatedPieces,
1604
+ concatenated_string: adjacentPieces
1605
+ };
1464
1606
  function emitExpressionFact(emitter, child, type) {
1465
1607
  if (type === "call") {
1466
1608
  emitCall(emitter, child);
@@ -1468,6 +1610,7 @@ function emitExpressionFact(emitter, child, type) {
1468
1610
  if (mapping !== null) {
1469
1611
  emitKeyedRead(emitter, child, mapping.container, mapping.key);
1470
1612
  }
1613
+ emitAttributeChoice(emitter, child);
1471
1614
  }
1472
1615
  if (type === "subscript") {
1473
1616
  const index = singleSubscript(child);
@@ -1497,6 +1640,13 @@ function emitExpressionFact(emitter, child, type) {
1497
1640
  if (COMPREHENSION_TYPES.has(type)) {
1498
1641
  emitComprehensionElement(emitter, child);
1499
1642
  }
1643
+ if (type === "for_in_clause") {
1644
+ emitElementVariables(emitter, child);
1645
+ }
1646
+ const pieces = TEMPLATE_PIECES[type]?.(emitter, child) ?? [];
1647
+ if (pieces.length > 0) {
1648
+ addTemplateRows(rowsOf(emitter), nodeId(emitter.filePath, child), pieces);
1649
+ }
1500
1650
  const branches = fallbackBranchesOf(child, type);
1501
1651
  if (branches !== null) {
1502
1652
  for (const branch of branches) {
@@ -2015,6 +2165,9 @@ function readUnderName(emitter, statement, funcKey, accessed) {
2015
2165
  function emitNestedDefinitions(emitter, node) {
2016
2166
  for (const child of children(node)) {
2017
2167
  const type = child.type;
2168
+ if (type === "decorated_definition") {
2169
+ emitDecoratorCalls(emitter, child);
2170
+ }
2018
2171
  if (FUNCTION_TYPES.has(type)) {
2019
2172
  emitFunctionFacts(emitter, child);
2020
2173
  if (emitter.enclosing !== null) {
@@ -2034,6 +2187,20 @@ function emitNestedDefinitions(emitter, node) {
2034
2187
  emitNestedDefinitions(emitter, child);
2035
2188
  }
2036
2189
  }
2190
+ function emitDecoratorCalls(emitter, decorated) {
2191
+ const { definition, decorators } = stripDecorators(decorated);
2192
+ let applied = nodeId(emitter.filePath, definition);
2193
+ for (const decorator of [...decorators].reverse()) {
2194
+ const [expression] = children(decorator);
2195
+ if (expression === void 0) {
2196
+ continue;
2197
+ }
2198
+ const callKey = nodeId(emitter.filePath, decorator);
2199
+ add(emitter, "call", callKey, valueKey(emitter, expression));
2200
+ add(emitter, "callArg", callKey, "0", applied);
2201
+ applied = callKey;
2202
+ }
2203
+ }
2037
2204
  var readingsByTree = /* @__PURE__ */ new WeakMap();
2038
2205
  function scopeReadingOf(fn, given) {
2039
2206
  const tree = fn.tree;
@@ -4771,13 +4938,37 @@ function modelColumn(node) {
4771
4938
  }
4772
4939
  return field(node, "attribute")?.text ?? null;
4773
4940
  }
4774
- function selectorOf(chain, valueMethods) {
4941
+ function selectorOf(chain, valueMethods, model) {
4942
+ const picking = laterCalls(chain).filter(
4943
+ (call) => !valueMethods.includes(methodNameOf(call))
4944
+ );
4775
4945
  return [
4776
- ...new Set(
4777
- laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap((call) => pickedBy(call).map((one) => one.name))
4778
- )
4946
+ .../* @__PURE__ */ new Set([
4947
+ ...picking.flatMap((call) => pickedBy(call).map((one) => one.name)),
4948
+ ...model === null ? [] : picking.flatMap((call) => columnsComparedBy(call, model))
4949
+ ])
4779
4950
  ];
4780
4951
  }
4952
+ var COMBINED_TESTS = ["and_", "or_", "not_"];
4953
+ function columnsComparedBy(call, model) {
4954
+ const args = field(call, "arguments");
4955
+ return (args === null ? [] : children(args)).flatMap(
4956
+ (argument) => columnsCompared(argument, model)
4957
+ );
4958
+ }
4959
+ function columnsCompared(node, model) {
4960
+ if (node.type === "call" && COMBINED_TESTS.includes(methodNameOf(node))) {
4961
+ return columnsComparedBy(node, model);
4962
+ }
4963
+ if (node.type !== "comparison_operator") {
4964
+ return [];
4965
+ }
4966
+ return children(node).flatMap((side) => {
4967
+ const object = side.type === "attribute" ? field(side, "object") : null;
4968
+ const column = object?.text === model ? field(side, "attribute") : null;
4969
+ return column === null ? [] : [column.text];
4970
+ });
4971
+ }
4781
4972
  var TABLE_METHODS = ["select_from", "join"];
4782
4973
  function modelOf(chain, options) {
4783
4974
  const fromRoot = modelArgument(chain.root, chain.root, options);
@@ -4842,14 +5033,15 @@ function isClassName(name) {
4842
5033
  }
4843
5034
  function effectFor2(pattern, chain, operation, options) {
4844
5035
  const valueMethods = pattern.valueMethods ?? [];
4845
- const picked = selectorOf(chain, valueMethods);
5036
+ const model = modelOf(chain, options);
5037
+ const picked = selectorOf(chain, valueMethods, model);
4846
5038
  const effect = {
4847
5039
  type: "interaction",
4848
5040
  binding: storageBinding2({
4849
5041
  recognition: "python-storage",
4850
5042
  storageSystem: pattern.storageSystem,
4851
5043
  scope: "default",
4852
- container: modelOf(chain, options)
5044
+ container: model
4853
5045
  }),
4854
5046
  callee: chain.last.text,
4855
5047
  interaction: {
@@ -6580,7 +6772,7 @@ function isStdlibModule(node, scope, module) {
6580
6772
  const binding = resolveName(scope, node.text);
6581
6773
  return binding?.kind === "import" && binding.module === module && binding.relativeLevel === 0;
6582
6774
  }
6583
- function isBuiltin(node, scope, name) {
6775
+ function isBuiltin2(node, scope, name) {
6584
6776
  return node.type === "identifier" && node.text === name && resolveName(scope, name) === null;
6585
6777
  }
6586
6778
  function isStdlibMember(node, scope, module, name) {
@@ -6628,7 +6820,7 @@ function streamWriteAt(call, scope) {
6628
6820
  }
6629
6821
  const written = callArguments(call);
6630
6822
  const positional = written.filter((argument) => argument.kind === "positional").map((argument) => argument.node);
6631
- if (callee.type === "identifier" && isBuiltin(callee, scope, "print")) {
6823
+ if (callee.type === "identifier" && isBuiltin2(callee, scope, "print")) {
6632
6824
  const file = written.find(
6633
6825
  (argument) => argument.kind === "keyword" && argument.name === "file"
6634
6826
  );
@@ -8068,6 +8260,7 @@ import {
8068
8260
  placeArgTargets as placeArgTargets2,
8069
8261
  placeCalleeParameters as placeCalleeParameters2,
8070
8262
  placeCalls as placeCalls2,
8263
+ placePossibleCalls as placePossibleCalls2,
8071
8264
  recordParameterGaps as recordParameterGaps2,
8072
8265
  summaryIdFromParts,
8073
8266
  unfollowedCallGap as unfollowedCallGap2
@@ -8079,10 +8272,12 @@ import {
8079
8272
  createCacheLayer,
8080
8273
  createTimer,
8081
8274
  effectToIR,
8275
+ extractionCacheDirFor,
8082
8276
  extractionConfigStamp,
8083
8277
  KeptParses,
8084
8278
  moduleInitStructure,
8085
8279
  noopTimer,
8280
+ ownersWhereWritten,
8086
8281
  runDigest,
8087
8282
  stampModuleImports,
8088
8283
  stampModules
@@ -8163,12 +8358,12 @@ function exitSiteAt(statement, scope) {
8163
8358
  return endsTheProcess(callee, scope) ? { statement, code, raised: false } : null;
8164
8359
  }
8165
8360
  if (statement.type === "raise_statement") {
8166
- return isBuiltin(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
8361
+ return isBuiltin2(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
8167
8362
  }
8168
8363
  return null;
8169
8364
  }
8170
8365
  function endsTheProcess(callee, scope) {
8171
- return isStdlibMember(callee, scope, "sys", "exit") || isBuiltin(callee, scope, "exit") || isBuiltin(callee, scope, "quit");
8366
+ return isStdlibMember(callee, scope, "sys", "exit") || isBuiltin2(callee, scope, "exit") || isBuiltin2(callee, scope, "quit");
8172
8367
  }
8173
8368
  function firstArgument(call) {
8174
8369
  return callArguments(call).find(
@@ -8329,6 +8524,7 @@ import {
8329
8524
  placeArgTargets,
8330
8525
  placeCalleeParameters,
8331
8526
  placeCalls,
8527
+ placePossibleCalls,
8332
8528
  recordParameterGaps,
8333
8529
  StopList,
8334
8530
  TargetPlacements,
@@ -8429,6 +8625,7 @@ function methodCalledOn(call) {
8429
8625
  import {
8430
8626
  calleeOutcomeOf,
8431
8627
  calleeOutcomes,
8628
+ dispatchOutcomes,
8432
8629
  noteKeyRead as noteKeyRead3,
8433
8630
  RECEIVER_STORE_NAME as RECEIVER_STORE_NAME2,
8434
8631
  writtenSourcesOf
@@ -8498,6 +8695,40 @@ function instancesHaveAttribute(facts, classKey, name) {
8498
8695
  function namedOutcomes(keys, ctx) {
8499
8696
  return calleeOutcomes(ctx.facts, [...new Set(keys)]);
8500
8697
  }
8698
+ function dispatchedCalls(calls, ctx) {
8699
+ const byKey = new Map(
8700
+ calls.map(({ call, site }) => [nodeId(site.file.file, call), call])
8701
+ );
8702
+ const found = /* @__PURE__ */ new Map();
8703
+ for (const [key, outcome] of dispatchOutcomes(ctx.facts, [...byKey.keys()])) {
8704
+ const call = byKey.get(key);
8705
+ if (call === void 0) {
8706
+ continue;
8707
+ }
8708
+ const targets = /* @__PURE__ */ new Map();
8709
+ for (const fn of outcome.functions) {
8710
+ for (const target of reachedThrough(fn, outcome.through, ctx)) {
8711
+ targets.set(nodeId(target.file.file, target.node), target);
8712
+ }
8713
+ }
8714
+ found.set(call.id, {
8715
+ targets: [...targets.values()],
8716
+ certain: outcome.certain && outcome.functions.every((fn) => !isLambda(fn, ctx)),
8717
+ unresolved: outcome.unresolved
8718
+ });
8719
+ }
8720
+ return found;
8721
+ }
8722
+ function reachedThrough(key, through, ctx) {
8723
+ const keys = isLambda(key, ctx) ? through.get(key) ?? [] : [key];
8724
+ return keys.flatMap((one) => {
8725
+ const target = isLambda(one, ctx) ? null : functionAt(one, ctx);
8726
+ return target === null ? [] : [target];
8727
+ });
8728
+ }
8729
+ function isLambda(key, ctx) {
8730
+ return ctx.definitions.get(key)?.type === "lambda";
8731
+ }
8501
8732
  function functionNamed(nameKey2, ctx, read) {
8502
8733
  if (assignedElsewhere(ctx.facts, nameKey2)) {
8503
8734
  return null;
@@ -8716,6 +8947,7 @@ function reachedFunctions(seeds, options) {
8716
8947
  const targetsByKey = /* @__PURE__ */ new Map();
8717
8948
  const stopsByKey = /* @__PURE__ */ new Map();
8718
8949
  const argTargetsByKey = /* @__PURE__ */ new Map();
8950
+ const possibleTargetsByKey = /* @__PURE__ */ new Map();
8719
8951
  const parameterCallsByKey = /* @__PURE__ */ new Map();
8720
8952
  const callsByKey = /* @__PURE__ */ new Map();
8721
8953
  const passedPositions = /* @__PURE__ */ new Set();
@@ -8778,6 +9010,7 @@ function reachedFunctions(seeds, options) {
8778
9010
  }
8779
9011
  targetsByKey.set(key, scan.targets);
8780
9012
  argTargetsByKey.set(key, scan.argTargets);
9013
+ possibleTargetsByKey.set(key, scan.possibleTargets);
8781
9014
  if (scan.parameterCalls.length > 0) {
8782
9015
  parameterCallsByKey.set(key, scan.parameterCalls);
8783
9016
  }
@@ -8828,6 +9061,7 @@ function reachedFunctions(seeds, options) {
8828
9061
  }
8829
9062
  placeCalls(summary2, targetsByKey.get(key));
8830
9063
  placeArgTargets(summary2, argTargetsByKey.get(key));
9064
+ placePossibleCalls(summary2, possibleTargetsByKey.get(key));
8831
9065
  placeCalleeParameters(summary2, parameterCallsByKey.get(key));
8832
9066
  return summary2;
8833
9067
  };
@@ -8851,6 +9085,7 @@ function reachedFunctions(seeds, options) {
8851
9085
  targetsByKey,
8852
9086
  stopsByKey,
8853
9087
  argTargetsByKey,
9088
+ possibleTargetsByKey,
8854
9089
  parameterCallsByKey,
8855
9090
  callsByKey,
8856
9091
  passedPositions,
@@ -8874,24 +9109,42 @@ function chargeOf(charges, key) {
8874
9109
  }
8875
9110
  return charge;
8876
9111
  }
9112
+ function dispatchedAmong(written, spellings, ctx) {
9113
+ return dispatchedCalls(
9114
+ written.filter(({ call, site }) => {
9115
+ if (!ENTRY_CALLEE_TYPES.has(field(call, "function")?.type ?? "")) {
9116
+ return false;
9117
+ }
9118
+ const outcome = resolveCallee2(call, site, ctx, spellings);
9119
+ return outcome.kind === "stopped" && outcome.reason !== "callerSupplied";
9120
+ }),
9121
+ ctx
9122
+ );
9123
+ }
9124
+ var ENTRY_CALLEE_TYPES = /* @__PURE__ */ new Set(["subscript", "call", "identifier"]);
8877
9125
  function batchedReads(round, ctx) {
8878
9126
  const bodies = round.flatMap(
8879
9127
  (entry) => "source" in entry ? [{ key: entry.key, source: entry.source, ...bodyOf(entry.source) }] : []
8880
9128
  );
8881
- const spellings = calleeSpellings(
8882
- bodies.flatMap((body) => body.written),
8883
- ctx
8884
- );
9129
+ const written = bodies.flatMap((body) => body.written);
9130
+ const spellings = calleeSpellings(written, ctx);
8885
9131
  const passed = namedOutcomes(
8886
9132
  bodies.flatMap(
8887
- ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
9133
+ ({ source, written: written2 }) => written2.flatMap(({ call }) => passedNameKeys(source.file, call))
8888
9134
  ),
8889
9135
  ctx
8890
9136
  );
9137
+ const dispatched = dispatchedAmong(written, spellings, ctx);
8891
9138
  return new Map(
8892
9139
  bodies.map((body) => [
8893
9140
  body.key,
8894
- { body: body.body, written: body.written, spellings, passed }
9141
+ {
9142
+ body: body.body,
9143
+ written: body.written,
9144
+ spellings,
9145
+ passed,
9146
+ dispatched
9147
+ }
8895
9148
  ])
8896
9149
  );
8897
9150
  }
@@ -8922,13 +9175,15 @@ function scanOnce(source, key, ctx, options, read, charges) {
8922
9175
  }
8923
9176
  function ownReads(source, ctx) {
8924
9177
  const body = bodyOf(source);
9178
+ const spellings = calleeSpellings(body.written, ctx);
8925
9179
  return {
8926
9180
  ...body,
8927
- spellings: calleeSpellings(body.written, ctx),
9181
+ spellings,
8928
9182
  passed: namedOutcomes(
8929
9183
  body.written.flatMap(({ call }) => passedNameKeys(source.file, call)),
8930
9184
  ctx
8931
- )
9185
+ ),
9186
+ dispatched: dispatchedAmong(body.written, spellings, ctx)
8932
9187
  };
8933
9188
  }
8934
9189
  function storedScan(scan) {
@@ -8944,6 +9199,10 @@ function storedScan(scan) {
8944
9199
  callee,
8945
9200
  [...byPosition]
8946
9201
  ]),
9202
+ possibleTargets: [...scan.possibleTargets].map(([callee, places]) => [
9203
+ callee,
9204
+ [...places]
9205
+ ]),
8947
9206
  parameterCalls: [...scan.parameterCalls],
8948
9207
  passedPositions: [...scan.passedPositions]
8949
9208
  };
@@ -8959,6 +9218,7 @@ function scanFromStored(stored, functionFor) {
8959
9218
  new Map(byPosition)
8960
9219
  ])
8961
9220
  ),
9221
+ possibleTargets: new Map(stored.possibleTargets),
8962
9222
  parameterCalls: stored.parameterCalls,
8963
9223
  passedPositions: new Set(stored.passedPositions)
8964
9224
  };
@@ -8987,6 +9247,7 @@ var EMPTY_SCAN = {
8987
9247
  stops: [],
8988
9248
  targets: /* @__PURE__ */ new Map(),
8989
9249
  argTargets: /* @__PURE__ */ new Map(),
9250
+ possibleTargets: /* @__PURE__ */ new Map(),
8990
9251
  parameterCalls: [],
8991
9252
  passedPositions: /* @__PURE__ */ new Set()
8992
9253
  };
@@ -9063,12 +9324,25 @@ function scanBody(source, ctx, recognized, read, implied) {
9063
9324
  return EMPTY_SCAN;
9064
9325
  }
9065
9326
  const ownParameters = callParameterNames(node, source.exportPath.length > 1);
9327
+ const follow = (target) => {
9328
+ const key = keyOf2(target);
9329
+ if (!seen.has(key)) {
9330
+ seen.add(key);
9331
+ followed.push(target);
9332
+ }
9333
+ };
9066
9334
  const recordPassedArgs = (call, callee, calleeKey2) => {
9067
9335
  const args = field(call, "arguments");
9068
9336
  if (args === null) {
9069
9337
  return;
9070
9338
  }
9071
9339
  children(args).forEach((arg, position) => {
9340
+ if (arg.type === "lambda") {
9341
+ if (calleeKey2 !== null) {
9342
+ passedPositions.add(`${calleeKey2}#${position}`);
9343
+ }
9344
+ return;
9345
+ }
9072
9346
  const nameKey2 = passedNameKeyOf(file, arg);
9073
9347
  if (nameKey2 === null) {
9074
9348
  return;
@@ -9077,11 +9351,7 @@ function scanBody(source, ctx, recognized, read, implied) {
9077
9351
  if (resolved === null) {
9078
9352
  return;
9079
9353
  }
9080
- const key = keyOf2(resolved);
9081
- if (!seen.has(key)) {
9082
- seen.add(key);
9083
- followed.push(resolved);
9084
- }
9354
+ follow(resolved);
9085
9355
  placements.placeArg(callee, position, {
9086
9356
  file: resolved.file.displayPath,
9087
9357
  span: spanOf(resolved.node)
@@ -9091,9 +9361,30 @@ function scanBody(source, ctx, recognized, read, implied) {
9091
9361
  }
9092
9362
  });
9093
9363
  };
9364
+ const recordPossible = (call, callee, dispatched) => {
9365
+ dispatched.targets.forEach(follow);
9366
+ placements.placePossible(
9367
+ callee,
9368
+ dispatched.targets.map((target) => ({
9369
+ file: target.file.displayPath,
9370
+ span: spanOf(target.node)
9371
+ }))
9372
+ );
9373
+ recordPassedArgs(call, callee, null);
9374
+ if (dispatched.unresolved > 0 && !recognized.has(call.id)) {
9375
+ stops.add({ callee, reason: "unsettledValue" });
9376
+ }
9377
+ };
9094
9378
  const record = (call, site) => {
9095
9379
  const callee = calleeText(call);
9096
- const outcome = resolveCallee2(call, site, ctx, spellings);
9380
+ const settled = resolveCallee2(call, site, ctx, spellings);
9381
+ const dispatched = settled.kind === "stopped" ? read.dispatched.get(call.id) : void 0;
9382
+ const [only] = dispatched?.targets ?? [];
9383
+ if (dispatched !== void 0 && only !== void 0 && !dispatched.certain) {
9384
+ recordPossible(call, callee, dispatched);
9385
+ return;
9386
+ }
9387
+ const outcome = only === void 0 ? settled : { kind: "followed", target: only };
9097
9388
  placeCallee(placements, callee, outcome, call, file);
9098
9389
  recordPassedArgs(
9099
9390
  call,
@@ -9117,11 +9408,7 @@ function scanBody(source, ctx, recognized, read, implied) {
9117
9408
  }
9118
9409
  return;
9119
9410
  }
9120
- const key = keyOf2(outcome.target);
9121
- if (!seen.has(key)) {
9122
- seen.add(key);
9123
- followed.push(outcome.target);
9124
- }
9411
+ follow(outcome.target);
9125
9412
  };
9126
9413
  for (const { call, site } of written) {
9127
9414
  record(call, site);
@@ -9131,17 +9418,14 @@ function scanBody(source, ctx, recognized, read, implied) {
9131
9418
  file: target.file.displayPath,
9132
9419
  span: spanOf(target.node)
9133
9420
  });
9134
- const key = keyOf2(target);
9135
- if (!seen.has(key)) {
9136
- seen.add(key);
9137
- followed.push(target);
9138
- }
9421
+ follow(target);
9139
9422
  }
9140
9423
  return {
9141
9424
  followed,
9142
9425
  stops: stops.stops,
9143
9426
  targets: placements.targets,
9144
9427
  argTargets: placements.argTargets,
9428
+ possibleTargets: placements.possibleTargets,
9145
9429
  parameterCalls,
9146
9430
  passedPositions
9147
9431
  };
@@ -9465,6 +9749,10 @@ var PackPositions = class {
9465
9749
  const [packAt, patternAt] = id.split("/").map(Number);
9466
9750
  return this.packs[packAt ?? -1]?.discovery[patternAt ?? -1];
9467
9751
  }
9752
+ packOf(id) {
9753
+ const [packAt] = id.split("/").map(Number);
9754
+ return this.packs[packAt ?? -1];
9755
+ }
9468
9756
  formAt(at) {
9469
9757
  const pack = this.packs[at[0]];
9470
9758
  const pattern = pack?.discovery[at[1]];
@@ -11559,7 +11847,10 @@ var PythonWrapperIndex = class {
11559
11847
  /** By wrapper reference: the parameters a dependency function declares, and its file. */
11560
11848
  dependencyParameterTable = /* @__PURE__ */ new Map();
11561
11849
  unitsByFile = /* @__PURE__ */ new Map();
11562
- /** Told about every registration, so a cache can repeat the ones a file's discovery made. */
11850
+ /**
11851
+ * Told about every registration, a pack's form or a project decorator's
11852
+ * closure, so a cache can repeat the ones a file's discovery made.
11853
+ */
11563
11854
  onRegistered = null;
11564
11855
  /**
11565
11856
  * The same lookup `wrappersFor` noted under `id`, made again over this
@@ -11640,6 +11931,25 @@ var PythonWrapperIndex = class {
11640
11931
  }
11641
11932
  return { reference, form: declared.form };
11642
11933
  }
11934
+ /**
11935
+ * The unit for the closure a project's decorator hands back, built once
11936
+ * however many routes the decorator is written on. Its name is what
11937
+ * calling the decorator writes, since the decorator may have a summary
11938
+ * of its own under its name.
11939
+ */
11940
+ registeredClosure(target, closure) {
11941
+ this.onRegistered?.(target, closure);
11942
+ const span = spanOf(target.node);
11943
+ const key = `${target.file.file}:${span.start}-${span.end}`;
11944
+ if (!this.unitsByKey.has(key)) {
11945
+ const unit = closureUnit(target, closure, this.options);
11946
+ this.unitsByKey.set(key, unit);
11947
+ const inFile = this.unitsByFile.get(target.file.file) ?? [];
11948
+ inFile.push(unit);
11949
+ this.unitsByFile.set(target.file.file, inFile);
11950
+ }
11951
+ return { file: target.file.displayPath, name: target.name };
11952
+ }
11643
11953
  /**
11644
11954
  * The parameters the dependencies among these wrappers declare, and
11645
11955
  * whether one is declared through a parameter source such as `Query()`.
@@ -11671,9 +11981,13 @@ var PythonWrapperIndex = class {
11671
11981
  pattern: query.pattern,
11672
11982
  form
11673
11983
  }));
11674
- if (file === void 0 || forms.length === 0) {
11984
+ if (file === void 0) {
11675
11985
  return [];
11676
11986
  }
11987
+ const decorators = this.decoratorLayers(query, file);
11988
+ if (forms.length === 0) {
11989
+ return decorators;
11990
+ }
11677
11991
  const own = this.ownRoutesOf(query);
11678
11992
  const found = [
11679
11993
  ...this.noted(
@@ -11711,7 +12025,68 @@ var PythonWrapperIndex = class {
11711
12025
  seen.add(key);
11712
12026
  ordered.push(wrapper.reference);
11713
12027
  }
11714
- return ordered;
12028
+ return [
12029
+ ...ordered.filter((one) => one.onThrow !== true),
12030
+ ...decorators,
12031
+ ...ordered.filter((one) => one.onThrow === true)
12032
+ ];
12033
+ }
12034
+ /**
12035
+ * The decorators written on the route's function below the one that
12036
+ * registers it, outermost first. Each wraps the function the library
12037
+ * calls. None has a summary: a library's has no body in the run, and a
12038
+ * project's own is not composed into the route yet, so a project
12039
+ * decorator goes by what calling it writes, `require_admin(fn)`, which
12040
+ * no summary is named. The decorators the packs read, such as the
12041
+ * route's own and the namespace's `@ns.doc`, are left out.
12042
+ */
12043
+ decoratorLayers(query, file) {
12044
+ const decorated = query.definitionNode.parent;
12045
+ if (decorated === null || decorated.type !== "decorated_definition") {
12046
+ return [];
12047
+ }
12048
+ const written = stripDecorators(decorated).decorators;
12049
+ const routeAt = written.findIndex(
12050
+ (decorator) => sameRange(rangeOf(decorator), query.classification.range)
12051
+ );
12052
+ return written.slice(routeAt + 1).flatMap((decorator) => this.decoratorLayer(decorator, query, file));
12053
+ }
12054
+ decoratorLayer(decorator, query, file) {
12055
+ const facts = this.options.facts;
12056
+ const direct = classifyDecorator(decorator, query.module, facts);
12057
+ const written = decoratorCallee(decorator);
12058
+ if (written === null || direct.module !== null && readByAPack(direct.module, this.options) || direct.objectName !== null && direct.objectName === query.classification.objectName || unwrapDecorator(decorator, facts) !== null) {
12059
+ return [];
12060
+ }
12061
+ const project = written.type === "identifier" ? this.functionCalled(file.file, written.text) : null;
12062
+ if (project !== null) {
12063
+ if (facts !== void 0) {
12064
+ noteKeyRead6(facts, project.file.file);
12065
+ }
12066
+ return [this.projectDecoratorLayer(project, query)];
12067
+ }
12068
+ return [{ file: direct.module ?? file.displayPath, name: written.text }];
12069
+ }
12070
+ /**
12071
+ * The layer a project's own decorator puts around the route. When it
12072
+ * hands back a closure written inside it that returns what calling the
12073
+ * function it was given returns, the closure is summarized and
12074
+ * composed into the route. Any other decorator, including one whose
12075
+ * closure keeps going after that call, goes by the same name with no
12076
+ * summary behind it.
12077
+ */
12078
+ projectDecoratorLayer(decorator, query) {
12079
+ const continuation = parameterNamesOf(decorator.node)[0] ?? "";
12080
+ const name = `${decorator.name}(${continuation})`;
12081
+ const closure = closureReturnedBy(decorator.node);
12082
+ const handsOn = closure === null || continuation === "" ? [] : callsThrough(closure, continuation);
12083
+ if (closure === null || handsOn.length === 0 || !handsOn.every((statement) => returnsCallTo(statement, continuation))) {
12084
+ return { file: decorator.file.displayPath, name };
12085
+ }
12086
+ return this.registeredClosure(
12087
+ { file: decorator.file, node: closure, name, exportPath: [] },
12088
+ { pack: query.pack, pattern: query.pattern, continuation }
12089
+ );
11715
12090
  }
11716
12091
  /**
11717
12092
  * What the mounts above the route's router register on the way down:
@@ -11872,6 +12247,21 @@ var PythonWrapperIndex = class {
11872
12247
  function referenceKey(reference) {
11873
12248
  return `${reference.file}::${reference.name}`;
11874
12249
  }
12250
+ function readByAPack(module, options) {
12251
+ return options.packs.some(
12252
+ (pack) => pack.discovery.some((pattern) => pattern.importModule.includes(module))
12253
+ );
12254
+ }
12255
+ function decoratorCallee(decorator) {
12256
+ const written = decorator.namedChild(0);
12257
+ if (written === null) {
12258
+ return null;
12259
+ }
12260
+ return written.type === "call" ? field(written, "function") : written;
12261
+ }
12262
+ function sameRange(a, b) {
12263
+ return a.start === b.start && a.end === b.end;
12264
+ }
11875
12265
  function parameterNamesOf(definition) {
11876
12266
  return children(field(definition, "parameters")).flatMap((parameter) => {
11877
12267
  const named = parameterNameAndType(parameter);
@@ -12059,16 +12449,42 @@ function parameterDependencies(definitionNode, form) {
12059
12449
  return names;
12060
12450
  }
12061
12451
  function wrapperUnit(target, declared, options) {
12452
+ return unitAround(
12453
+ target,
12454
+ declared.pattern,
12455
+ {
12456
+ continuations: continuationStatements(target.node, declared.form),
12457
+ continuationName: null,
12458
+ form: declared.form,
12459
+ nameKind: "binding"
12460
+ },
12461
+ options
12462
+ );
12463
+ }
12464
+ function closureUnit(target, closure, options) {
12465
+ return unitAround(
12466
+ target,
12467
+ closure.pattern,
12468
+ {
12469
+ continuations: callsThrough(target.node, closure.continuation),
12470
+ continuationName: closure.continuation,
12471
+ form: null,
12472
+ nameKind: "label"
12473
+ },
12474
+ options
12475
+ );
12476
+ }
12477
+ function unitAround(target, pattern, reading, options) {
12062
12478
  const { file, node, name, exportPath } = target;
12063
12479
  const body = field(node, "body");
12064
12480
  const range = rangeOf(node);
12065
12481
  const raised = raisedResponses(body, {
12066
- calls: declared.pattern.responseStatusCalls ?? [],
12482
+ calls: pattern.responseStatusCalls ?? [],
12067
12483
  module: file.module,
12068
12484
  facts: options.facts,
12069
- constants: declared.pattern.statusCodeConstants ?? {}
12485
+ constants: pattern.statusCodeConstants ?? {}
12070
12486
  });
12071
- const continuations = continuationStatements(node, declared.form);
12487
+ const continuations = reading.continuations;
12072
12488
  const terminals = bodyTerminals(body, raised, continuations);
12073
12489
  const effects = invocationEffects(node, options.facts);
12074
12490
  const extra = recognizedBodyEffects(
@@ -12077,7 +12493,7 @@ function wrapperUnit(target, declared, options) {
12077
12493
  options.storageFor(file),
12078
12494
  options.facts
12079
12495
  );
12080
- const delegate = (at) => ({
12496
+ const delegate = (at, to = null) => ({
12081
12497
  terminal: {
12082
12498
  kind: "delegate",
12083
12499
  statusCode: null,
@@ -12086,18 +12502,18 @@ function wrapperUnit(target, declared, options) {
12086
12502
  message: null,
12087
12503
  component: null,
12088
12504
  renderTree: null,
12089
- delegateTarget: null,
12505
+ delegateTarget: to,
12090
12506
  emitEvent: null,
12091
12507
  location: rangeOf(at)
12092
12508
  },
12093
12509
  location: rangeOf(at)
12094
12510
  });
12095
12511
  const respond = (statement) => returnBranch(statement, {
12096
- pattern: declared.pattern,
12512
+ pattern,
12097
12513
  responseShape: { reading: absentReading2 },
12098
12514
  declaredStatus: {
12099
12515
  reading: absentReading2,
12100
- ...declared.pattern.defaultStatusCode !== void 0 ? { libraryDefault: declared.pattern.defaultStatusCode } : {}
12516
+ ...pattern.defaultStatusCode !== void 0 ? { libraryDefault: pattern.defaultStatusCode } : {}
12101
12517
  },
12102
12518
  module: file.module,
12103
12519
  facts: options.facts
@@ -12109,7 +12525,10 @@ function wrapperUnit(target, declared, options) {
12109
12525
  location: rangeOf(terminal.statement)
12110
12526
  };
12111
12527
  }
12112
- if (terminal.type === "continuation" || returnHandsOn(terminal.statement, declared.form)) {
12528
+ if (terminal.type === "continuation") {
12529
+ return delegate(terminal.statement, reading.continuationName);
12530
+ }
12531
+ if (reading.form !== null && returnHandsOn(terminal.statement, reading.form)) {
12113
12532
  return delegate(terminal.statement);
12114
12533
  }
12115
12534
  return respond(terminal.statement);
@@ -12126,7 +12545,7 @@ function wrapperUnit(target, declared, options) {
12126
12545
  return {
12127
12546
  identity: {
12128
12547
  name,
12129
- nameKind: "binding",
12548
+ nameKind: reading.nameKind,
12130
12549
  kind: "middleware",
12131
12550
  file: file.displayPath,
12132
12551
  range,
@@ -12162,23 +12581,45 @@ function continuationStatements(node, form) {
12162
12581
  const params = children(field(node, "parameters"));
12163
12582
  const param = params[form.continuationParam];
12164
12583
  const continuation = param === void 0 ? null : field(param, "name")?.text ?? (param.type === "identifier" ? param.text : null);
12165
- if (continuation === null) {
12166
- return [];
12167
- }
12584
+ return continuation === null ? [] : callsThrough(node, continuation);
12585
+ }
12586
+ function callsThrough(node, callee) {
12168
12587
  const body = field(node, "body");
12169
12588
  if (body === null) {
12170
12589
  return [];
12171
12590
  }
12172
12591
  const found = [];
12173
12592
  for (const call of bodyCalls(body)) {
12174
- const callee = field(call, "function");
12175
- const statement = callee?.type === "identifier" && callee.text === continuation ? enclosingStatement(call, body) : null;
12593
+ const called = field(call, "function");
12594
+ const statement = called?.type === "identifier" && called.text === callee ? enclosingStatement(call, body) : null;
12176
12595
  if (statement !== null) {
12177
12596
  found.push(statement);
12178
12597
  }
12179
12598
  }
12180
12599
  return found;
12181
12600
  }
12601
+ function returnsCallTo(statement, callee) {
12602
+ if (statement.type !== "return_statement") {
12603
+ return false;
12604
+ }
12605
+ const value = statement.namedChild(0);
12606
+ const call = value?.type === "await" ? value.namedChild(0) : value;
12607
+ return call?.type === "call" && field(call, "function")?.text === callee;
12608
+ }
12609
+ function closureReturnedBy(decorator) {
12610
+ const statements = children(field(decorator, "body"));
12611
+ const returned = statements.find((statement) => statement.type === "return_statement")?.namedChild(0);
12612
+ if (returned?.type !== "identifier") {
12613
+ return null;
12614
+ }
12615
+ for (const statement of statements) {
12616
+ const { definition } = stripDecorators(statement);
12617
+ if (definition.type === "function_definition" && field(definition, "name")?.text === returned.text) {
12618
+ return definition;
12619
+ }
12620
+ }
12621
+ return null;
12622
+ }
12182
12623
 
12183
12624
  // src/project.ts
12184
12625
  function keptPythonParses() {
@@ -12273,7 +12714,7 @@ async function extractPythonProject(options) {
12273
12714
  const { roots, unreadManifests } = rootsOfRun(options);
12274
12715
  const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
12275
12716
  const cacheDir = adapterStamp.declineWhenRunFromSource(
12276
- options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? path5.join(options.projectRoot, ".suss", "cache") : null)
12717
+ options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? extractionCacheDirFor(options.projectRoot) : null)
12277
12718
  );
12278
12719
  const layer = createCacheLayer(cacheDir);
12279
12720
  const packsDigest = `${adapterStamp.packsDigest(
@@ -12296,11 +12737,14 @@ async function extractPythonProject(options) {
12296
12737
  "cache.lookup",
12297
12738
  () => layer.lookup(input)
12298
12739
  );
12740
+ const composed = (stored) => composeWrappers(stored, {
12741
+ gapHandling: options.gapHandling ?? "permissive"
12742
+ });
12299
12743
  if (lookup.kind === "hit") {
12300
12744
  options.onCacheDiagnostic?.(lookup.diagnostic);
12301
12745
  options.onTiming?.(timer.report());
12302
12746
  return {
12303
- summaries: lookup.summaries,
12747
+ summaries: composed(lookup.summaries),
12304
12748
  facts: new Database2(),
12305
12749
  roots,
12306
12750
  unreadManifests
@@ -12315,7 +12759,7 @@ async function extractPythonProject(options) {
12315
12759
  options.onCacheDiagnostic?.({ kind: "hit" });
12316
12760
  options.onTiming?.(timer.report());
12317
12761
  return {
12318
- summaries: plan.allSummaries(),
12762
+ summaries: composed(plan.allSummaries()),
12319
12763
  facts: new Database2(),
12320
12764
  roots,
12321
12765
  unreadManifests
@@ -12497,15 +12941,10 @@ async function runPython(options, roots, cache, previous) {
12497
12941
  });
12498
12942
  let registering = null;
12499
12943
  if (ledger !== null) {
12500
- wrapperIndex.onRegistered = (target, declared) => {
12501
- const form = positions.positionOf(declared);
12502
- if (registering !== null && form !== void 0) {
12503
- registering.push({
12504
- key: nodeId(target.file.file, target.node),
12505
- name: target.name,
12506
- exportPath: target.exportPath,
12507
- form
12508
- });
12944
+ wrapperIndex.onRegistered = (target, how) => {
12945
+ const stored = storedRegistration(target, how, positions);
12946
+ if (registering !== null && stored !== null) {
12947
+ registering.push(stored);
12509
12948
  }
12510
12949
  };
12511
12950
  }
@@ -12518,12 +12957,11 @@ async function runPython(options, roots, cache, previous) {
12518
12957
  if (record !== void 0) {
12519
12958
  discoveries.set(file, record);
12520
12959
  for (const registration of record.registrations) {
12521
- const declared = positions.formAt(registration.form);
12522
- const target = storedFunction(registration, definitions, filesByPath);
12523
- if (declared === void 0 || target === null) {
12524
- throw new ReplayFailed(registration.key);
12525
- }
12526
- wrapperIndex.registered(target, declared);
12960
+ registerAgain(registration, {
12961
+ positions,
12962
+ wrapperIndex,
12963
+ target: storedFunction(registration, definitions, filesByPath)
12964
+ });
12527
12965
  }
12528
12966
  continue;
12529
12967
  }
@@ -12692,6 +13130,7 @@ async function runPython(options, roots, cache, previous) {
12692
13130
  }
12693
13131
  placeCalls2(summary, reached.targetsByKey.get(key));
12694
13132
  placeArgTargets2(summary, reached.argTargetsByKey.get(key));
13133
+ placePossibleCalls2(summary, reached.possibleTargetsByKey.get(key));
12695
13134
  placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
12696
13135
  };
12697
13136
  for (const [key, owners] of summariesBySeed) {
@@ -12781,16 +13220,20 @@ async function runPython(options, roots, cache, previous) {
12781
13220
  markExitCodeFunctions(summaries, exits, db, options.workspaceRoot);
12782
13221
  return composeWrappers(summaries, { gapHandling });
12783
13222
  });
13223
+ const walkedFiles = new Map(
13224
+ bound.map(({ file, displayPath }) => [displayPath, file])
13225
+ );
12784
13226
  await timer.timeAsync("cache.write", async () => {
12785
- if (cacheDir === null || composed.length === 0) {
13227
+ if (cacheDir === null || summaries.length === 0) {
12786
13228
  return;
12787
13229
  }
12788
13230
  try {
12789
13231
  await cache.layer.write(
12790
13232
  cache.input,
12791
- composed,
13233
+ summaries,
12792
13234
  log === null || ledger === null || overBudget ? void 0 : pythonAttribution({
12793
- composed,
13235
+ summaries,
13236
+ walkedFileOf: (locationFile) => walkedFiles.get(locationFile),
12794
13237
  discoveries,
12795
13238
  charges,
12796
13239
  unitDrafts,
@@ -12845,6 +13288,39 @@ async function runPython(options, roots, cache, previous) {
12845
13288
  options.onTiming?.(timer.report());
12846
13289
  return { summaries: composed, facts: db };
12847
13290
  }
13291
+ function storedRegistration(target, how, positions) {
13292
+ const at = {
13293
+ key: nodeId(target.file.file, target.node),
13294
+ name: target.name,
13295
+ exportPath: target.exportPath
13296
+ };
13297
+ if ("continuation" in how) {
13298
+ const pattern = positions.idOf(how.pattern);
13299
+ return pattern === void 0 ? null : { ...at, pattern, continuation: how.continuation };
13300
+ }
13301
+ const form = positions.positionOf(how);
13302
+ return form === void 0 ? null : { ...at, form };
13303
+ }
13304
+ function registerAgain(registration, context) {
13305
+ const { positions, wrapperIndex, target } = context;
13306
+ const how = howRegistered(registration, positions);
13307
+ if (target === null || how === null) {
13308
+ throw new ReplayFailed(registration.key);
13309
+ }
13310
+ if ("continuation" in how) {
13311
+ wrapperIndex.registeredClosure(target, how);
13312
+ return;
13313
+ }
13314
+ wrapperIndex.registered(target, how);
13315
+ }
13316
+ function howRegistered(registration, positions) {
13317
+ if (!("continuation" in registration)) {
13318
+ return positions.formAt(registration.form) ?? null;
13319
+ }
13320
+ const pack = positions.packOf(registration.pattern);
13321
+ const pattern = positions.patternOf(registration.pattern);
13322
+ return pack === void 0 || pattern === void 0 ? null : { pack, pattern, continuation: registration.continuation };
13323
+ }
12848
13324
  function pythonAttribution(args) {
12849
13325
  const walkedByFile = walkRecordsByFile({
12850
13326
  scans: args.reached.scans,
@@ -12873,7 +13349,10 @@ function pythonAttribution(args) {
12873
13349
  }
12874
13350
  };
12875
13351
  });
12876
- return { roots, owners: args.composed.map(() => []) };
13352
+ return {
13353
+ roots,
13354
+ owners: ownersWhereWritten(args.summaries, args.walkedFileOf)
13355
+ };
12877
13356
  }
12878
13357
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
12879
13358
  "__pycache__",