@suss/adapter-python 0.34.0 → 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
@@ -153,10 +153,7 @@ function stringLiteralValue(node) {
153
153
  return null;
154
154
  }
155
155
  let content = "";
156
- for (const child of node.namedChildren) {
157
- if (child === null) {
158
- continue;
159
- }
156
+ for (const child of children(node)) {
160
157
  if (child.type === "string_content") {
161
158
  content += stringContentValue(child);
162
159
  continue;
@@ -168,7 +165,29 @@ function stringLiteralValue(node) {
168
165
  return content;
169
166
  }
170
167
  function children(node) {
171
- return node.namedChildren.filter((child) => child !== null);
168
+ if (node === null || node === void 0) {
169
+ return [];
170
+ }
171
+ const found = [];
172
+ const count = node.namedChildCount;
173
+ for (let index = 0; index < count; index++) {
174
+ const child = node.namedChild(index);
175
+ if (child !== null) {
176
+ found.push(child);
177
+ }
178
+ }
179
+ return found;
180
+ }
181
+ function allChildren(node) {
182
+ const found = [];
183
+ const count = node.childCount;
184
+ for (let index = 0; index < count; index++) {
185
+ const child = node.child(index);
186
+ if (child !== null) {
187
+ found.push(child);
188
+ }
189
+ }
190
+ return found;
172
191
  }
173
192
  function bodyStatements(body) {
174
193
  return children(body);
@@ -200,11 +219,59 @@ function stripDecorators(node) {
200
219
  if (definition === null) {
201
220
  throw new Error("decorated_definition with no definition field");
202
221
  }
203
- const decorators = node.namedChildren.filter(
204
- (child) => child !== null && child.type === "decorator"
222
+ const decorators = children(node).filter(
223
+ (child) => child.type === "decorator"
205
224
  );
206
225
  return { definition, decorators };
207
226
  }
227
+ var isChild = (fieldName) => (parent, child) => field(parent, fieldName)?.id === child.id;
228
+ var always = () => true;
229
+ var asCondition = isChild("condition");
230
+ var isConditionalTest = (parent, child) => parent.namedChild(1)?.id === child.id;
231
+ var VALUE_USED_UNDER = {
232
+ assignment: isChild("right"),
233
+ augmented_assignment: isChild("right"),
234
+ named_expression: isChild("value"),
235
+ attribute: isChild("object"),
236
+ subscript: always,
237
+ argument_list: always,
238
+ keyword_argument: isChild("value"),
239
+ pair: always,
240
+ list: always,
241
+ tuple: always,
242
+ set: always,
243
+ dictionary: always,
244
+ interpolation: always,
245
+ comparison_operator: always,
246
+ binary_operator: always,
247
+ unary_operator: always,
248
+ not_operator: always,
249
+ if_statement: asCondition,
250
+ elif_clause: asCondition,
251
+ while_statement: asCondition,
252
+ conditional_expression: isConditionalTest,
253
+ assert_statement: always,
254
+ for_statement: isChild("right"),
255
+ for_in_clause: isChild("right"),
256
+ with_item: always,
257
+ as_pattern: always
258
+ };
259
+ var PASSES_VALUE_ON = {
260
+ parenthesized_expression: always,
261
+ await: always,
262
+ boolean_operator: always,
263
+ conditional_expression: (parent, child) => !isConditionalTest(parent, child)
264
+ };
265
+ function resultUsedAsValue(node) {
266
+ let child = node;
267
+ let parent = node.parent;
268
+ while (parent !== null && PASSES_VALUE_ON[parent.type]?.(parent, child) === true) {
269
+ child = parent;
270
+ parent = parent.parent;
271
+ }
272
+ const used = parent === null ? void 0 : VALUE_USED_UNDER[parent.type];
273
+ return parent !== null && used !== void 0 && used(parent, child);
274
+ }
208
275
  var NodeSet = class extends import_extractor.IdSet {
209
276
  };
210
277
  var NodeMap = class extends import_extractor.IdMap {
@@ -266,7 +333,7 @@ function bindWithStatement(stmt, scope, ctx) {
266
333
  }
267
334
  }
268
335
  function asPatternsIn(stmt, found = []) {
269
- for (const child of stmt.namedChildren) {
336
+ for (const child of children(stmt)) {
270
337
  if (child === null || child.type === "block") {
271
338
  continue;
272
339
  }
@@ -295,12 +362,12 @@ function bindDeleteStatement(stmt, scope) {
295
362
  return;
296
363
  }
297
364
  if (node.type === "expression_list") {
298
- for (const child of node.namedChildren) {
365
+ for (const child of children(node)) {
299
366
  unbind(child);
300
367
  }
301
368
  }
302
369
  };
303
- for (const child of stmt.namedChildren) {
370
+ for (const child of children(stmt)) {
304
371
  unbind(child);
305
372
  }
306
373
  }
@@ -341,10 +408,7 @@ function bindImportStatement(stmt, scope) {
341
408
  function relativeImportParts(node) {
342
409
  let relativeLevel = 0;
343
410
  let module2 = "";
344
- for (const child of node.namedChildren) {
345
- if (child === null) {
346
- continue;
347
- }
411
+ for (const child of children(node)) {
348
412
  if (child.type === "import_prefix") {
349
413
  relativeLevel += child.text.length;
350
414
  continue;
@@ -361,8 +425,8 @@ function bindImportFromStatement(stmt, scope, ctx) {
361
425
  return;
362
426
  }
363
427
  const { module: module2, relativeLevel } = moduleNode.type === "relative_import" ? relativeImportParts(moduleNode) : { module: moduleNode.text, relativeLevel: 0 };
364
- const hasWildcard = stmt.namedChildren.some(
365
- (child) => child !== null && child.type === "wildcard_import"
428
+ const hasWildcard = children(stmt).some(
429
+ (child) => child.type === "wildcard_import"
366
430
  );
367
431
  if (hasWildcard) {
368
432
  ctx.openImports.push(
@@ -420,10 +484,7 @@ function bindFunctionDefinition(stmt, scope, ctx) {
420
484
  }
421
485
  }
422
486
  function bindParameters(parametersNode, functionScope) {
423
- for (const param of parametersNode.namedChildren) {
424
- if (param === null) {
425
- continue;
426
- }
487
+ for (const param of children(parametersNode)) {
427
488
  const name = parameterName(param);
428
489
  if (name !== null) {
429
490
  bindName(functionScope, name, { kind: "parameter" });
@@ -440,9 +501,7 @@ function parameterName(param) {
440
501
  "list_splat_pattern",
441
502
  "dictionary_splat_pattern"
442
503
  )) {
443
- const inner = param.namedChildren.find(
444
- (child) => child !== null && child.type === "identifier"
445
- );
504
+ const inner = children(param).find((child) => child.type === "identifier");
446
505
  return inner?.text ?? null;
447
506
  }
448
507
  if (isType(param, "default_parameter", "typed_default_parameter")) {
@@ -469,8 +528,8 @@ function bindClassDefinition(stmt, scope, ctx) {
469
528
  }
470
529
  }
471
530
  function bindExpressionStatement(stmt, scope) {
472
- const assignment2 = stmt.namedChildren.find(
473
- (child) => child !== null && child.type === "assignment"
531
+ const assignment2 = children(stmt).find(
532
+ (child) => child.type === "assignment"
474
533
  );
475
534
  if (assignment2 === void 0 || assignment2 === null) {
476
535
  return;
@@ -487,15 +546,15 @@ function bindExpressionStatement(stmt, scope) {
487
546
  }
488
547
  }
489
548
  function bindGlobalStatement(stmt, scope) {
490
- for (const child of stmt.namedChildren) {
491
- if (child !== null && child.type === "identifier") {
549
+ for (const child of children(stmt)) {
550
+ if (child.type === "identifier") {
492
551
  scope.bindings.set(child.text, { kind: "global" });
493
552
  }
494
553
  }
495
554
  }
496
555
  function bindNonlocalStatement(stmt, scope) {
497
- for (const child of stmt.namedChildren) {
498
- if (child !== null && child.type === "identifier") {
556
+ for (const child of children(stmt)) {
557
+ if (child.type === "identifier") {
499
558
  scope.bindings.set(child.text, { kind: "nonlocal" });
500
559
  }
501
560
  }
@@ -644,18 +703,9 @@ function shapeFromName(name, scope, ctx) {
644
703
  if (DICT_NAMES.has(name)) {
645
704
  return { type: "dictionary", values: { type: "unknown" } };
646
705
  }
647
- const binding = resolveName(scope, name);
648
- if (binding?.kind === "classDef") {
649
- return recordShapeRef(name, binding.node, ctx);
650
- }
651
- if (binding?.kind === "import" || binding?.kind === "importFrom") {
652
- const imported = ctx.importedDefinition?.(scope, name) ?? null;
653
- if (imported?.node.type === "class_definition") {
654
- if (!ctx.scopeMaps.includes(imported.scopeFor)) {
655
- ctx.scopeMaps.push(imported.scopeFor);
656
- }
657
- return recordShapeRef(name, imported.node, ctx);
658
- }
706
+ const classNode = projectClassNamed(name, scope, ctx);
707
+ if (classNode !== null) {
708
+ return recordShapeRef(name, classNode, ctx);
659
709
  }
660
710
  const alias = aliasValueOf(name, scope, ctx);
661
711
  if (alias !== null) {
@@ -663,6 +713,23 @@ function shapeFromName(name, scope, ctx) {
663
713
  }
664
714
  return { type: "ref", name };
665
715
  }
716
+ function projectClassNamed(name, scope, ctx) {
717
+ const binding = resolveName(scope, name);
718
+ if (binding?.kind === "classDef") {
719
+ return binding.node;
720
+ }
721
+ if (binding?.kind !== "import" && binding?.kind !== "importFrom") {
722
+ return null;
723
+ }
724
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
725
+ if (imported?.node.type !== "class_definition") {
726
+ return null;
727
+ }
728
+ if (!ctx.scopeMaps.includes(imported.scopeFor)) {
729
+ ctx.scopeMaps.push(imported.scopeFor);
730
+ }
731
+ return imported.node;
732
+ }
666
733
  function shapeFromAliasValue(name, alias, ctx) {
667
734
  if (ctx.expanding.has(alias.node.id)) {
668
735
  return { type: "ref", name };
@@ -689,8 +756,9 @@ function shapeFromSubscript(node, scope, ctx) {
689
756
  );
690
757
  }
691
758
  function annotationTarget(annotation) {
692
- if (annotation.type === "type" && annotation.namedChildren[0]) {
693
- return annotationTarget(annotation.namedChildren[0]);
759
+ const inner = annotation.type === "type" ? annotation.namedChild(0) : null;
760
+ if (inner !== null) {
761
+ return annotationTarget(inner);
694
762
  }
695
763
  if (annotation.type === "binary_operator") {
696
764
  const named = [field(annotation, "left"), field(annotation, "right")].find(
@@ -699,7 +767,7 @@ function annotationTarget(annotation) {
699
767
  return named === void 0 || named === null ? null : annotationTarget(named);
700
768
  }
701
769
  if (annotation.type === "generic_type") {
702
- const outer = annotation.namedChildren[0];
770
+ const outer = annotation.namedChild(0);
703
771
  const first = genericTypeArgs(annotation)[0];
704
772
  if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
705
773
  return annotationTarget(first);
@@ -716,12 +784,10 @@ function typeNameOf(annotation) {
716
784
  return target.type === "identifier" ? target.text : stringLiteralValue(target);
717
785
  }
718
786
  function genericTypeArgs(node) {
719
- const typeParameter = node.namedChildren.find(
720
- (child) => child !== null && child.type === "type_parameter"
787
+ const typeParameter = children(node).find(
788
+ (child) => child.type === "type_parameter"
721
789
  );
722
- return typeParameter?.namedChildren.filter(
723
- (child) => child !== null && child.type === "type"
724
- ) ?? [];
790
+ return children(typeParameter).filter((child) => child.type === "type");
725
791
  }
726
792
  function shapeFromGenericType(node, scope, ctx) {
727
793
  const base = node.namedChild(0);
@@ -773,22 +839,44 @@ function recordShapeRef(name, classNode, ctx) {
773
839
  return ref;
774
840
  }
775
841
  ctx.definitions.set(key, null);
842
+ ctx.definitions.set(key, {
843
+ type: "record",
844
+ properties: classFields(classNode, ctx, /* @__PURE__ */ new Set())
845
+ });
846
+ return ref;
847
+ }
848
+ function classFields(classNode, ctx, visiting) {
776
849
  const bodyNode = field(classNode, "body");
777
850
  const classScope = scopeOfNode(ctx, classNode);
778
- ctx.definitions.set(
779
- key,
780
- bodyNode !== null && classScope !== void 0 ? recordShapeOf(bodyNode, classScope, ctx) : { type: "record", properties: {} }
851
+ if (bodyNode === null || classScope === void 0 || visiting.has(classNode.id)) {
852
+ return {};
853
+ }
854
+ visiting.add(classNode.id);
855
+ const inherited = baseClassNames(classNode).flatMap((name) => {
856
+ const base = projectClassNamed(name, classScope, ctx);
857
+ return base === null ? [] : [classFields(base, ctx, visiting)];
858
+ });
859
+ const own = recordShapeOf(bodyNode, classScope, ctx);
860
+ return Object.assign(
861
+ {},
862
+ ...inherited.reverse(),
863
+ own.type === "record" ? own.properties : {}
864
+ );
865
+ }
866
+ function baseClassNames(classNode) {
867
+ const superclasses = field(classNode, "superclasses");
868
+ return children(superclasses).flatMap(
869
+ (child) => child.type === "identifier" ? [child.text] : []
781
870
  );
782
- return ref;
783
871
  }
784
872
  function recordShapeOf(bodyNode, classScope, ctx) {
785
873
  const properties = {};
786
- for (const stmt of bodyNode.namedChildren) {
874
+ for (const stmt of children(bodyNode)) {
787
875
  if (stmt === null || stmt.type !== "expression_statement") {
788
876
  continue;
789
877
  }
790
- const assignment2 = stmt.namedChildren.find(
791
- (child) => child !== null && child.type === "assignment"
878
+ const assignment2 = children(stmt).find(
879
+ (child) => child.type === "assignment"
792
880
  );
793
881
  if (assignment2 === void 0 || assignment2 === null) {
794
882
  continue;
@@ -798,6 +886,9 @@ function recordShapeOf(bodyNode, classScope, ctx) {
798
886
  if (left === null || left.type !== "identifier" || typeNode === null) {
799
887
  continue;
800
888
  }
889
+ if (typeNameOf(typeNode) === "ClassVar") {
890
+ continue;
891
+ }
801
892
  properties[left.text] = annotationToShape(typeNode, classScope, ctx);
802
893
  }
803
894
  return { type: "record", properties };
@@ -807,13 +898,14 @@ function shortHash(text2) {
807
898
  }
808
899
 
809
900
  // src/decorators.ts
810
- var import_behavioral_ir = require("@suss/behavioral-ir");
901
+ var import_behavioral_ir2 = require("@suss/behavioral-ir");
811
902
 
812
903
  // src/values/evaluator.ts
813
904
  var import_resolution4 = require("@suss/resolution");
814
905
  var import_values4 = require("@suss/values");
815
906
 
816
907
  // src/facts/resolve.ts
908
+ var import_behavioral_ir = require("@suss/behavioral-ir");
817
909
  var import_resolution = require("@suss/resolution");
818
910
  var import_resolution2 = require("@suss/resolution");
819
911
  var evaluationsUnderSite = /* @__PURE__ */ new WeakMap();
@@ -859,7 +951,13 @@ function constructionSites(db, classKey) {
859
951
  }
860
952
  function writtenValueOf(db, key) {
861
953
  resolveWritten(db, [key]);
862
- return (0, import_resolution.writtenValueOf)(db, key, (keys) => resolveWritten(db, keys));
954
+ return (0, import_resolution.writtenValueOf)(
955
+ db,
956
+ key,
957
+ (keys) => resolveWritten(db, keys),
958
+ void 0,
959
+ (0, import_resolution.answersFromTestsLeftOut)(key, import_behavioral_ir.isTestFile)
960
+ );
863
961
  }
864
962
  function writtenValuesOf(db, key) {
865
963
  resolveWritten(db, [key]);
@@ -950,6 +1048,8 @@ var CONVERTING_BUILTINS = /* @__PURE__ */ new Set([
950
1048
  "str",
951
1049
  "float"
952
1050
  ]);
1051
+ var ATTRIBUTE_GETTER = "getattr";
1052
+ var POSITION_COUNTER = "enumerate";
953
1053
 
954
1054
  // src/facts/values.ts
955
1055
  function nodeId(filePath, node) {
@@ -1121,8 +1221,7 @@ function emitConversion(emitter, call, callKey) {
1121
1221
  return;
1122
1222
  }
1123
1223
  const name = callee.text;
1124
- const builtin = nameKey(emitter.filePath, emitter.enclosing, name) === nameId(emitter.filePath, name) && !emitter.moduleNames().has(name);
1125
- if (builtin) {
1224
+ if (isBuiltin(emitter, name)) {
1126
1225
  add(emitter, "converts", callKey, valueKey(emitter, first.node), name);
1127
1226
  }
1128
1227
  }
@@ -1171,6 +1270,7 @@ function emitSequence(emitter, sequence) {
1171
1270
  const objectKey = nodeId(emitter.filePath, sequence);
1172
1271
  add(emitter, "objectValue", objectKey);
1173
1272
  let position = 0;
1273
+ const entries = [];
1174
1274
  for (const element of children(sequence)) {
1175
1275
  add(
1176
1276
  emitter,
@@ -1180,12 +1280,21 @@ function emitSequence(emitter, sequence) {
1180
1280
  valueKey(emitter, element)
1181
1281
  );
1182
1282
  position += 1;
1283
+ if (element.type !== "comment") {
1284
+ entries.push(writtenEntry(emitter, element));
1285
+ }
1183
1286
  }
1287
+ (0, import_resolution3.addEntryRows)(rowsOf(emitter), objectKey, entries);
1184
1288
  }
1185
1289
  function emitDictionary(emitter, dictionary) {
1186
1290
  const objectKey = nodeId(emitter.filePath, dictionary);
1187
1291
  add(emitter, "objectValue", objectKey);
1292
+ const entries = [];
1188
1293
  for (const pair of children(dictionary)) {
1294
+ if (SPREAD_TYPES.has(pair.type)) {
1295
+ entries.push(writtenEntry(emitter, pair));
1296
+ continue;
1297
+ }
1189
1298
  if (pair.type !== "pair") {
1190
1299
  continue;
1191
1300
  }
@@ -1194,6 +1303,7 @@ function emitDictionary(emitter, dictionary) {
1194
1303
  if (key === null || value === null) {
1195
1304
  continue;
1196
1305
  }
1306
+ entries.push(writtenEntry(emitter, value));
1197
1307
  if (key.type !== "string") {
1198
1308
  add(emitter, "holdsUnderKey", objectKey, valueKey(emitter, value));
1199
1309
  continue;
@@ -1206,6 +1316,15 @@ function emitDictionary(emitter, dictionary) {
1206
1316
  valueKey(emitter, value)
1207
1317
  );
1208
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);
1209
1328
  }
1210
1329
  function emitAttribute(emitter, attribute) {
1211
1330
  const object = field(attribute, "object");
@@ -1270,13 +1389,50 @@ function emitKeyedRead(emitter, site, container, key) {
1270
1389
  if (WRITTEN_KEY_TYPES.has(key.type)) {
1271
1390
  return;
1272
1391
  }
1273
- add(
1274
- emitter,
1275
- "readsKeyed",
1276
- nodeId(emitter.filePath, site),
1277
- valueKey(emitter, container),
1278
- 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"
1279
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);
1280
1436
  }
1281
1437
  function emitWrittenKeyRead(emitter, site, container, key) {
1282
1438
  const name = plainStringOf(key);
@@ -1412,6 +1568,86 @@ function emitComprehensionElement(emitter, comprehension) {
1412
1568
  );
1413
1569
  }
1414
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
+ };
1415
1651
  function emitExpressionFact(emitter, child, type) {
1416
1652
  if (type === "call") {
1417
1653
  emitCall(emitter, child);
@@ -1419,6 +1655,7 @@ function emitExpressionFact(emitter, child, type) {
1419
1655
  if (mapping !== null) {
1420
1656
  emitKeyedRead(emitter, child, mapping.container, mapping.key);
1421
1657
  }
1658
+ emitAttributeChoice(emitter, child);
1422
1659
  }
1423
1660
  if (type === "subscript") {
1424
1661
  const index = singleSubscript(child);
@@ -1448,6 +1685,13 @@ function emitExpressionFact(emitter, child, type) {
1448
1685
  if (COMPREHENSION_TYPES.has(type)) {
1449
1686
  emitComprehensionElement(emitter, child);
1450
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
+ }
1451
1695
  const branches = fallbackBranchesOf(child, type);
1452
1696
  if (branches !== null) {
1453
1697
  for (const branch of branches) {
@@ -1480,10 +1724,10 @@ function emitExpressionFacts(emitter, node) {
1480
1724
  });
1481
1725
  }
1482
1726
  function yieldedValue(node) {
1483
- if (node.children.some((child) => child?.type === "from")) {
1727
+ if (allChildren(node).some((child) => child.type === "from")) {
1484
1728
  return null;
1485
1729
  }
1486
- return node.namedChildren.find((child) => child?.type !== "comment") ?? null;
1730
+ return children(node).find((child) => child?.type !== "comment") ?? null;
1487
1731
  }
1488
1732
  function emitFunctionFacts(emitter, fn, classKey) {
1489
1733
  const funcKey = nodeId(emitter.filePath, fn);
@@ -1555,7 +1799,7 @@ function emitFunctionFacts(emitter, fn, classKey) {
1555
1799
  const stores = /* @__PURE__ */ new Map();
1556
1800
  const visit = (child, type) => {
1557
1801
  if (type === "return_statement") {
1558
- const returned = child.namedChildren[0];
1802
+ const returned = child.namedChild(0);
1559
1803
  if (returned != null) {
1560
1804
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1561
1805
  statesReturn = true;
@@ -1966,6 +2210,9 @@ function readUnderName(emitter, statement, funcKey, accessed) {
1966
2210
  function emitNestedDefinitions(emitter, node) {
1967
2211
  for (const child of children(node)) {
1968
2212
  const type = child.type;
2213
+ if (type === "decorated_definition") {
2214
+ emitDecoratorCalls(emitter, child);
2215
+ }
1969
2216
  if (FUNCTION_TYPES.has(type)) {
1970
2217
  emitFunctionFacts(emitter, child);
1971
2218
  if (emitter.enclosing !== null) {
@@ -1985,6 +2232,20 @@ function emitNestedDefinitions(emitter, node) {
1985
2232
  emitNestedDefinitions(emitter, child);
1986
2233
  }
1987
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
+ }
1988
2249
  var readingsByTree = /* @__PURE__ */ new WeakMap();
1989
2250
  function scopeReadingOf(fn, given) {
1990
2251
  const tree = fn.tree;
@@ -2593,9 +2854,7 @@ function stringExpression(node) {
2593
2854
  }
2594
2855
  function comparisonExpression(node) {
2595
2856
  const operands = children(node);
2596
- const operators = node.children.filter(
2597
- (child) => child !== null && !child.isNamed
2598
- );
2857
+ const operators = allChildren(node).filter((child) => !child.isNamed);
2599
2858
  const operator = operators[0];
2600
2859
  if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2601
2860
  return { kind: "opaque" };
@@ -2689,6 +2948,7 @@ var STATEMENT_TYPES = {
2689
2948
  kind: "return",
2690
2949
  value: children(node)[0] ?? null
2691
2950
  }),
2951
+ raise_statement: (node) => caughtInSameFunction(node) ? OPAQUE : { kind: "throw" },
2692
2952
  try_statement: (node) => ({
2693
2953
  kind: "block",
2694
2954
  body: [
@@ -2777,6 +3037,19 @@ function blockStatements(block) {
2777
3037
  function isRoot(node) {
2778
3038
  return ROOT_TYPES.has(node.type);
2779
3039
  }
3040
+ var EXCEPT_CLAUSE_TYPES = /* @__PURE__ */ new Set(["except_clause", "except_group_clause"]);
3041
+ function caughtInSameFunction(node) {
3042
+ let child = node;
3043
+ let parent = node.parent;
3044
+ while (parent !== null && !isRoot(parent)) {
3045
+ if (parent.type === "try_statement" && field(parent, "body")?.id === child.id && children(parent).some((clause) => EXCEPT_CLAUSE_TYPES.has(clause.type))) {
3046
+ return true;
3047
+ }
3048
+ child = parent;
3049
+ parent = parent.parent;
3050
+ }
3051
+ return false;
3052
+ }
2780
3053
  function isPathStatement(node, parent) {
2781
3054
  return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2782
3055
  }
@@ -3167,6 +3440,10 @@ function writtenNodeOf(node, db) {
3167
3440
  const bound = db === void 0 ? void 0 : projects.get(db);
3168
3441
  return bound?.context.writtenTo(node) ?? null;
3169
3442
  }
3443
+ function calledDefinition(call, db) {
3444
+ const bound = db === void 0 ? void 0 : projects.get(db);
3445
+ return bound?.context.callable(call) ?? null;
3446
+ }
3170
3447
  function resolutionKeyOf(node, db) {
3171
3448
  const bound = db === void 0 ? void 0 : projects.get(db);
3172
3449
  return bound?.keyOf(node) ?? null;
@@ -3389,7 +3666,7 @@ function argShapeOf(node) {
3389
3666
  if (node.type === "list") {
3390
3667
  return {
3391
3668
  kind: "list",
3392
- items: node.namedChildren.filter((child) => child !== null).map(readArg)
3669
+ items: children(node).map(readArg)
3393
3670
  };
3394
3671
  }
3395
3672
  return { kind: "other" };
@@ -3400,10 +3677,7 @@ function readCallArguments(argumentList) {
3400
3677
  if (argumentList === null) {
3401
3678
  return { args, keywordArgs };
3402
3679
  }
3403
- for (const child of argumentList.namedChildren) {
3404
- if (child === null) {
3405
- continue;
3406
- }
3680
+ for (const child of children(argumentList)) {
3407
3681
  if (child.type === "keyword_argument") {
3408
3682
  const nameNode = field(child, "name");
3409
3683
  const valueNode = field(child, "value");
@@ -3447,7 +3721,7 @@ function builtObjectCallee(built, attributeName, objectName) {
3447
3721
  return { importedName: attributeName, module: agreed, objectName };
3448
3722
  }
3449
3723
  };
3450
- return (0, import_behavioral_ir.dispatchByType)(table, built);
3724
+ return (0, import_behavioral_ir2.dispatchByType)(table, built);
3451
3725
  }
3452
3726
  function resolveCallee(expr, module2, facts) {
3453
3727
  if (expr.type === "identifier") {
@@ -3529,10 +3803,10 @@ function unwrapDecorator(decoratorNode, facts) {
3529
3803
  }
3530
3804
  function passesParametersThrough(def, inner) {
3531
3805
  const params = field(def, "parameters");
3532
- const names = (params?.namedChildren ?? []).filter((child) => child?.type === "identifier").map((child) => child.text);
3806
+ const names = children(params).filter((child) => child.type === "identifier").map((child) => child.text);
3533
3807
  const argumentList = field(inner, "arguments");
3534
- const positional = (argumentList?.namedChildren ?? []).filter(
3535
- (child) => child !== null && child.type !== "keyword_argument"
3808
+ const positional = children(argumentList).filter(
3809
+ (child) => child.type !== "keyword_argument"
3536
3810
  );
3537
3811
  return positional.length === names.length && positional.every(
3538
3812
  (argument, at) => argument.type === "identifier" && argument.text === names[at]
@@ -3540,13 +3814,14 @@ function passesParametersThrough(def, inner) {
3540
3814
  }
3541
3815
 
3542
3816
  // src/discovery.ts
3543
- var import_behavioral_ir5 = require("@suss/behavioral-ir");
3544
- var import_extractor7 = require("@suss/extractor");
3545
- var import_values19 = require("@suss/values");
3817
+ var import_behavioral_ir6 = require("@suss/behavioral-ir");
3818
+ var import_extractor9 = require("@suss/extractor");
3819
+ var import_values20 = require("@suss/values");
3546
3820
 
3547
3821
  // src/clientCalls.ts
3548
- var import_behavioral_ir2 = require("@suss/behavioral-ir");
3549
- var import_values12 = require("@suss/values");
3822
+ var import_behavioral_ir3 = require("@suss/behavioral-ir");
3823
+ var import_extractor6 = require("@suss/extractor");
3824
+ var import_values14 = require("@suss/values");
3550
3825
 
3551
3826
  // src/paths/bodyBranches.ts
3552
3827
  var import_extractor4 = require("@suss/extractor");
@@ -3567,7 +3842,7 @@ var COMPARISONS = {
3567
3842
  "<=": "lte"
3568
3843
  };
3569
3844
  function operatorText(node) {
3570
- return node.children.filter((child) => child !== null && !child.isNamed).map((child) => child.text).join(" ");
3845
+ return allChildren(node).filter((child) => !child.isNamed).map((child) => child.text).join(" ");
3571
3846
  }
3572
3847
  function valueRefOf(node, facts, inputs) {
3573
3848
  const value = evaluatedValue(node, facts);
@@ -3619,7 +3894,7 @@ function nullCheckOf(subject, negated2, facts, inputs) {
3619
3894
  };
3620
3895
  }
3621
3896
  function comparisonOf(node, facts, inputs) {
3622
- const [left, right] = node.namedChildren.filter(
3897
+ const [left, right] = children(node).filter(
3623
3898
  (child) => child !== null
3624
3899
  );
3625
3900
  if (left === void 0 || right === void 0) {
@@ -3648,14 +3923,14 @@ function comparisonOf(node, facts, inputs) {
3648
3923
  }
3649
3924
  function predicateOf(node, facts, inputs) {
3650
3925
  if (node.type === "parenthesized_expression") {
3651
- const inner = node.namedChildren[0];
3926
+ const inner = node.namedChild(0);
3652
3927
  return inner == null ? opaqueOf(node) : predicateOf(inner, facts, inputs);
3653
3928
  }
3654
3929
  if (node.type === "comparison_operator") {
3655
3930
  return comparisonOf(node, facts, inputs);
3656
3931
  }
3657
3932
  if (node.type === "not_operator") {
3658
- const operand2 = field(node, "argument") ?? node.namedChildren[0];
3933
+ const operand2 = field(node, "argument") ?? node.namedChild(0);
3659
3934
  return operand2 == null ? opaqueOf(node) : { type: "negation", operand: predicateOf(operand2, facts, inputs) };
3660
3935
  }
3661
3936
  if (node.type === "identifier" || node.type === "attribute" || inputs?.has(node.id) === true) {
@@ -3670,11 +3945,11 @@ function predicateOf(node, facts, inputs) {
3670
3945
  var SUBJECT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "subscript"]);
3671
3946
  function operandsOf(node) {
3672
3947
  if (node.type === "parenthesized_expression" || node.type === "not_operator") {
3673
- const inner = field(node, "argument") ?? node.namedChildren[0];
3948
+ const inner = field(node, "argument") ?? node.namedChild(0);
3674
3949
  return inner == null ? [] : operandsOf(inner);
3675
3950
  }
3676
3951
  if (node.type === "comparison_operator") {
3677
- return node.namedChildren.filter(
3952
+ return children(node).filter(
3678
3953
  (child) => child !== null && SUBJECT_TYPES.has(child.type)
3679
3954
  );
3680
3955
  }
@@ -3700,7 +3975,7 @@ var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set(["function_definition", "lam
3700
3975
  function exitKindOf(node, thrown) {
3701
3976
  let sawReturn = false;
3702
3977
  const raises = (current) => {
3703
- for (const child of current.namedChildren) {
3978
+ for (const child of children(current)) {
3704
3979
  if (child === null || NESTED_FUNCTION_TYPES.has(child.type)) {
3705
3980
  continue;
3706
3981
  }
@@ -3728,12 +4003,10 @@ function blockStatements2(block) {
3728
4003
  if (block === null) {
3729
4004
  return [];
3730
4005
  }
3731
- return block.namedChildren.filter((child) => child !== null);
4006
+ return children(block);
3732
4007
  }
3733
4008
  function childBlock(node) {
3734
- return node.namedChildren.find(
3735
- (child) => child !== null && child.type === "block"
3736
- ) ?? null;
4009
+ return children(node).find((child) => child.type === "block") ?? null;
3737
4010
  }
3738
4011
  var Lowerer = class {
3739
4012
  // tree-sitter hands back a fresh wrapper object each time a child is
@@ -3782,7 +4055,7 @@ var Lowerer = class {
3782
4055
  return [nested];
3783
4056
  }
3784
4057
  lowerIf(node) {
3785
- const clauses = node.namedChildren.filter(
4058
+ const clauses = children(node).filter(
3786
4059
  (child) => child !== null && (child.type === "elif_clause" || child.type === "else_clause")
3787
4060
  );
3788
4061
  return {
@@ -3794,10 +4067,10 @@ var Lowerer = class {
3794
4067
  };
3795
4068
  }
3796
4069
  lowerTry(node) {
3797
- const excepts = node.namedChildren.filter(
4070
+ const excepts = children(node).filter(
3798
4071
  (child) => child !== null && (child.type === "except_clause" || child.type === "except_group_clause")
3799
4072
  );
3800
- const finallyClause = node.namedChildren.find(
4073
+ const finallyClause = children(node).find(
3801
4074
  (child) => child !== null && child.type === "finally_clause"
3802
4075
  ) ?? null;
3803
4076
  return {
@@ -3813,7 +4086,7 @@ var Lowerer = class {
3813
4086
  (child) => child.type === "case_clause"
3814
4087
  );
3815
4088
  const groups = cases.map((clause) => {
3816
- const pattern = clause.namedChildren.find(
4089
+ const pattern = children(clause).find(
3817
4090
  (child) => child !== null && child.type === "case_pattern"
3818
4091
  ) ?? null;
3819
4092
  return {
@@ -3894,7 +4167,7 @@ function literalArgOf(node, facts) {
3894
4167
  return null;
3895
4168
  }
3896
4169
  function bodyCalls(node, found = []) {
3897
- for (const child of node.namedChildren) {
4170
+ for (const child of children(node)) {
3898
4171
  if (child === null || NESTED_DEFINITION_TYPES.has(child.type)) {
3899
4172
  continue;
3900
4173
  }
@@ -3906,7 +4179,7 @@ function bodyCalls(node, found = []) {
3906
4179
  return found;
3907
4180
  }
3908
4181
  function moduleLoadCalls(node, found = []) {
3909
- for (const child of node.namedChildren) {
4182
+ for (const child of children(node)) {
3910
4183
  if (child === null || !runsAtModuleLoad(child)) {
3911
4184
  continue;
3912
4185
  }
@@ -3946,7 +4219,7 @@ function argumentNodes(call) {
3946
4219
  if (args === null) {
3947
4220
  return [];
3948
4221
  }
3949
- return args.namedChildren.filter((child) => child !== null).map(
4222
+ return children(args).map(
3950
4223
  (child) => child.type === "keyword_argument" ? field(child, "value") ?? child : child
3951
4224
  );
3952
4225
  }
@@ -3957,6 +4230,9 @@ function calleeText(call) {
3957
4230
  return field(call, "function")?.text ?? call.text;
3958
4231
  }
3959
4232
  function invocationEffects(definitionNode, facts) {
4233
+ return invocationSites(definitionNode, facts).map((site) => site.effect);
4234
+ }
4235
+ function invocationSites(definitionNode, facts) {
3960
4236
  const body = field(definitionNode, "body");
3961
4237
  if (body === null) {
3962
4238
  return [];
@@ -3964,14 +4240,18 @@ function invocationEffects(definitionNode, facts) {
3964
4240
  return invocationEffectsIn(body, bodyCalls(body), facts);
3965
4241
  }
3966
4242
  function moduleLoadInvocationEffects(moduleNode, facts) {
3967
- return invocationEffectsIn(moduleNode, moduleLoadCalls(moduleNode), facts);
4243
+ return invocationEffectsIn(
4244
+ moduleNode,
4245
+ moduleLoadCalls(moduleNode),
4246
+ facts
4247
+ ).map((site) => site.effect);
3968
4248
  }
3969
4249
  var ASKED_ABOUT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "call"]);
3970
4250
  function askedNodesUnder(node, found = []) {
3971
4251
  if (ASKED_ABOUT_TYPES.has(node.type)) {
3972
4252
  found.push(node);
3973
4253
  }
3974
- for (const child of node.namedChildren) {
4254
+ for (const child of children(node)) {
3975
4255
  if (child !== null && !NESTED_DEFINITION_TYPES.has(child.type)) {
3976
4256
  askedNodesUnder(child, found);
3977
4257
  }
@@ -4045,11 +4325,14 @@ function invocationEffectsIn(body, written, facts) {
4045
4325
  (condition) => predicateOf(condition, facts, inputs)
4046
4326
  );
4047
4327
  return {
4048
- type: "invocation",
4049
- callee: calleeText(call),
4050
- args: argsOf(call, facts),
4051
- async: false,
4052
- ...conditions.length > 0 ? { preconditions: conditions } : {}
4328
+ call,
4329
+ effect: {
4330
+ type: "invocation",
4331
+ callee: calleeText(call),
4332
+ args: argsOf(call, facts),
4333
+ async: false,
4334
+ ...conditions.length > 0 ? { preconditions: conditions } : {}
4335
+ }
4053
4336
  };
4054
4337
  });
4055
4338
  }
@@ -4084,7 +4367,7 @@ function statedBy(statement, name) {
4084
4367
  }
4085
4368
  if (statement.type === "as_pattern") {
4086
4369
  const alias = field(statement, "alias");
4087
- const value = statement.namedChildren[0] ?? null;
4370
+ const value = statement.namedChild(0) ?? null;
4088
4371
  const callee = value?.type === "call" ? field(value, "function") : null;
4089
4372
  return alias?.text === name ? callee : null;
4090
4373
  }
@@ -4280,7 +4563,7 @@ function importedFunctionMatch(call, options) {
4280
4563
  return null;
4281
4564
  }
4282
4565
  const args = field(call, "arguments");
4283
- const first = args?.namedChildren.find((child) => child !== null) ?? null;
4566
+ const first = children(args)[0] ?? null;
4284
4567
  const statement = first === null ? null : writtenStatement(first, void 0, options.facts);
4285
4568
  if (statement === null) {
4286
4569
  return null;
@@ -4644,7 +4927,7 @@ function columnComparisons(node) {
4644
4927
  const args = field(node, "arguments");
4645
4928
  return (args === null ? [] : children(args)).flatMap(columnComparisons);
4646
4929
  }
4647
- const operator = node.children.find((child) => !child?.isNamed);
4930
+ const operator = allChildren(node).find((child) => !child.isNamed);
4648
4931
  const [left, right, ...rest] = children(node);
4649
4932
  if (node.type !== "comparison_operator" || operator?.type !== "==" || left === void 0 || right === void 0 || rest.length > 0) {
4650
4933
  return [];
@@ -4663,13 +4946,37 @@ function modelColumn(node) {
4663
4946
  }
4664
4947
  return field(node, "attribute")?.text ?? null;
4665
4948
  }
4666
- function selectorOf(chain, valueMethods) {
4949
+ function selectorOf(chain, valueMethods, model) {
4950
+ const picking = laterCalls(chain).filter(
4951
+ (call) => !valueMethods.includes(methodNameOf(call))
4952
+ );
4667
4953
  return [
4668
- ...new Set(
4669
- laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap((call) => pickedBy(call).map((one) => one.name))
4670
- )
4954
+ .../* @__PURE__ */ new Set([
4955
+ ...picking.flatMap((call) => pickedBy(call).map((one) => one.name)),
4956
+ ...model === null ? [] : picking.flatMap((call) => columnsComparedBy(call, model))
4957
+ ])
4671
4958
  ];
4672
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
+ }
4673
4980
  var TABLE_METHODS = ["select_from", "join"];
4674
4981
  function modelOf(chain, options) {
4675
4982
  const fromRoot = modelArgument(chain.root, chain.root, options);
@@ -4734,14 +5041,15 @@ function isClassName(name) {
4734
5041
  }
4735
5042
  function effectFor2(pattern, chain, operation, options) {
4736
5043
  const valueMethods = pattern.valueMethods ?? [];
4737
- const picked = selectorOf(chain, valueMethods);
5044
+ const model = modelOf(chain, options);
5045
+ const picked = selectorOf(chain, valueMethods, model);
4738
5046
  const effect = {
4739
5047
  type: "interaction",
4740
5048
  binding: (0, import_ir_core2.storageBinding)({
4741
5049
  recognition: "python-storage",
4742
5050
  storageSystem: pattern.storageSystem,
4743
5051
  scope: "default",
4744
- container: modelOf(chain, options)
5052
+ container: model
4745
5053
  }),
4746
5054
  callee: chain.last.text,
4747
5055
  interaction: {
@@ -5017,10 +5325,7 @@ function literalAt(key, facts) {
5017
5325
 
5018
5326
  // src/paths/bodyBranches.ts
5019
5327
  function returnStatements(node, found = []) {
5020
- for (const child of node?.namedChildren ?? []) {
5021
- if (child === null) {
5022
- continue;
5023
- }
5328
+ for (const child of children(node)) {
5024
5329
  if (child.type === "function_definition" || child.type === "lambda") {
5025
5330
  continue;
5026
5331
  }
@@ -5077,7 +5382,8 @@ function enumerateBodyBranches(options) {
5077
5382
  const enumerated = (0, import_extractor4.enumerateOrDegrade)(
5078
5383
  {
5079
5384
  statements: lowered.statements,
5080
- terminalsByStmt: lowered.terminalsByStmt
5385
+ terminalsByStmt: lowered.terminalsByStmt,
5386
+ ...options.keepsArms === void 0 ? {} : { keepsArms: options.keepsArms }
5081
5387
  },
5082
5388
  statements
5083
5389
  );
@@ -5114,57 +5420,287 @@ function enumerateBodyBranches(options) {
5114
5420
  return branches;
5115
5421
  }
5116
5422
 
5117
- // src/clientCalls.ts
5118
- function clientCallUnits(root, module2, pack, pattern, options) {
5119
- const units = [];
5120
- for (const definition of functionDefinitions(root)) {
5121
- const name = field(definition, "name")?.text;
5122
- if (name === void 0) {
5123
- continue;
5124
- }
5125
- for (const call of bodyCalls(definition)) {
5126
- for (const request of requestCalls(call, pattern, module2, options)) {
5127
- units.push(
5128
- clientUnit(definition, name, request, pattern, pack, options)
5129
- );
5130
- }
5131
- }
5423
+ // src/paths/raisedResponses.ts
5424
+ var import_values12 = require("@suss/values");
5425
+ function statusNumberOf(node, options) {
5426
+ const value = (0, import_values12.constantOf)(evaluatedValue(node, options.facts));
5427
+ if (typeof value === "number") {
5428
+ return value;
5132
5429
  }
5133
- return units;
5430
+ const origin = originOf(node, options.module);
5431
+ return origin === null ? null : options.constants?.[`${origin.module}.${origin.name}`] ?? null;
5134
5432
  }
5135
- function clientCallReceivers(root, module2) {
5433
+ var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
5434
+ function raisedResponses(body, options) {
5435
+ if (options.calls.length === 0) {
5436
+ return [];
5437
+ }
5438
+ return raisesIn(body, options);
5439
+ }
5440
+ function raisesIn(body, options) {
5441
+ if (body === null) {
5442
+ return [];
5443
+ }
5136
5444
  const found = [];
5137
- for (const definition of functionDefinitions(root)) {
5138
- for (const call of bodyCalls(definition)) {
5139
- const callee = field(call, "function");
5140
- if (callee === null || originOf(callee, module2) !== null) {
5445
+ const walk = (node) => {
5446
+ for (const child of children(node)) {
5447
+ if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
5141
5448
  continue;
5142
5449
  }
5143
- const object = field(callee, "object");
5144
- if (object !== null && field(callee, "attribute") !== null) {
5145
- found.push(object);
5450
+ const response = raisedResponseOf(child, options);
5451
+ if (response !== null) {
5452
+ found.push(response);
5146
5453
  }
5454
+ walk(child);
5147
5455
  }
5148
- }
5456
+ };
5457
+ walk(body);
5149
5458
  return found;
5150
5459
  }
5151
- function functionDefinitions(node, found = []) {
5152
- for (const child of children(node)) {
5153
- if (isFunction(child)) {
5154
- found.push(child);
5155
- }
5156
- functionDefinitions(child, found);
5460
+ var ENDING_EXPRESSION = {
5461
+ raise_statement: (statement) => statement.namedChild(0) ?? null,
5462
+ expression_statement: (statement) => {
5463
+ const only = statement.namedChild(0);
5464
+ return only?.type === "call" ? only : null;
5157
5465
  }
5158
- return found;
5159
- }
5160
- function requestCalls(call, pattern, module2, options) {
5161
- const callee = field(call, "function");
5162
- const attribute = callee === null ? null : calledAttribute(callee, pattern, module2, options.facts);
5163
- if (attribute === null) {
5164
- return [];
5466
+ };
5467
+ function raisedResponseOf(statement, options) {
5468
+ const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
5469
+ if (expression === null) {
5470
+ return null;
5165
5471
  }
5166
- const plain = requestCall(call, attribute, pattern, options);
5167
- if (plain !== null && !(0, import_behavioral_ir2.hasNameHole)(plain.path)) {
5472
+ const thrownByCall = statement.type !== "raise_statement";
5473
+ const prebuilt = thrownByCall ? null : prebuiltCallOf(expression, options);
5474
+ if (prebuilt !== null) {
5475
+ return {
5476
+ statement,
5477
+ thrownByCall,
5478
+ terminal: terminalOf(
5479
+ statement,
5480
+ calleeOf(prebuilt.call, prebuilt.call),
5481
+ prebuilt.call,
5482
+ prebuilt.declared,
5483
+ prebuilt.options
5484
+ )
5485
+ };
5486
+ }
5487
+ const call = expression.type === "call" ? expression : null;
5488
+ const callee = calleeOf(expression, call);
5489
+ const declared = declaredCallFor(callee, options);
5490
+ if (declared === void 0 && thrownByCall) {
5491
+ return null;
5492
+ }
5493
+ return {
5494
+ statement,
5495
+ thrownByCall,
5496
+ terminal: terminalOf(statement, callee, call, declared, options)
5497
+ };
5498
+ }
5499
+ function prebuiltCallOf(expression, options) {
5500
+ if (expression.type !== "identifier" && expression.type !== "attribute") {
5501
+ return null;
5502
+ }
5503
+ const written = writtenNodeOf(expression, options.facts);
5504
+ if (written === null || written.type !== "call") {
5505
+ return null;
5506
+ }
5507
+ const inItsFile = { ...options, module: moduleOf(written) };
5508
+ const declared = declaredCallFor(calleeOf(written, written), inItsFile);
5509
+ return declared === void 0 ? null : { call: written, declared, options: inItsFile };
5510
+ }
5511
+ function returnedResponseStatus(statement, options) {
5512
+ const returned = statement.namedChild(0);
5513
+ if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
5514
+ return null;
5515
+ }
5516
+ const callee = field(returned, "function");
5517
+ if (callee === null) {
5518
+ return null;
5519
+ }
5520
+ const declared = declaredCallFor(callee, options);
5521
+ return declared === void 0 ? null : statusOf(declared, returned, options);
5522
+ }
5523
+ function calleeOf(expression, call) {
5524
+ return call === null ? expression : field(call, "function") ?? expression;
5525
+ }
5526
+ function declaredCallFor(callee, options) {
5527
+ const origin = originOf(callee, options.module);
5528
+ if (origin === null) {
5529
+ return void 0;
5530
+ }
5531
+ const qualified = `${origin.module}.${origin.name}`;
5532
+ return options.calls.find((call) => call.callee === qualified);
5533
+ }
5534
+ function terminalOf(statement, callee, call, declared, options) {
5535
+ return {
5536
+ kind: "throw",
5537
+ statusCode: declared === void 0 ? null : statusOf(declared, call, options),
5538
+ body: null,
5539
+ exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
5540
+ message: call === null ? null : writtenMessageOf(call),
5541
+ ...declared === void 0 ? {} : { producesResponse: true },
5542
+ component: null,
5543
+ renderTree: null,
5544
+ delegateTarget: null,
5545
+ emitEvent: null,
5546
+ location: rangeOf(statement)
5547
+ };
5548
+ }
5549
+ function lastSegmentOf(dotted) {
5550
+ const segment = dotted.split(".").pop();
5551
+ return segment === void 0 || segment === "" ? null : segment;
5552
+ }
5553
+ function statusOf(declared, call, options) {
5554
+ const written = call === null ? null : statusArgumentOf(declared, call);
5555
+ if (written === null) {
5556
+ return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
5557
+ }
5558
+ const value = statusNumberOf(written, options);
5559
+ return value === null ? { type: "dynamic", sourceText: written.text } : { type: "literal", value };
5560
+ }
5561
+ function argumentsOf2(call) {
5562
+ return children(field(call, "arguments"));
5563
+ }
5564
+ function statusArgumentOf(declared, call) {
5565
+ const args = argumentsOf2(call);
5566
+ const keyed = args.find(
5567
+ (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
5568
+ );
5569
+ if (keyed !== void 0) {
5570
+ return field(keyed, "value");
5571
+ }
5572
+ if (declared.statusArgument === void 0) {
5573
+ return null;
5574
+ }
5575
+ const positional = args.filter((arg) => arg.type !== "keyword_argument");
5576
+ return positional[declared.statusArgument] ?? null;
5577
+ }
5578
+ function writtenMessageOf(call) {
5579
+ for (const arg of argumentsOf2(call)) {
5580
+ const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
5581
+ const literal = value === null ? null : stringLiteralValue(value);
5582
+ if (literal !== null) {
5583
+ return literal;
5584
+ }
5585
+ }
5586
+ return null;
5587
+ }
5588
+
5589
+ // src/responseHandOff.ts
5590
+ var import_extractor5 = require("@suss/extractor");
5591
+ function throughResponseHelpers(branches, sites, options) {
5592
+ const handOffs = [];
5593
+ for (const { call, effect } of sites) {
5594
+ const helper = helperBranchesAt(call, options);
5595
+ if (helper !== null) {
5596
+ handOffs.push({
5597
+ helper,
5598
+ preconditions: effect.preconditions,
5599
+ line: rangeOf(call).start,
5600
+ at: rangeOf(call)
5601
+ });
5602
+ }
5603
+ }
5604
+ return handOffs.length === 0 ? [...branches] : branches.flatMap((branch) => (0, import_extractor5.splitAtHandOffs)(branch, handOffs));
5605
+ }
5606
+ function helperBranchesAt(call, options) {
5607
+ const handed = callArguments(call).filter(
5608
+ (argument) => argument.node.type === "identifier" && refersToResponse(argument.node, options)
5609
+ );
5610
+ if (handed.length === 0) {
5611
+ return null;
5612
+ }
5613
+ const helper = calledDefinition(call, options.facts);
5614
+ if (helper === null || helper.type !== "function_definition") {
5615
+ return null;
5616
+ }
5617
+ const passed = /* @__PURE__ */ new Map();
5618
+ const parameters = parameterShapes(helper, isMethod(helper) ? 1 : 0);
5619
+ for (const argument of handed) {
5620
+ const parameter = parameters.find(
5621
+ (one) => argument.kind === "positional" ? one.position === argument.position : one.name === argument.name
5622
+ );
5623
+ if (parameter !== void 0) {
5624
+ passed.set(parameter.name, argument.node.text);
5625
+ }
5626
+ }
5627
+ return passed.size === 0 ? null : (0, import_extractor5.helperPathsOnVariables)(options.readHelper(helper), passed);
5628
+ }
5629
+ function refersToResponse(name, options) {
5630
+ const written = awaited(writtenNodeOf(name, options.facts));
5631
+ if (written === null) {
5632
+ return false;
5633
+ }
5634
+ if (sameNode(written, options.request)) {
5635
+ return true;
5636
+ }
5637
+ const callee = written.type === "call" ? field(written, "function") : null;
5638
+ const method = callee === null ? null : field(callee, "attribute");
5639
+ const receiver = callee === null ? null : field(callee, "object");
5640
+ if (method === null || receiver?.type !== "identifier" || !options.bodyMethods.includes(method.text)) {
5641
+ return false;
5642
+ }
5643
+ const parsedFrom = awaited(writtenNodeOf(receiver, options.facts));
5644
+ return parsedFrom !== null && sameNode(parsedFrom, options.request);
5645
+ }
5646
+ function awaited(node) {
5647
+ return node?.type === "await" ? node.namedChild(0) ?? null : node;
5648
+ }
5649
+ function sameNode(a, b) {
5650
+ return a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.tree === b.tree;
5651
+ }
5652
+
5653
+ // src/clientCalls.ts
5654
+ function clientCallUnits(root, module2, pack, pattern, options) {
5655
+ const units = [];
5656
+ for (const definition of functionDefinitions(root)) {
5657
+ const name = field(definition, "name")?.text;
5658
+ if (name === void 0) {
5659
+ continue;
5660
+ }
5661
+ for (const call of bodyCalls(definition)) {
5662
+ for (const request of requestCalls(call, pattern, module2, options)) {
5663
+ units.push(
5664
+ clientUnit(definition, name, request, pattern, pack, options)
5665
+ );
5666
+ }
5667
+ }
5668
+ }
5669
+ return units;
5670
+ }
5671
+ function clientCallReceivers(root, module2) {
5672
+ const found = [];
5673
+ for (const definition of functionDefinitions(root)) {
5674
+ for (const call of bodyCalls(definition)) {
5675
+ const callee = field(call, "function");
5676
+ if (callee === null || originOf(callee, module2) !== null) {
5677
+ continue;
5678
+ }
5679
+ const object = field(callee, "object");
5680
+ if (object !== null && field(callee, "attribute") !== null) {
5681
+ found.push(object);
5682
+ }
5683
+ }
5684
+ }
5685
+ return found;
5686
+ }
5687
+ function functionDefinitions(node, found = []) {
5688
+ for (const child of children(node)) {
5689
+ if (isFunction(child)) {
5690
+ found.push(child);
5691
+ }
5692
+ functionDefinitions(child, found);
5693
+ }
5694
+ return found;
5695
+ }
5696
+ function requestCalls(call, pattern, module2, options) {
5697
+ const callee = field(call, "function");
5698
+ const attribute = callee === null ? null : calledAttribute(callee, pattern, module2, options.facts);
5699
+ if (attribute === null) {
5700
+ return [];
5701
+ }
5702
+ const plain = requestCall(call, attribute, pattern, options);
5703
+ if (plain !== null && !(0, import_behavioral_ir3.hasNameHole)(plain.path)) {
5168
5704
  return [plain];
5169
5705
  }
5170
5706
  const byBoundary = /* @__PURE__ */ new Map();
@@ -5173,7 +5709,7 @@ function requestCalls(call, pattern, module2, options) {
5173
5709
  if (stated === null) {
5174
5710
  continue;
5175
5711
  }
5176
- const key = `${stated.method} ${stated.path}`;
5712
+ const key = `${stated.method} ${stated.host ?? ""} ${stated.path}`;
5177
5713
  if (!byBoundary.has(key)) {
5178
5714
  byBoundary.set(key, stated);
5179
5715
  }
@@ -5184,16 +5720,33 @@ function requestCalls(call, pattern, module2, options) {
5184
5720
  return plain === null ? [] : [plain];
5185
5721
  }
5186
5722
  function requestCall(call, attribute, pattern, options, site) {
5723
+ const stated = methodAndPath(call, attribute, pattern, options, site);
5724
+ if (stated === null) {
5725
+ return null;
5726
+ }
5727
+ const redirectDelivery = redirectDeliveryAt(call, pattern, options, site);
5728
+ return redirectDelivery === void 0 ? stated : { ...stated, redirectDelivery };
5729
+ }
5730
+ function redirectDeliveryAt(call, pattern, options, site) {
5731
+ const option = pattern.response?.redirectOption;
5732
+ const argument = option === void 0 ? null : argumentAt(call, null, option.name);
5733
+ if (option === void 0 || argument === null) {
5734
+ return void 0;
5735
+ }
5736
+ const value = (0, import_values14.scalarOf)(evaluatedValue(argument, options.facts, site));
5737
+ return value === null ? void 0 : (0, import_extractor6.redirectDeliveryWhenSet)(option, value);
5738
+ }
5739
+ function methodAndPath(call, attribute, pattern, options, site) {
5187
5740
  const verb = pattern.verbAttributeNames[attribute];
5188
5741
  if (verb !== void 0) {
5189
- const path9 = urlAt(
5742
+ const target2 = urlAt(
5190
5743
  call,
5191
5744
  pattern.url.position,
5192
5745
  pattern.url.keyword,
5193
5746
  options,
5194
5747
  site
5195
5748
  );
5196
- return path9 === null ? null : { method: verb, path: path9, range: rangeOf(call) };
5749
+ return target2 === null ? null : { method: verb, ...target2, range: rangeOf(call), node: call };
5197
5750
  }
5198
5751
  const methodCall = pattern.methodCall;
5199
5752
  if (methodCall === void 0 || methodCall.attribute !== attribute) {
@@ -5208,14 +5761,19 @@ function requestCall(call, attribute, pattern, options, site) {
5208
5761
  if (method === null) {
5209
5762
  return null;
5210
5763
  }
5211
- const path8 = urlAt(
5764
+ const target = urlAt(
5212
5765
  call,
5213
5766
  methodCall.urlPosition,
5214
5767
  pattern.url.keyword,
5215
5768
  options,
5216
5769
  site
5217
5770
  );
5218
- return path8 === null ? null : { method: method.toUpperCase(), path: path8, range: rangeOf(call) };
5771
+ return target === null ? null : {
5772
+ method: method.toUpperCase(),
5773
+ ...target,
5774
+ range: rangeOf(call),
5775
+ node: call
5776
+ };
5219
5777
  }
5220
5778
  function calledAttribute(callee, pattern, module2, facts) {
5221
5779
  const origin = originOf(callee, module2);
@@ -5248,7 +5806,9 @@ function urlAt(call, position, keyword, options, site) {
5248
5806
  if (argument === null) {
5249
5807
  return null;
5250
5808
  }
5251
- return (0, import_values12.pathOf)(evaluatedValue(argument, options.facts, site)) ?? null;
5809
+ const value = evaluatedValue(argument, options.facts, site);
5810
+ const path8 = (0, import_values14.pathOf)(value);
5811
+ return path8 === void 0 ? null : { path: path8, host: (0, import_values14.hostOf)(value) };
5252
5812
  }
5253
5813
  function argumentAt(call, position, keyword) {
5254
5814
  const args = field(call, "arguments");
@@ -5266,25 +5826,33 @@ function argumentAt(call, position, keyword) {
5266
5826
  }
5267
5827
  positional.push(child);
5268
5828
  }
5269
- return positional[position] ?? null;
5829
+ return position === null ? null : positional[position] ?? null;
5270
5830
  }
5271
- function callerBranches(definition, range, facts) {
5831
+ function callerBranches(definition, reading) {
5832
+ const range = rangeOf(definition);
5272
5833
  const body = field(definition, "body");
5273
- const effects = invocationEffects(definition, facts);
5274
- const terminals = bodyTerminals(body, []);
5275
- if (body === null || terminals.length === 0) {
5834
+ const sites = invocationSites(definition, reading.facts);
5835
+ const effects = sites.map((site) => site.effect);
5836
+ if (body === null) {
5276
5837
  return [handsBack(range, effects)];
5277
5838
  }
5839
+ const raised = raisesIn(body, {
5840
+ calls: [],
5841
+ module: moduleOf(definition),
5842
+ facts: reading.facts
5843
+ });
5278
5844
  const branches = enumerateBodyBranches({
5279
5845
  body,
5280
- terminals,
5281
- raised: [],
5846
+ terminals: bodyTerminals(body, raised),
5847
+ raised,
5282
5848
  effects,
5283
- branchOf: (found) => found.type === "raise" ? { terminal: found.terminal, location: rangeOf(found.statement) } : handsBackAt(rangeOf(found.statement)),
5849
+ branchOf: (found2) => found2.type === "raise" ? { terminal: found2.terminal, location: rangeOf(found2.statement) } : handsBackAt(rangeOf(found2.statement)),
5284
5850
  fallthrough: handsBackAt(range),
5285
- facts
5851
+ facts: reading.facts,
5852
+ keepsArms: (condition) => (0, import_behavioral_ir3.testsStatus)(predicateOf(condition, reading.facts), reading.statusMembers)
5286
5853
  });
5287
- return branches.length === 0 ? [handsBack(range, effects)] : branches;
5854
+ const found = branches.length === 0 ? [handsBack(range, effects)] : branches;
5855
+ return reading.handOff === void 0 ? found : throughResponseHelpers(found, sites, reading.handOff);
5288
5856
  }
5289
5857
  function handsBackAt(range) {
5290
5858
  return {
@@ -5320,11 +5888,24 @@ function responseAccessors(pattern) {
5320
5888
  ...response.body === void 0 ? {} : { bodyAccessors: response.body },
5321
5889
  ...response.statusCode === void 0 ? {} : { statusAccessors: response.statusCode },
5322
5890
  ...response.success === void 0 ? {} : { successAccessors: response.success },
5323
- ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery }
5891
+ ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery },
5892
+ ...response.redirectDelivery === void 0 ? {} : { redirectDelivery: response.redirectDelivery }
5324
5893
  };
5325
5894
  }
5326
5895
  function clientUnit(definition, name, request, pattern, pack, options) {
5327
5896
  const range = rangeOf(definition);
5897
+ const accessors = responseAccessors(pattern);
5898
+ const statusMembers = (0, import_behavioral_ir3.statusMembersOf)(accessors);
5899
+ const reading = {
5900
+ facts: options.facts,
5901
+ statusMembers,
5902
+ handOff: {
5903
+ request: request.node,
5904
+ bodyMethods: accessors.bodyAccessors ?? [],
5905
+ facts: options.facts,
5906
+ readHelper: (helper) => callerBranches(helper, { facts: options.facts, statusMembers })
5907
+ }
5908
+ };
5328
5909
  return {
5329
5910
  identity: {
5330
5911
  name,
@@ -5336,15 +5917,17 @@ function clientUnit(definition, name, request, pattern, pack, options) {
5336
5917
  exportName: name,
5337
5918
  exportPath: [name]
5338
5919
  },
5339
- boundaryBinding: (0, import_behavioral_ir2.restBinding)({
5920
+ boundaryBinding: (0, import_behavioral_ir3.restBinding)({
5340
5921
  transport: pack.protocol,
5341
5922
  method: request.method,
5342
5923
  path: request.path,
5924
+ host: request.host,
5343
5925
  recognition: pack.name
5344
5926
  }),
5345
5927
  parameters: [],
5346
- branches: callerBranches(definition, range, options.facts),
5347
- ...responseAccessors(pattern),
5928
+ branches: callerBranches(definition, reading),
5929
+ ...accessors,
5930
+ ...request.redirectDelivery === void 0 ? {} : { redirectDelivery: request.redirectDelivery },
5348
5931
  bodyContent: "statements",
5349
5932
  dependencyCalls: [],
5350
5933
  declaredContract: null
@@ -5388,8 +5971,8 @@ function routeOnDroppedApp(facts, object, constructionKey, pattern) {
5388
5971
  }
5389
5972
 
5390
5973
  // src/envReads.ts
5391
- var import_behavioral_ir3 = require("@suss/behavioral-ir");
5392
- var import_extractor5 = require("@suss/extractor");
5974
+ var import_behavioral_ir4 = require("@suss/behavioral-ir");
5975
+ var import_extractor7 = require("@suss/extractor");
5393
5976
  var import_resolution7 = require("@suss/resolution");
5394
5977
 
5395
5978
  // src/envSpellings.ts
@@ -5567,7 +6150,7 @@ function isTestedAt(parent, child, subject, seen) {
5567
6150
  }
5568
6151
  function conditionOf(node) {
5569
6152
  if (node.type === "conditional_expression") {
5570
- return node.namedChildren[1] ?? null;
6153
+ return node.namedChild(1) ?? null;
5571
6154
  }
5572
6155
  return CONDITIONED_TYPES.has(node.type) ? field(node, "condition") : null;
5573
6156
  }
@@ -5590,7 +6173,7 @@ function isPresentAt(node, subject) {
5590
6173
  }
5591
6174
  function isBranchWherePresent(parent, child, subject) {
5592
6175
  if (parent.type === "conditional_expression") {
5593
- const [whenTrue, test2, whenFalse] = parent.namedChildren;
6176
+ const [whenTrue, test2, whenFalse] = children(parent);
5594
6177
  return test2 !== void 0 && test2 !== null && (whenTrue?.id === child.id && isPresentWhen(test2, true, subject) || whenFalse?.id === child.id && isPresentWhen(test2, false, subject));
5595
6178
  }
5596
6179
  if (parent.type === "boolean_operator") {
@@ -5628,7 +6211,7 @@ function followsExitWhenAbsent(parent, child, subject) {
5628
6211
  if (parent.type !== "block" && parent.type !== "module") {
5629
6212
  return false;
5630
6213
  }
5631
- const statements = parent.namedChildren;
6214
+ const statements = children(parent);
5632
6215
  const at = statements.findIndex((statement) => statement?.id === child.id);
5633
6216
  return statements.slice(0, at).some(
5634
6217
  (statement) => statement !== null && leavesWhenAbsent(statement, subject)
@@ -5672,7 +6255,7 @@ function continuationRaises(from) {
5672
6255
  let parent = current.parent;
5673
6256
  while (parent !== null) {
5674
6257
  if (parent.type === "block" || parent.type === "module") {
5675
- const statements = parent.namedChildren;
6258
+ const statements = children(parent);
5676
6259
  const at = statements.findIndex((one) => one?.id === current.id);
5677
6260
  const exit = firstExit(statements.slice(at + 1));
5678
6261
  if (exit !== null) {
@@ -5716,7 +6299,7 @@ function exitOf(statement) {
5716
6299
  if (LEAVING_TYPES.has(statement.type)) {
5717
6300
  return "leave";
5718
6301
  }
5719
- return statement.type === "block" ? firstExit(statement.namedChildren) : null;
6302
+ return statement.type === "block" ? firstExit(children(statement)) : null;
5720
6303
  }
5721
6304
  function isPresentWhen(test, outcome, subject) {
5722
6305
  const expression = peelParens(test);
@@ -5756,7 +6339,7 @@ function isPresentAfterComparison(comparison, outcome, subject) {
5756
6339
  }
5757
6340
  function comparisonParts(comparison) {
5758
6341
  const operators = fields(comparison, "operators");
5759
- const operands = comparison.namedChildren.filter(
6342
+ const operands = children(comparison).filter(
5760
6343
  (child) => child !== null && !operators.some((one) => one.id === child.id)
5761
6344
  );
5762
6345
  const [left, right] = operands;
@@ -5779,7 +6362,7 @@ function comparedWithNone(comparison) {
5779
6362
  }
5780
6363
  var NONE_OPERATORS = /* @__PURE__ */ new Set(["is", "is not", "==", "!="]);
5781
6364
  function peelParens(node) {
5782
- const inner = node.type === "parenthesized_expression" ? node.namedChildren[0] : null;
6365
+ const inner = node.type === "parenthesized_expression" ? node.namedChild(0) : null;
5783
6366
  return inner === null || inner === void 0 ? node : peelParens(inner);
5784
6367
  }
5785
6368
  function climbParens(node) {
@@ -5837,7 +6420,7 @@ function envReadEffects(root, module2, facts) {
5837
6420
  const slots = [];
5838
6421
  const readsHelpers = facts !== void 0 && sitesByDb.has(facts);
5839
6422
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
5840
- (0, import_extractor5.walkDescendants)(root, startScope, {
6423
+ (0, import_extractor7.walkDescendants)(root, startScope, children, {
5841
6424
  at: (node, scope) => {
5842
6425
  const syntax = envReadSyntaxAt(node, scope);
5843
6426
  if (syntax !== null) {
@@ -5853,7 +6436,7 @@ function envReadEffects(root, module2, facts) {
5853
6436
  },
5854
6437
  into: (node, scope) => {
5855
6438
  if (DEFERRED_BODY_TYPES.has(node.type)) {
5856
- return import_extractor5.SKIP_CHILDREN;
6439
+ return import_extractor7.SKIP_CHILDREN;
5857
6440
  }
5858
6441
  return module2.scopeFor.get(node.id) ?? scope;
5859
6442
  }
@@ -5872,7 +6455,7 @@ function envReadEffects(root, module2, facts) {
5872
6455
  function configReadEffect(read) {
5873
6456
  return {
5874
6457
  type: "interaction",
5875
- binding: (0, import_behavioral_ir3.runtimeConfigBinding)({ recognition: PYTHON_ENV_RECOGNITION }),
6458
+ binding: (0, import_behavioral_ir4.runtimeConfigBinding)({ recognition: PYTHON_ENV_RECOGNITION }),
5876
6459
  callee: `os.environ["${read.name}"]`,
5877
6460
  interaction: {
5878
6461
  class: "config-read",
@@ -5896,7 +6479,7 @@ function hasFallback(node, spelling, scope) {
5896
6479
  }
5897
6480
  function envFactsIn(filePath, root, module2) {
5898
6481
  const found = { sites: [], objects: [] };
5899
- (0, import_extractor5.walkDescendants)(root, module2.moduleScope, {
6482
+ (0, import_extractor7.walkDescendants)(root, module2.moduleScope, children, {
5900
6483
  at: (node, scope) => {
5901
6484
  if (isEnviron(node, scope) && handsOnward(node)) {
5902
6485
  found.objects.push(keyOf(filePath, node));
@@ -6114,137 +6697,6 @@ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6114
6697
  );
6115
6698
  }
6116
6699
 
6117
- // src/paths/raisedResponses.ts
6118
- var import_values17 = require("@suss/values");
6119
- var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
6120
- function raisedResponses(body, options) {
6121
- if (body === null || options.calls.length === 0) {
6122
- return [];
6123
- }
6124
- const found = [];
6125
- const walk = (node) => {
6126
- for (const child of node.namedChildren) {
6127
- if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
6128
- continue;
6129
- }
6130
- const response = raisedResponseOf(child, options);
6131
- if (response !== null) {
6132
- found.push(response);
6133
- }
6134
- walk(child);
6135
- }
6136
- };
6137
- walk(body);
6138
- return found;
6139
- }
6140
- var ENDING_EXPRESSION = {
6141
- raise_statement: (statement) => statement.namedChildren[0] ?? null,
6142
- expression_statement: (statement) => {
6143
- const only = statement.namedChildren[0];
6144
- return only?.type === "call" ? only : null;
6145
- }
6146
- };
6147
- function raisedResponseOf(statement, options) {
6148
- const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
6149
- if (expression === null) {
6150
- return null;
6151
- }
6152
- const thrownByCall = statement.type !== "raise_statement";
6153
- const call = expression.type === "call" ? expression : null;
6154
- const callee = calleeOf(expression, call);
6155
- const declared = declaredCallFor(callee, options);
6156
- if (declared === void 0 && thrownByCall) {
6157
- return null;
6158
- }
6159
- return {
6160
- statement,
6161
- thrownByCall,
6162
- terminal: terminalOf(statement, callee, call, declared, options.facts)
6163
- };
6164
- }
6165
- function returnedResponseStatus(statement, options) {
6166
- const returned = statement.namedChildren[0];
6167
- if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
6168
- return null;
6169
- }
6170
- const callee = field(returned, "function");
6171
- if (callee === null) {
6172
- return null;
6173
- }
6174
- const declared = declaredCallFor(callee, options);
6175
- return declared === void 0 ? null : statusOf(declared, returned, options.facts);
6176
- }
6177
- function calleeOf(expression, call) {
6178
- return call === null ? expression : field(call, "function") ?? expression;
6179
- }
6180
- function declaredCallFor(callee, options) {
6181
- const origin = originOf(callee, options.module);
6182
- if (origin === null) {
6183
- return void 0;
6184
- }
6185
- const qualified = `${origin.module}.${origin.name}`;
6186
- return options.calls.find((call) => call.callee === qualified);
6187
- }
6188
- function terminalOf(statement, callee, call, declared, facts) {
6189
- return {
6190
- kind: "throw",
6191
- statusCode: declared === void 0 ? null : statusOf(declared, call, facts),
6192
- body: null,
6193
- exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
6194
- message: call === null ? null : writtenMessageOf(call),
6195
- ...declared === void 0 ? {} : { producesResponse: true },
6196
- component: null,
6197
- renderTree: null,
6198
- delegateTarget: null,
6199
- emitEvent: null,
6200
- location: rangeOf(statement)
6201
- };
6202
- }
6203
- function lastSegmentOf(dotted) {
6204
- const segment = dotted.split(".").pop();
6205
- return segment === void 0 || segment === "" ? null : segment;
6206
- }
6207
- function statusOf(declared, call, facts) {
6208
- const written = call === null ? null : statusArgumentOf(declared, call);
6209
- if (written === null) {
6210
- return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
6211
- }
6212
- const value = (0, import_values17.constantOf)(evaluatedValue(written, facts));
6213
- if (typeof value === "number") {
6214
- return { type: "literal", value };
6215
- }
6216
- return { type: "dynamic", sourceText: written.text };
6217
- }
6218
- function argumentsOf2(call) {
6219
- return (field(call, "arguments")?.namedChildren ?? []).filter(
6220
- (child) => child !== null
6221
- );
6222
- }
6223
- function statusArgumentOf(declared, call) {
6224
- const args = argumentsOf2(call);
6225
- const keyed = args.find(
6226
- (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
6227
- );
6228
- if (keyed !== void 0) {
6229
- return field(keyed, "value");
6230
- }
6231
- if (declared.statusArgument === void 0) {
6232
- return null;
6233
- }
6234
- const positional = args.filter((arg) => arg.type !== "keyword_argument");
6235
- return positional[declared.statusArgument] ?? null;
6236
- }
6237
- function writtenMessageOf(call) {
6238
- for (const arg of argumentsOf2(call)) {
6239
- const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
6240
- const literal = value === null ? null : stringLiteralValue(value);
6241
- if (literal !== null) {
6242
- return literal;
6243
- }
6244
- }
6245
- return null;
6246
- }
6247
-
6248
6700
  // src/paths/returnedShape.ts
6249
6701
  var MAX_DEPTH = 6;
6250
6702
  var SCALARS = {
@@ -6304,7 +6756,7 @@ function shapeOfReturned(node, depth = 0) {
6304
6756
  return { type: "unknown" };
6305
6757
  }
6306
6758
  function returnedBodyShape(statement) {
6307
- const returned = statement.namedChildren[0];
6759
+ const returned = statement.namedChild(0);
6308
6760
  if (returned == null) {
6309
6761
  return null;
6310
6762
  }
@@ -6317,8 +6769,8 @@ function returnedBodyShape(statement) {
6317
6769
  }
6318
6770
 
6319
6771
  // src/streamWrites.ts
6320
- var import_behavioral_ir4 = require("@suss/behavioral-ir");
6321
- var import_extractor6 = require("@suss/extractor");
6772
+ var import_behavioral_ir5 = require("@suss/behavioral-ir");
6773
+ var import_extractor8 = require("@suss/extractor");
6322
6774
 
6323
6775
  // src/stdlibNames.ts
6324
6776
  function isStdlibModule(node, scope, module2) {
@@ -6328,7 +6780,7 @@ function isStdlibModule(node, scope, module2) {
6328
6780
  const binding = resolveName(scope, node.text);
6329
6781
  return binding?.kind === "import" && binding.module === module2 && binding.relativeLevel === 0;
6330
6782
  }
6331
- function isBuiltin(node, scope, name) {
6783
+ function isBuiltin2(node, scope, name) {
6332
6784
  return node.type === "identifier" && node.text === name && resolveName(scope, name) === null;
6333
6785
  }
6334
6786
  function isStdlibMember(node, scope, module2, name) {
@@ -6350,7 +6802,7 @@ var DEFERRED_BODY_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lamb
6350
6802
  function streamWriteEffects(root, module2) {
6351
6803
  const found = [];
6352
6804
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
6353
- (0, import_extractor6.walkDescendants)(root, startScope, {
6805
+ (0, import_extractor8.walkDescendants)(root, startScope, children, {
6354
6806
  at: (node, scope) => {
6355
6807
  if (node.type !== "call") {
6356
6808
  return;
@@ -6362,7 +6814,7 @@ function streamWriteEffects(root, module2) {
6362
6814
  },
6363
6815
  into: (node, scope) => {
6364
6816
  if (DEFERRED_BODY_TYPES2.has(node.type)) {
6365
- return import_extractor6.SKIP_CHILDREN;
6817
+ return import_extractor8.SKIP_CHILDREN;
6366
6818
  }
6367
6819
  return module2.scopeFor.get(node.id) ?? scope;
6368
6820
  }
@@ -6376,7 +6828,7 @@ function streamWriteAt(call, scope) {
6376
6828
  }
6377
6829
  const written = callArguments(call);
6378
6830
  const positional = written.filter((argument) => argument.kind === "positional").map((argument) => argument.node);
6379
- if (callee.type === "identifier" && isBuiltin(callee, scope, "print")) {
6831
+ if (callee.type === "identifier" && isBuiltin2(callee, scope, "print")) {
6380
6832
  const file = written.find(
6381
6833
  (argument) => argument.kind === "keyword" && argument.name === "file"
6382
6834
  );
@@ -6398,7 +6850,7 @@ function writeEffect(site) {
6398
6850
  const { call, callee, target } = site;
6399
6851
  return {
6400
6852
  type: "interaction",
6401
- binding: (0, import_behavioral_ir4.ioBinding)({ recognition: PYTHON_STREAM_RECOGNITION, target }),
6853
+ binding: (0, import_behavioral_ir5.ioBinding)({ recognition: PYTHON_STREAM_RECOGNITION, target }),
6402
6854
  callee,
6403
6855
  groupId: `${call.startPosition.row + 1}:${call.startPosition.column + 1}`,
6404
6856
  interaction: {
@@ -6437,7 +6889,7 @@ function jsonDumped(node, scope) {
6437
6889
  // src/discovery.ts
6438
6890
  function decoratedNodes(root) {
6439
6891
  const found = [];
6440
- (0, import_extractor7.walkDescendants)(root, null, {
6892
+ (0, import_extractor9.walkDescendants)(root, null, children, {
6441
6893
  at: (node) => {
6442
6894
  if (node.type !== "decorated_definition") {
6443
6895
  return;
@@ -6588,18 +7040,18 @@ function unitsFor(pattern, pack, decoratorModule, classification, definition, mo
6588
7040
  );
6589
7041
  }
6590
7042
  };
6591
- return (0, import_behavioral_ir5.dispatchByType)(table, pattern);
7043
+ return (0, import_behavioral_ir6.dispatchByType)(table, pattern);
6592
7044
  }
6593
7045
  function readPathArgument(classification, options) {
6594
7046
  const first = classification.args[0];
6595
7047
  if (first !== void 0) {
6596
7048
  const value = evaluatedValue(first.node, options.facts);
6597
- const path8 = (0, import_values19.literalOf)(value) ?? (0, import_values19.pathOf)(value);
7049
+ const path8 = (0, import_values20.literalOf)(value) ?? (0, import_values20.pathOf)(value);
6598
7050
  if (path8 !== void 0) {
6599
- return (0, import_extractor7.writtenReading)(path8, classification.range);
7051
+ return (0, import_extractor9.writtenReading)(path8, classification.range);
6600
7052
  }
6601
7053
  }
6602
- return (0, import_extractor7.unreadableReading)(
7054
+ return (0, import_extractor9.unreadableReading)(
6603
7055
  "The path in this route's decorator is not a string literal, so the binding names no path and nothing pairs with it",
6604
7056
  classification.range
6605
7057
  );
@@ -6624,16 +7076,16 @@ var PATH_TEMPLATE_READERS = {
6624
7076
  function readPathTemplate(pattern, path8, range) {
6625
7077
  const syntax = pattern.pathParamSyntax;
6626
7078
  if (syntax === void 0) {
6627
- return (0, import_extractor7.writtenReading)({ path: path8, paramNames: NO_PATH_PARAMS }, range);
7079
+ return (0, import_extractor9.writtenReading)({ path: path8, paramNames: NO_PATH_PARAMS }, range);
6628
7080
  }
6629
7081
  const reader = PATH_TEMPLATE_READERS[syntax];
6630
7082
  if (reader === void 0) {
6631
- return (0, import_extractor7.unreadableReading)(
7083
+ return (0, import_extractor9.unreadableReading)(
6632
7084
  `The route's pack declares path-parameter syntax "${syntax}", which this adapter has no reader for, so the binding names no path and nothing pairs with it`,
6633
7085
  range
6634
7086
  );
6635
7087
  }
6636
- return (0, import_extractor7.writtenReading)(reader(path8), range);
7088
+ return (0, import_extractor9.writtenReading)(reader(path8), range);
6637
7089
  }
6638
7090
  function classRouteUnits(pattern, pack, classification, classNode, module2, options) {
6639
7091
  const pathArgument = readPathArgument(classification, options);
@@ -6684,7 +7136,7 @@ function classRouteUnits(pattern, pack, classification, classNode, module2, opti
6684
7136
  module: module2,
6685
7137
  filePath: options.filePath,
6686
7138
  skipReceiverParam: true,
6687
- responseShape: import_extractor7.absentReading,
7139
+ responseShape: import_extractor9.absentReading,
6688
7140
  statusCode: defaultedStatus(
6689
7141
  readReturnedStatus(pattern, maybeMethod, {
6690
7142
  module: module2,
@@ -6711,11 +7163,11 @@ function classRouteUnits(pattern, pack, classification, classNode, module2, opti
6711
7163
  return units;
6712
7164
  }
6713
7165
  function readRoutePath(pattern, pathArgument, classification, module2, options) {
6714
- const composed = (0, import_extractor7.andThenReading)(
7166
+ const composed = (0, import_extractor9.andThenReading)(
6715
7167
  pathArgument,
6716
7168
  (literal, range) => composeRoutePath(pattern, classification, module2, options, literal, range)
6717
7169
  );
6718
- return (0, import_extractor7.andThenReading)(
7170
+ return (0, import_extractor9.andThenReading)(
6719
7171
  composed,
6720
7172
  (path8, range) => readPathTemplate(pattern, path8, range)
6721
7173
  );
@@ -6741,10 +7193,10 @@ function readRoutePaths(pattern, pathArgument, classification, module2, options)
6741
7193
  ];
6742
7194
  }
6743
7195
  return resolution.values.map((prefix) => ({
6744
- routePath: (0, import_extractor7.andThenReading)(
6745
- (0, import_extractor7.andThenReading)(
7196
+ routePath: (0, import_extractor9.andThenReading)(
7197
+ (0, import_extractor9.andThenReading)(
6746
7198
  pathArgument,
6747
- (literal, range) => (0, import_extractor7.writtenReading)(servedSpelling(pattern, prefix + literal), range)
7199
+ (literal, range) => (0, import_extractor9.writtenReading)(servedSpelling(pattern, prefix + literal), range)
6748
7200
  ),
6749
7201
  (path8, range) => readPathTemplate(pattern, path8, range)
6750
7202
  ),
@@ -6785,9 +7237,9 @@ function composeRoutePath(pattern, classification, module2, options, literal, ra
6785
7237
  range
6786
7238
  );
6787
7239
  if (prefix.kind === "absent") {
6788
- return (0, import_extractor7.writtenReading)(servedSpelling(pattern, literal), range);
7240
+ return (0, import_extractor9.writtenReading)(servedSpelling(pattern, literal), range);
6789
7241
  }
6790
- return (0, import_extractor7.mapReading)(
7242
+ return (0, import_extractor9.mapReading)(
6791
7243
  prefix,
6792
7244
  (value) => servedSpelling(pattern, value + literal)
6793
7245
  );
@@ -6807,32 +7259,36 @@ function readRouterPrefix(pattern, classification, module2, options, range) {
6807
7259
  options
6808
7260
  );
6809
7261
  if (resolution === null) {
6810
- return import_extractor7.absentReading;
7262
+ return import_extractor9.absentReading;
6811
7263
  }
6812
7264
  if (resolution.kind === "abstain") {
6813
- return (0, import_extractor7.unreadableReading)(
7265
+ return (0, import_extractor9.unreadableReading)(
6814
7266
  `The router this route is declared on ${resolution.reason}, so the binding names no path and nothing pairs with it`,
6815
7267
  range
6816
7268
  );
6817
7269
  }
6818
7270
  if (resolution.kind === "composed") {
6819
- return (0, import_extractor7.writtenReading)(resolution.value, range);
7271
+ return (0, import_extractor9.writtenReading)(resolution.value, range);
6820
7272
  }
6821
- return import_extractor7.absentReading;
7273
+ return import_extractor9.absentReading;
6822
7274
  }
6823
- function readStatusCode(pattern, classification) {
7275
+ function readStatusCode(pattern, classification, options) {
6824
7276
  if (pattern.statusCodeKeyword === void 0) {
6825
- return import_extractor7.absentReading;
7277
+ return import_extractor9.absentReading;
6826
7278
  }
6827
7279
  const arg = classification.keywordArgs[pattern.statusCodeKeyword];
6828
7280
  if (arg === void 0) {
6829
- return import_extractor7.absentReading;
7281
+ return import_extractor9.absentReading;
6830
7282
  }
6831
- if (arg.kind === "number") {
6832
- return (0, import_extractor7.writtenReading)(arg.value, classification.range);
7283
+ const status = statusNumberOf(arg.node, {
7284
+ ...options,
7285
+ constants: pattern.statusCodeConstants ?? {}
7286
+ });
7287
+ if (status !== null) {
7288
+ return (0, import_extractor9.writtenReading)(status, classification.range);
6833
7289
  }
6834
- return (0, import_extractor7.unreadableReading)(
6835
- "The status this route's decorator states is not a literal number, so the response claims no status",
7290
+ return (0, import_extractor9.unreadableReading)(
7291
+ "The status this route's decorator states does not come to a number here, so the response claims no status",
6836
7292
  classification.range
6837
7293
  );
6838
7294
  }
@@ -6841,11 +7297,11 @@ function statusFromReturnedValue(node, facts) {
6841
7297
  return null;
6842
7298
  }
6843
7299
  const value = evaluatedValue(node, facts);
6844
- const number = (0, import_values19.constantOf)(value);
7300
+ const number = (0, import_values20.constantOf)(value);
6845
7301
  if (typeof number === "number") {
6846
7302
  return Number.isInteger(number) ? number : null;
6847
7303
  }
6848
- const text2 = (0, import_values19.literalOf)(value);
7304
+ const text2 = (0, import_values20.literalOf)(value);
6849
7305
  if (text2 === null) {
6850
7306
  return null;
6851
7307
  }
@@ -6853,30 +7309,49 @@ function statusFromReturnedValue(node, facts) {
6853
7309
  return digits === null ? null : Number.parseInt(digits[1], 10);
6854
7310
  }
6855
7311
  function statusOfReturn(statement, facts) {
6856
- const returned = statement.namedChildren[0];
7312
+ const returned = statement.namedChild(0);
6857
7313
  if (returned?.type !== "expression_list") {
6858
7314
  return { kind: "none" };
6859
7315
  }
6860
- const status = statusFromReturnedValue(returned.namedChildren[1], facts);
7316
+ const status = statusFromReturnedValue(
7317
+ returned.namedChild(1) ?? void 0,
7318
+ facts
7319
+ );
6861
7320
  return status === null ? { kind: "unreadable" } : { kind: "status", value: status };
6862
7321
  }
6863
7322
  function returnStatusReading(statement, ctx) {
6864
7323
  const read = statusOfReturnIn(statement, ctx.pattern, ctx);
6865
7324
  const range = rangeOf(statement);
6866
7325
  if (read.kind === "status") {
6867
- return (0, import_extractor7.writtenReading)(read.value, range);
7326
+ return (0, import_extractor9.writtenReading)(read.value, range);
6868
7327
  }
6869
7328
  if (read.kind === "unreadable") {
6870
- return (0, import_extractor7.unreadableReading)(
7329
+ return (0, import_extractor9.unreadableReading)(
6871
7330
  "This return gives a status this reading cannot resolve to a number, so this outcome claims none",
6872
7331
  range
6873
7332
  );
6874
7333
  }
6875
- return ctx.pattern.statusFromReturnedTuple === true ? import_extractor7.absentReading : ctx.declaredStatus.reading;
7334
+ return ctx.pattern.statusFromReturnedTuple === true ? import_extractor9.absentReading : ctx.declaredStatus.reading;
6876
7335
  }
6877
7336
  function returnBranch(statement, ctx) {
6878
7337
  const range = rangeOf(statement);
6879
7338
  const writtenBody = returnedBodyShape(statement);
7339
+ return responseBranchAt(
7340
+ range,
7341
+ returnStatusReading(statement, ctx),
7342
+ writtenBody === null ? ctx.responseShape.reading : (0, import_extractor9.writtenReading)(writtenBody, range),
7343
+ ctx
7344
+ );
7345
+ }
7346
+ function fallthroughBranch(definitionNode, ctx) {
7347
+ return responseBranchAt(
7348
+ rangeOf(definitionNode),
7349
+ ctx.pattern.statusFromReturnedTuple === true ? import_extractor9.absentReading : ctx.declaredStatus.reading,
7350
+ ctx.responseShape.reading,
7351
+ ctx
7352
+ );
7353
+ }
7354
+ function responseBranchAt(range, status, body, ctx) {
6880
7355
  return {
6881
7356
  terminal: {
6882
7357
  kind: "response",
@@ -6893,12 +7368,10 @@ function returnBranch(statement, ctx) {
6893
7368
  location: range
6894
7369
  },
6895
7370
  statusCodeReading: {
6896
- reading: returnStatusReading(statement, ctx),
7371
+ reading: status,
6897
7372
  ...ctx.declaredStatus.libraryDefault !== void 0 ? { libraryDefault: ctx.declaredStatus.libraryDefault } : {}
6898
7373
  },
6899
- bodyShapeReading: {
6900
- reading: writtenBody === null ? ctx.responseShape.reading : (0, import_extractor7.writtenReading)(writtenBody, range)
6901
- },
7374
+ bodyShapeReading: { reading: body },
6902
7375
  location: range
6903
7376
  };
6904
7377
  }
@@ -6912,18 +7385,19 @@ function terminalBranchOf(found, ctx) {
6912
7385
  // A route has no continuation, so none is ever collected for it.
6913
7386
  continuation: (variant) => returnBranch(variant.statement, ctx)
6914
7387
  };
6915
- return (0, import_behavioral_ir5.dispatchByType)(table, found);
7388
+ return (0, import_behavioral_ir6.dispatchByType)(table, found);
6916
7389
  }
6917
7390
  function branchesPerTerminal(options) {
6918
7391
  const body = field(options.definitionNode, "body");
6919
7392
  const raised = raisedResponses(body, {
6920
7393
  calls: options.responseStatusCalls,
6921
7394
  module: options.ctx.module,
6922
- facts: options.ctx.facts
7395
+ facts: options.ctx.facts,
7396
+ constants: options.ctx.pattern.statusCodeConstants ?? {}
6923
7397
  });
6924
7398
  const terminals = bodyTerminals(body, raised);
6925
7399
  const soleReturnWritesBody = terminals.length === 1 && terminals[0]?.type === "return" && returnedBodyShape(terminals[0].statement) !== null;
6926
- if (terminals.length < 2 && !soleReturnWritesBody) {
7400
+ if (terminals.length === 0) {
6927
7401
  return null;
6928
7402
  }
6929
7403
  const branches = enumerateBodyBranches({
@@ -6932,6 +7406,7 @@ function branchesPerTerminal(options) {
6932
7406
  raised,
6933
7407
  effects: options.effects,
6934
7408
  branchOf: (found) => terminalBranchOf(found, options.ctx),
7409
+ fallthrough: fallthroughBranch(options.definitionNode, options.ctx),
6935
7410
  facts: options.ctx.facts
6936
7411
  });
6937
7412
  return branches.length > 1 || soleReturnWritesBody ? branches : null;
@@ -6940,7 +7415,8 @@ function statusOfReturnIn(statement, pattern, options) {
6940
7415
  const constructed = returnedResponseStatus(statement, {
6941
7416
  calls: pattern.responseConstructors ?? [],
6942
7417
  module: options.module,
6943
- facts: options.facts
7418
+ facts: options.facts,
7419
+ constants: pattern.statusCodeConstants ?? {}
6944
7420
  });
6945
7421
  if (constructed !== null) {
6946
7422
  return constructed.type === "literal" ? { kind: "status", value: constructed.value } : { kind: "unreadable" };
@@ -6952,7 +7428,7 @@ function statusOfReturnIn(statement, pattern, options) {
6952
7428
  }
6953
7429
  function readReturnedStatus(pattern, definitionNode, options) {
6954
7430
  if (pattern.statusFromReturnedTuple !== true && (pattern.responseConstructors ?? []).length === 0) {
6955
- return import_extractor7.absentReading;
7431
+ return import_extractor9.absentReading;
6956
7432
  }
6957
7433
  const range = rangeOf(definitionNode);
6958
7434
  const statuses = /* @__PURE__ */ new Set();
@@ -6969,23 +7445,23 @@ function readReturnedStatus(pattern, definitionNode, options) {
6969
7445
  }
6970
7446
  }
6971
7447
  if (unreadable > 0) {
6972
- return (0, import_extractor7.unreadableReading)(
7448
+ return (0, import_extractor9.unreadableReading)(
6973
7449
  "This route returns a status this reading cannot resolve to a number, so no status is claimed for it",
6974
7450
  range
6975
7451
  );
6976
7452
  }
6977
7453
  if (statuses.size === 0) {
6978
- return import_extractor7.absentReading;
7454
+ return import_extractor9.absentReading;
6979
7455
  }
6980
7456
  const candidates = [...statuses];
6981
7457
  if (statuses.size > 1 || plainReturns > 0) {
6982
- return (0, import_extractor7.ambiguousReading)(
7458
+ return (0, import_extractor9.ambiguousReading)(
6983
7459
  candidates,
6984
7460
  "This route returns more than one status depending on which branch runs, and nothing here reads which, so no status is claimed for it",
6985
7461
  range
6986
7462
  );
6987
7463
  }
6988
- return (0, import_extractor7.writtenReading)(candidates[0], range);
7464
+ return (0, import_extractor9.writtenReading)(candidates[0], range);
6989
7465
  }
6990
7466
  function declaredOrReturnedStatus(declared, returned) {
6991
7467
  return declared.kind === "absent" ? returned : declared;
@@ -6998,19 +7474,19 @@ function defaultedStatus(reading, pattern) {
6998
7474
  }
6999
7475
  function readResponseModel(pattern, classification, module2, ctx) {
7000
7476
  if (pattern.responseModelKeyword === void 0) {
7001
- return import_extractor7.absentReading;
7477
+ return import_extractor9.absentReading;
7002
7478
  }
7003
7479
  const arg = classification.keywordArgs[pattern.responseModelKeyword];
7004
7480
  if (arg === void 0) {
7005
- return import_extractor7.absentReading;
7481
+ return import_extractor9.absentReading;
7006
7482
  }
7007
7483
  if (arg.kind === "identifier") {
7008
- return (0, import_extractor7.writtenReading)(
7484
+ return (0, import_extractor9.writtenReading)(
7009
7485
  shapeFromName(arg.name, module2.moduleScope, ctx),
7010
7486
  classification.range
7011
7487
  );
7012
7488
  }
7013
- return (0, import_extractor7.unreadableReading)(
7489
+ return (0, import_extractor9.unreadableReading)(
7014
7490
  "The response model this route's decorator states is not a name, so the response claims no body shape",
7015
7491
  classification.range
7016
7492
  );
@@ -7018,9 +7494,9 @@ function readResponseModel(pattern, classification, module2, ctx) {
7018
7494
  function readReturnAnnotation(definitionNode, scope, ctx) {
7019
7495
  const returnTypeNode = field(definitionNode, "return_type");
7020
7496
  if (returnTypeNode === null) {
7021
- return import_extractor7.absentReading;
7497
+ return import_extractor9.absentReading;
7022
7498
  }
7023
- return (0, import_extractor7.writtenReading)(
7499
+ return (0, import_extractor9.writtenReading)(
7024
7500
  annotationToShape(returnTypeNode, scope, ctx),
7025
7501
  rangeOf(returnTypeNode)
7026
7502
  );
@@ -7034,6 +7510,25 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7034
7510
  module2.scopeFor,
7035
7511
  options.importedDefinition ?? null
7036
7512
  );
7513
+ const wrappers = wrappersAround(
7514
+ pattern,
7515
+ pack,
7516
+ classification,
7517
+ functionNode,
7518
+ module2,
7519
+ options
7520
+ );
7521
+ const dependencyParameters = options.wrappers?.dependencyParameters(
7522
+ wrappers
7523
+ ) ?? { names: /* @__PURE__ */ new Set(), declaresSource: false };
7524
+ const declaredStatuses = declaredStatusesOf(
7525
+ pattern,
7526
+ pack,
7527
+ classification,
7528
+ functionNode,
7529
+ module2,
7530
+ options
7531
+ );
7037
7532
  return readRoutePaths(
7038
7533
  pattern,
7039
7534
  readPathArgument(classification, options),
@@ -7046,6 +7541,8 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7046
7541
  pack,
7047
7542
  name: functionName,
7048
7543
  exportPath: [functionName],
7544
+ dependencyParameters,
7545
+ declaredStatuses,
7049
7546
  method: verb,
7050
7547
  routePath: entry.routePath,
7051
7548
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
@@ -7062,7 +7559,10 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7062
7559
  responseShape: readResponseModel(pattern, classification, module2, ctx),
7063
7560
  statusCode: defaultedStatus(
7064
7561
  declaredOrReturnedStatus(
7065
- readStatusCode(pattern, classification),
7562
+ readStatusCode(pattern, classification, {
7563
+ module: module2,
7564
+ facts: options.facts
7565
+ }),
7066
7566
  readReturnedStatus(pattern, functionNode, {
7067
7567
  module: module2,
7068
7568
  facts: options.facts
@@ -7073,14 +7573,7 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7073
7573
  definitionsCtx: ctx,
7074
7574
  storage: options.storage,
7075
7575
  importedDefinition: options.importedDefinition,
7076
- wrappers: wrappersAround(
7077
- pattern,
7078
- pack,
7079
- classification,
7080
- functionNode,
7081
- module2,
7082
- options
7083
- )
7576
+ wrappers
7084
7577
  },
7085
7578
  options
7086
7579
  )
@@ -7099,11 +7592,43 @@ function wrappersAround(pattern, pack, classification, definitionNode, module2,
7099
7592
  definitionNode
7100
7593
  });
7101
7594
  }
7595
+ function declaredStatusesOf(pattern, pack, classification, definitionNode, module2, options) {
7596
+ const keyword = pattern.responsesKeyword;
7597
+ if (keyword === void 0) {
7598
+ return [];
7599
+ }
7600
+ const above = options.wrappers?.callsAbove({
7601
+ pack,
7602
+ pattern,
7603
+ file: options.absoluteFile ?? options.filePath,
7604
+ module: module2,
7605
+ classification,
7606
+ definitionNode
7607
+ }) ?? [];
7608
+ const dictionaries = [
7609
+ classification.keywordArgs[keyword]?.node,
7610
+ ...above.map(
7611
+ (call) => readCallArguments(field(call, "arguments")).keywordArgs[keyword]?.node
7612
+ )
7613
+ ].filter((node) => node?.type === "dictionary");
7614
+ const statuses = dictionaries.flatMap(
7615
+ (dictionary) => children(dictionary).flatMap((pair) => {
7616
+ const key = pair?.type === "pair" ? field(pair, "key") : null;
7617
+ const status = key === null ? null : statusNumberOf(key, {
7618
+ module: moduleOf(key),
7619
+ facts: options.facts,
7620
+ constants: pattern.statusCodeConstants ?? {}
7621
+ });
7622
+ return status === null ? [] : [status];
7623
+ })
7624
+ );
7625
+ return [...new Set(statuses)].sort((a, b) => a - b);
7626
+ }
7102
7627
  function readResponseShape(declared, readAnnotation) {
7103
7628
  if (declared.kind === "written") {
7104
7629
  return { reading: declared, passedOver: [] };
7105
7630
  }
7106
- return (0, import_extractor7.firstWrittenReading)([declared, readAnnotation()]);
7631
+ return (0, import_extractor9.firstWrittenReading)([declared, readAnnotation()]);
7107
7632
  }
7108
7633
  function routeUnitOrAbstention(unitOptions, options) {
7109
7634
  try {
@@ -7131,7 +7656,7 @@ function unbuiltRouteUnit(unitOptions, error) {
7131
7656
  exportName: unitOptions.exportPath[0] ?? unitOptions.name,
7132
7657
  exportPath: unitOptions.exportPath
7133
7658
  },
7134
- boundaryBinding: (0, import_behavioral_ir5.restBinding)({
7659
+ boundaryBinding: (0, import_behavioral_ir6.restBinding)({
7135
7660
  transport: unitOptions.pack.protocol,
7136
7661
  method: nullIfEmpty(unitOptions.method),
7137
7662
  path: null,
@@ -7143,7 +7668,7 @@ function unbuiltRouteUnit(unitOptions, error) {
7143
7668
  dependencyCalls: [],
7144
7669
  declaredContract: null,
7145
7670
  readings: [
7146
- (0, import_extractor7.unreadableReading)(
7671
+ (0, import_extractor9.unreadableReading)(
7147
7672
  `This route could not be read into a unit (${message}), so the binding names no path and nothing pairs with it`,
7148
7673
  range
7149
7674
  )
@@ -7169,7 +7694,7 @@ function buildRouteUnit(options) {
7169
7694
  definitionsCtx,
7170
7695
  storage: storageLookup
7171
7696
  } = options;
7172
- const template = (0, import_extractor7.valueToReadFurtherFrom)(routePath);
7697
+ const template = (0, import_extractor9.valueToReadFurtherFrom)(routePath);
7173
7698
  const ctx = definitionsCtx ?? createAnnotationContext(
7174
7699
  module2.scopeFor,
7175
7700
  options.importedDefinition ?? null
@@ -7182,6 +7707,8 @@ function buildRouteUnit(options) {
7182
7707
  pathParamNames: template?.paramNames ?? null,
7183
7708
  requestBodyFromAnnotatedClass,
7184
7709
  injectedCallees,
7710
+ injectedTypes: new Set(pattern.injectedParameterTypes ?? []),
7711
+ module: module2,
7185
7712
  sources: pattern.parameterSources ?? {},
7186
7713
  aliasKeyword: pattern.parameterAliasKeyword
7187
7714
  },
@@ -7238,6 +7765,22 @@ function buildRouteUnit(options) {
7238
7765
  };
7239
7766
  branches.push(withBodyEffects(bare, extra));
7240
7767
  }
7768
+ if (branches.length > 0) {
7769
+ branches.push(
7770
+ ...validationFailureBranches(
7771
+ pattern,
7772
+ readsTheRequest(
7773
+ parameters,
7774
+ template,
7775
+ options.dependencyParameters ?? {
7776
+ names: /* @__PURE__ */ new Set(),
7777
+ declaresSource: false
7778
+ }
7779
+ ),
7780
+ rangeOf(definitionNode)
7781
+ )
7782
+ );
7783
+ }
7241
7784
  const bodyNode = field(definitionNode, "body");
7242
7785
  return {
7243
7786
  identity: {
@@ -7250,7 +7793,7 @@ function buildRouteUnit(options) {
7250
7793
  exportName: exportPath[0] ?? name,
7251
7794
  exportPath
7252
7795
  },
7253
- boundaryBinding: (0, import_behavioral_ir5.restBinding)({
7796
+ boundaryBinding: (0, import_behavioral_ir6.restBinding)({
7254
7797
  transport: pack.protocol,
7255
7798
  method,
7256
7799
  path: template?.path ?? null,
@@ -7269,15 +7812,57 @@ function buildRouteUnit(options) {
7269
7812
  ...collectedDefinitions(ctx) !== null ? { definitions: collectedDefinitions(ctx) } : {},
7270
7813
  ...options.mount !== void 0 ? { mount: options.mount } : {},
7271
7814
  ...options.wrappers !== void 0 && options.wrappers.length > 0 ? { wrappers: options.wrappers } : {},
7272
- ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling }
7815
+ ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling },
7816
+ ...options.declaredStatuses !== void 0 && options.declaredStatuses.length > 0 ? { declaredStatuses: options.declaredStatuses } : {}
7273
7817
  };
7274
7818
  }
7819
+ function readsTheRequest(parameters, template, dependencies) {
7820
+ if (dependencies.declaresSource || parameters.some((parameter) => parameter.role !== null)) {
7821
+ return true;
7822
+ }
7823
+ return [...template?.paramNames ?? []].some(
7824
+ (name) => dependencies.names.has(name)
7825
+ );
7826
+ }
7827
+ function validationFailureBranches(pattern, readsRequest, range) {
7828
+ const status = pattern.validationFailureStatus;
7829
+ if (status === void 0 || !readsRequest) {
7830
+ return [];
7831
+ }
7832
+ return [
7833
+ {
7834
+ conditions: [
7835
+ {
7836
+ sourceText: "request input fails validation",
7837
+ structured: null,
7838
+ polarity: "positive",
7839
+ source: "earlyThrow"
7840
+ }
7841
+ ],
7842
+ terminal: {
7843
+ kind: "response",
7844
+ statusCode: { type: "literal", value: status },
7845
+ body: null,
7846
+ exceptionType: null,
7847
+ message: null,
7848
+ component: null,
7849
+ renderTree: null,
7850
+ delegateTarget: null,
7851
+ emitEvent: null,
7852
+ location: range
7853
+ },
7854
+ effects: [],
7855
+ location: range,
7856
+ isDefault: false
7857
+ }
7858
+ ];
7859
+ }
7275
7860
  function unreadRoleReadings(parameters, range) {
7276
7861
  if (!parameters.some((parameter) => parameter.role === null)) {
7277
7862
  return [];
7278
7863
  }
7279
7864
  return [
7280
- (0, import_extractor7.unreadableReading)(
7865
+ (0, import_extractor9.unreadableReading)(
7281
7866
  "Nothing read this route's path, so its parameters name no role and a path parameter here does not read as one",
7282
7867
  range
7283
7868
  )
@@ -7338,10 +7923,7 @@ function readParameters(definitionNode, reading, skipReceiverParam) {
7338
7923
  }
7339
7924
  const out = [];
7340
7925
  let position = 0;
7341
- for (const param of parametersNode.namedChildren) {
7342
- if (param === null) {
7343
- continue;
7344
- }
7926
+ for (const param of children(parametersNode)) {
7345
7927
  if (skipReceiverParam && position === 0 && isReceiverParam(param)) {
7346
7928
  position += 1;
7347
7929
  continue;
@@ -7365,7 +7947,7 @@ function readParameter(param, reading, position) {
7365
7947
  const { name, typeNode } = info;
7366
7948
  const shape = typeNode !== null ? annotationToShape(typeNode, reading.scope, reading.ctx) : null;
7367
7949
  const typeText = typeNode !== null ? typeNode.text : null;
7368
- if (isInjectedParameter(param, reading)) {
7950
+ if (isInjectedParameter(param, reading) || isInjectedType(typeNode, reading)) {
7369
7951
  return { name, position, role: null, typeText };
7370
7952
  }
7371
7953
  const declared = declaringCall(
@@ -7445,6 +8027,14 @@ function calleeName2(call) {
7445
8027
  function isInjectedParameter(param, reading) {
7446
8028
  return declaringCall(param, reading.injectedCallees, reading) !== null;
7447
8029
  }
8030
+ function isInjectedType(typeNode, reading) {
8031
+ if (typeNode === null || reading.injectedTypes.size === 0) {
8032
+ return false;
8033
+ }
8034
+ const written = typeNode.type === "type" ? typeNode.namedChild(0) ?? null : typeNode;
8035
+ const origin = written === null ? null : originOf(written, reading.module);
8036
+ return origin !== null && reading.injectedTypes.has(`${origin.module}.${origin.name}`);
8037
+ }
7448
8038
  function declaringCall(param, callees, reading) {
7449
8039
  if (callees.size === 0) {
7450
8040
  return null;
@@ -7668,15 +8258,15 @@ function parseWith(language, source) {
7668
8258
  // src/project.ts
7669
8259
  var import_node_fs4 = __toESM(require("fs"), 1);
7670
8260
  var import_node_path5 = __toESM(require("path"), 1);
7671
- var import_behavioral_ir9 = require("@suss/behavioral-ir");
8261
+ var import_behavioral_ir10 = require("@suss/behavioral-ir");
7672
8262
  var import_datalog2 = require("@suss/datalog");
7673
- var import_extractor16 = require("@suss/extractor");
8263
+ var import_extractor18 = require("@suss/extractor");
7674
8264
  var import_resolution18 = require("@suss/resolution");
7675
8265
 
7676
8266
  // src/diagnostics.ts
7677
- var import_extractor8 = require("@suss/extractor");
8267
+ var import_extractor10 = require("@suss/extractor");
7678
8268
  function buildPythonExtractionReport(args) {
7679
- return (0, import_extractor8.buildUngatedExtractionReport)({
8269
+ return (0, import_extractor10.buildUngatedExtractionReport)({
7680
8270
  packs: args.packs.map((pack) => ({
7681
8271
  name: pack.name,
7682
8272
  version: pack.version ?? null,
@@ -7689,14 +8279,14 @@ function buildPythonExtractionReport(args) {
7689
8279
  }
7690
8280
 
7691
8281
  // src/exits.ts
7692
- var import_extractor9 = require("@suss/extractor");
8282
+ var import_extractor11 = require("@suss/extractor");
7693
8283
  var import_resolution9 = require("@suss/resolution");
7694
- var import_values20 = require("@suss/values");
8284
+ var import_values21 = require("@suss/values");
7695
8285
  var DEFERRED_BODY_TYPES3 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
7696
8286
  function exitSites(root, module2) {
7697
8287
  const found = [];
7698
8288
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
7699
- (0, import_extractor9.walkDescendants)(root, startScope, {
8289
+ (0, import_extractor11.walkDescendants)(root, startScope, children, {
7700
8290
  at: (node, scope) => {
7701
8291
  const site = exitSiteAt(node, scope);
7702
8292
  if (site !== null) {
@@ -7705,7 +8295,7 @@ function exitSites(root, module2) {
7705
8295
  },
7706
8296
  into: (node, scope) => {
7707
8297
  if (DEFERRED_BODY_TYPES3.has(node.type)) {
7708
- return import_extractor9.SKIP_CHILDREN;
8298
+ return import_extractor11.SKIP_CHILDREN;
7709
8299
  }
7710
8300
  return module2.scopeFor.get(node.id) ?? scope;
7711
8301
  }
@@ -7713,7 +8303,7 @@ function exitSites(root, module2) {
7713
8303
  return found;
7714
8304
  }
7715
8305
  function exitSiteAt(statement, scope) {
7716
- const expression = statement.namedChildren[0] ?? null;
8306
+ const expression = statement.namedChild(0) ?? null;
7717
8307
  if (expression === null) {
7718
8308
  return null;
7719
8309
  }
@@ -7727,12 +8317,12 @@ function exitSiteAt(statement, scope) {
7727
8317
  return endsTheProcess(callee, scope) ? { statement, code, raised: false } : null;
7728
8318
  }
7729
8319
  if (statement.type === "raise_statement") {
7730
- return isBuiltin(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
8320
+ return isBuiltin2(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
7731
8321
  }
7732
8322
  return null;
7733
8323
  }
7734
8324
  function endsTheProcess(callee, scope) {
7735
- 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");
7736
8326
  }
7737
8327
  function firstArgument(call) {
7738
8328
  return callArguments(call).find(
@@ -7760,7 +8350,7 @@ function exitCodeOf(code, facts) {
7760
8350
  if (stringLiteralValue(code) !== null) {
7761
8351
  return { type: "literal", value: 1 };
7762
8352
  }
7763
- const value = (0, import_values20.constantOf)(evaluatedValue(code, facts));
8353
+ const value = (0, import_values21.constantOf)(evaluatedValue(code, facts));
7764
8354
  return typeof value === "number" ? { type: "literal", value } : { type: "dynamic", sourceText: code.text };
7765
8355
  }
7766
8356
  function exitingBranches(definition, module2, effects, facts) {
@@ -7794,7 +8384,7 @@ function markExitCodeFunctions(summaries, exits, facts, workspaceRoot) {
7794
8384
  (site) => site.code === null ? [] : [nodeId(file, site.code)]
7795
8385
  )
7796
8386
  );
7797
- (0, import_extractor9.markReturnsAsExitCode)(
8387
+ (0, import_extractor11.markReturnsAsExitCode)(
7798
8388
  summaries,
7799
8389
  (0, import_resolution9.exitCodeFunctions)(facts, sinks),
7800
8390
  workspaceRoot
@@ -7879,25 +8469,25 @@ function definedNodeOf(binding) {
7879
8469
  }
7880
8470
 
7881
8471
  // src/moduleSurface.ts
7882
- var import_behavioral_ir7 = require("@suss/behavioral-ir");
7883
- var import_extractor12 = require("@suss/extractor");
8472
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
8473
+ var import_extractor14 = require("@suss/extractor");
7884
8474
  var import_resolution13 = require("@suss/resolution");
7885
8475
 
7886
8476
  // src/reach/closure.ts
7887
- var import_behavioral_ir6 = require("@suss/behavioral-ir");
8477
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
7888
8478
  var import_datalog = require("@suss/datalog");
7889
- var import_extractor11 = require("@suss/extractor");
8479
+ var import_extractor13 = require("@suss/extractor");
7890
8480
  var import_resolution12 = require("@suss/resolution");
7891
8481
 
7892
8482
  // src/flags.ts
7893
- var import_extractor10 = require("@suss/extractor");
8483
+ var import_extractor12 = require("@suss/extractor");
7894
8484
  var PARSE_METHODS = /* @__PURE__ */ new Set(["parse_args", "parse_known_args"]);
7895
8485
  var DEFERRED_BODY_TYPES4 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
7896
8486
  function argparseFlagReads(definition, module2, parameterNames2) {
7897
8487
  const parsers = /* @__PURE__ */ new Map();
7898
8488
  const handedTo = /* @__PURE__ */ new Map();
7899
8489
  const startScope = module2.scopeFor.get(definition.id) ?? module2.moduleScope;
7900
- (0, import_extractor10.walkDescendants)(definition, startScope, {
8490
+ (0, import_extractor12.walkDescendants)(definition, startScope, children, {
7901
8491
  at: (node, scope) => {
7902
8492
  noteParser(node, scope, parsers);
7903
8493
  noteArgument(node, parsers);
@@ -7905,7 +8495,7 @@ function argparseFlagReads(definition, module2, parameterNames2) {
7905
8495
  },
7906
8496
  into: (node, scope) => {
7907
8497
  if (DEFERRED_BODY_TYPES4.has(node.type)) {
7908
- return import_extractor10.SKIP_CHILDREN;
8498
+ return import_extractor12.SKIP_CHILDREN;
7909
8499
  }
7910
8500
  return module2.scopeFor.get(node.id) ?? scope;
7911
8501
  }
@@ -7999,11 +8589,77 @@ function resolveCallee2(call, site, ctx, read) {
7999
8589
  return spelling;
8000
8590
  }
8001
8591
  const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution11.calleeOutcomeOf)(ctx.facts, spelling.key);
8592
+ if (selfCallInMixin(call, outcome, site, ctx)) {
8593
+ return { kind: "stopped", reason: "selfCallInMixin" };
8594
+ }
8002
8595
  return asCallee(outcome, site.owner, ctx);
8003
8596
  }
8597
+ function selfCallInMixin(call, outcome, site, ctx) {
8598
+ if (outcome.kind !== "callerSupplied" || !outcome.key.startsWith(`${site.owner}#`)) {
8599
+ return false;
8600
+ }
8601
+ const callee = readThrough(field(call, "function"));
8602
+ const receiver = callee?.type === "attribute" ? field(callee, "object") : null;
8603
+ const name = callee === null ? null : field(callee, "attribute")?.text;
8604
+ if (receiver?.type !== "identifier" || name === void 0 || name === null) {
8605
+ return false;
8606
+ }
8607
+ if (outcome.key !== `${site.owner}#${receiver.text}`) {
8608
+ return false;
8609
+ }
8610
+ return ctx.facts.lookup("instanceOf", 0, outcome.key).some((row) => {
8611
+ const classKey = String(row[1]);
8612
+ return ctx.facts.has("plainClass", [classKey]) && !instancesHaveAttribute(ctx.facts, classKey, name);
8613
+ });
8614
+ }
8615
+ var FALLBACK_ATTRIBUTE = "__getattr__";
8616
+ function instancesHaveAttribute(facts, classKey, name) {
8617
+ return facts.lookup("holdsProperty", 0, classKey).some((member) => {
8618
+ if (String(member[1]) === FALLBACK_ATTRIBUTE) {
8619
+ return true;
8620
+ }
8621
+ return facts.lookup("storesProperty", 0, String(member[2])).some(
8622
+ (store) => String(store[1]) === name && store[3] === import_resolution11.RECEIVER_STORE_NAME
8623
+ );
8624
+ });
8625
+ }
8004
8626
  function namedOutcomes(keys, ctx) {
8005
8627
  return (0, import_resolution11.calleeOutcomes)(ctx.facts, [...new Set(keys)]);
8006
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
+ }
8007
8663
  function functionNamed(nameKey2, ctx, read) {
8008
8664
  if (assignedElsewhere(ctx.facts, nameKey2)) {
8009
8665
  return null;
@@ -8222,6 +8878,7 @@ function reachedFunctions(seeds, options) {
8222
8878
  const targetsByKey = /* @__PURE__ */ new Map();
8223
8879
  const stopsByKey = /* @__PURE__ */ new Map();
8224
8880
  const argTargetsByKey = /* @__PURE__ */ new Map();
8881
+ const possibleTargetsByKey = /* @__PURE__ */ new Map();
8225
8882
  const parameterCallsByKey = /* @__PURE__ */ new Map();
8226
8883
  const callsByKey = /* @__PURE__ */ new Map();
8227
8884
  const passedPositions = /* @__PURE__ */ new Set();
@@ -8284,6 +8941,7 @@ function reachedFunctions(seeds, options) {
8284
8941
  }
8285
8942
  targetsByKey.set(key, scan.targets);
8286
8943
  argTargetsByKey.set(key, scan.argTargets);
8944
+ possibleTargetsByKey.set(key, scan.possibleTargets);
8287
8945
  if (scan.parameterCalls.length > 0) {
8288
8946
  parameterCallsByKey.set(key, scan.parameterCalls);
8289
8947
  }
@@ -8324,17 +8982,18 @@ function reachedFunctions(seeds, options) {
8324
8982
  continue;
8325
8983
  }
8326
8984
  const build = () => {
8327
- const summary2 = (0, import_extractor11.assembleSummary)(libraryUnit(target, options), {
8985
+ const summary2 = (0, import_extractor13.assembleSummary)(libraryUnit(target, options), {
8328
8986
  gapHandling: options.gapHandling
8329
8987
  });
8330
8988
  if (options.gapHandling !== "silent") {
8331
8989
  summary2.gaps.push(
8332
- ...(stopsByKey.get(key) ?? []).map(import_behavioral_ir6.unfollowedCallGap)
8990
+ ...(stopsByKey.get(key) ?? []).map(import_behavioral_ir7.unfollowedCallGap)
8333
8991
  );
8334
8992
  }
8335
- (0, import_behavioral_ir6.placeCalls)(summary2, targetsByKey.get(key));
8336
- (0, import_behavioral_ir6.placeArgTargets)(summary2, argTargetsByKey.get(key));
8337
- (0, import_behavioral_ir6.placeCalleeParameters)(summary2, parameterCallsByKey.get(key));
8993
+ (0, import_behavioral_ir7.placeCalls)(summary2, targetsByKey.get(key));
8994
+ (0, import_behavioral_ir7.placeArgTargets)(summary2, argTargetsByKey.get(key));
8995
+ (0, import_behavioral_ir7.placePossibleCalls)(summary2, possibleTargetsByKey.get(key));
8996
+ (0, import_behavioral_ir7.placeCalleeParameters)(summary2, parameterCallsByKey.get(key));
8338
8997
  return summary2;
8339
8998
  };
8340
8999
  const summary = ledger === void 0 ? build() : ledger.charging(chargeOf(charges, key), () => {
@@ -8350,13 +9009,14 @@ function reachedFunctions(seeds, options) {
8350
9009
  summaries.push(summary);
8351
9010
  }
8352
9011
  if (options.gapHandling !== "silent") {
8353
- (0, import_behavioral_ir6.recordParameterGaps)(parameterCallsByKey, summariesByKey, passedPositions);
9012
+ (0, import_behavioral_ir7.recordParameterGaps)(parameterCallsByKey, summariesByKey, passedPositions);
8354
9013
  }
8355
9014
  return {
8356
9015
  summaries,
8357
9016
  targetsByKey,
8358
9017
  stopsByKey,
8359
9018
  argTargetsByKey,
9019
+ possibleTargetsByKey,
8360
9020
  parameterCallsByKey,
8361
9021
  callsByKey,
8362
9022
  passedPositions,
@@ -8380,24 +9040,42 @@ function chargeOf(charges, key) {
8380
9040
  }
8381
9041
  return charge;
8382
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"]);
8383
9056
  function batchedReads(round, ctx) {
8384
9057
  const bodies = round.flatMap(
8385
9058
  (entry) => "source" in entry ? [{ key: entry.key, source: entry.source, ...bodyOf(entry.source) }] : []
8386
9059
  );
8387
- const spellings = calleeSpellings(
8388
- bodies.flatMap((body) => body.written),
8389
- ctx
8390
- );
9060
+ const written = bodies.flatMap((body) => body.written);
9061
+ const spellings = calleeSpellings(written, ctx);
8391
9062
  const passed = namedOutcomes(
8392
9063
  bodies.flatMap(
8393
- ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
9064
+ ({ source, written: written2 }) => written2.flatMap(({ call }) => passedNameKeys(source.file, call))
8394
9065
  ),
8395
9066
  ctx
8396
9067
  );
9068
+ const dispatched = dispatchedAmong(written, spellings, ctx);
8397
9069
  return new Map(
8398
9070
  bodies.map((body) => [
8399
9071
  body.key,
8400
- { body: body.body, written: body.written, spellings, passed }
9072
+ {
9073
+ body: body.body,
9074
+ written: body.written,
9075
+ spellings,
9076
+ passed,
9077
+ dispatched
9078
+ }
8401
9079
  ])
8402
9080
  );
8403
9081
  }
@@ -8428,13 +9106,15 @@ function scanOnce(source, key, ctx, options, read, charges) {
8428
9106
  }
8429
9107
  function ownReads(source, ctx) {
8430
9108
  const body = bodyOf(source);
9109
+ const spellings = calleeSpellings(body.written, ctx);
8431
9110
  return {
8432
9111
  ...body,
8433
- spellings: calleeSpellings(body.written, ctx),
9112
+ spellings,
8434
9113
  passed: namedOutcomes(
8435
9114
  body.written.flatMap(({ call }) => passedNameKeys(source.file, call)),
8436
9115
  ctx
8437
- )
9116
+ ),
9117
+ dispatched: dispatchedAmong(body.written, spellings, ctx)
8438
9118
  };
8439
9119
  }
8440
9120
  function storedScan(scan) {
@@ -8450,6 +9130,10 @@ function storedScan(scan) {
8450
9130
  callee,
8451
9131
  [...byPosition]
8452
9132
  ]),
9133
+ possibleTargets: [...scan.possibleTargets].map(([callee, places]) => [
9134
+ callee,
9135
+ [...places]
9136
+ ]),
8453
9137
  parameterCalls: [...scan.parameterCalls],
8454
9138
  passedPositions: [...scan.passedPositions]
8455
9139
  };
@@ -8465,6 +9149,7 @@ function scanFromStored(stored, functionFor) {
8465
9149
  new Map(byPosition)
8466
9150
  ])
8467
9151
  ),
9152
+ possibleTargets: new Map(stored.possibleTargets),
8468
9153
  parameterCalls: stored.parameterCalls,
8469
9154
  passedPositions: new Set(stored.passedPositions)
8470
9155
  };
@@ -8493,6 +9178,7 @@ var EMPTY_SCAN = {
8493
9178
  stops: [],
8494
9179
  targets: /* @__PURE__ */ new Map(),
8495
9180
  argTargets: /* @__PURE__ */ new Map(),
9181
+ possibleTargets: /* @__PURE__ */ new Map(),
8496
9182
  parameterCalls: [],
8497
9183
  passedPositions: /* @__PURE__ */ new Set()
8498
9184
  };
@@ -8550,15 +9236,15 @@ function passedNameKeys(file, call) {
8550
9236
  if (args === null) {
8551
9237
  return [];
8552
9238
  }
8553
- return args.namedChildren.flatMap((arg) => {
9239
+ return children(args).flatMap((arg) => {
8554
9240
  const key = passedNameKeyOf(file, arg);
8555
9241
  return key === null ? [] : [key];
8556
9242
  });
8557
9243
  }
8558
9244
  function scanBody(source, ctx, recognized, read, implied) {
8559
9245
  const followed = [];
8560
- const stops = [];
8561
- const placements = new import_behavioral_ir6.TargetPlacements();
9246
+ const stops = new import_behavioral_ir7.StopList();
9247
+ const placements = new import_behavioral_ir7.TargetPlacements();
8562
9248
  const parameterCalls = [];
8563
9249
  const passedPositions = /* @__PURE__ */ new Set();
8564
9250
  const seen = /* @__PURE__ */ new Set();
@@ -8569,12 +9255,25 @@ function scanBody(source, ctx, recognized, read, implied) {
8569
9255
  return EMPTY_SCAN;
8570
9256
  }
8571
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
+ };
8572
9265
  const recordPassedArgs = (call, callee, calleeKey2) => {
8573
9266
  const args = field(call, "arguments");
8574
9267
  if (args === null) {
8575
9268
  return;
8576
9269
  }
8577
- args.namedChildren.forEach((arg, position) => {
9270
+ children(args).forEach((arg, position) => {
9271
+ if (arg.type === "lambda") {
9272
+ if (calleeKey2 !== null) {
9273
+ passedPositions.add(`${calleeKey2}#${position}`);
9274
+ }
9275
+ return;
9276
+ }
8578
9277
  const nameKey2 = passedNameKeyOf(file, arg);
8579
9278
  if (nameKey2 === null) {
8580
9279
  return;
@@ -8583,11 +9282,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8583
9282
  if (resolved === null) {
8584
9283
  return;
8585
9284
  }
8586
- const key = keyOf2(resolved);
8587
- if (!seen.has(key)) {
8588
- seen.add(key);
8589
- followed.push(resolved);
8590
- }
9285
+ follow(resolved);
8591
9286
  placements.placeArg(callee, position, {
8592
9287
  file: resolved.file.displayPath,
8593
9288
  span: spanOf(resolved.node)
@@ -8597,9 +9292,30 @@ function scanBody(source, ctx, recognized, read, implied) {
8597
9292
  }
8598
9293
  });
8599
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
+ };
8600
9309
  const record = (call, site) => {
8601
9310
  const callee = calleeText(call);
8602
- 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 };
8603
9319
  placeCallee(placements, callee, outcome, call, file);
8604
9320
  recordPassedArgs(
8605
9321
  call,
@@ -8607,10 +9323,12 @@ function scanBody(source, ctx, recognized, read, implied) {
8607
9323
  outcome.kind === "followed" ? keyOf2(outcome.target) : null
8608
9324
  );
8609
9325
  if (outcome.kind === "stopped") {
8610
- const stopKey = `${outcome.reason}:${callee}`;
8611
- if (!seen.has(stopKey) && !recognized.has(call.id) && (0, import_behavioral_ir6.worthRecording)(outcome.reason)) {
8612
- seen.add(stopKey);
8613
- stops.push({ callee, reason: outcome.reason });
9326
+ if (!recognized.has(call.id) && (0, import_behavioral_ir7.worthRecording)(outcome.reason)) {
9327
+ stops.add({
9328
+ callee,
9329
+ reason: outcome.reason,
9330
+ ...resultUsedAsValue(call) ? { usedAsValue: true } : {}
9331
+ });
8614
9332
  }
8615
9333
  if (outcome.reason === "callerSupplied" && !parameterCallsSeen.has(callee)) {
8616
9334
  const parameterIndex = ownParameters.indexOf(callee);
@@ -8621,11 +9339,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8621
9339
  }
8622
9340
  return;
8623
9341
  }
8624
- const key = keyOf2(outcome.target);
8625
- if (!seen.has(key)) {
8626
- seen.add(key);
8627
- followed.push(outcome.target);
8628
- }
9342
+ follow(outcome.target);
8629
9343
  };
8630
9344
  for (const { call, site } of written) {
8631
9345
  record(call, site);
@@ -8635,30 +9349,27 @@ function scanBody(source, ctx, recognized, read, implied) {
8635
9349
  file: target.file.displayPath,
8636
9350
  span: spanOf(target.node)
8637
9351
  });
8638
- const key = keyOf2(target);
8639
- if (!seen.has(key)) {
8640
- seen.add(key);
8641
- followed.push(target);
8642
- }
9352
+ follow(target);
8643
9353
  }
8644
9354
  return {
8645
9355
  followed,
8646
- stops,
9356
+ stops: stops.stops,
8647
9357
  targets: placements.targets,
8648
9358
  argTargets: placements.argTargets,
9359
+ possibleTargets: placements.possibleTargets,
8649
9360
  parameterCalls,
8650
9361
  passedPositions
8651
9362
  };
8652
9363
  }
8653
9364
  function callsWritten(file, body, outer, owner, limit) {
8654
9365
  const found = [];
8655
- (0, import_extractor11.walkDescendants)(body, outer, {
9366
+ (0, import_extractor13.walkDescendants)(body, outer, children, {
8656
9367
  at: (child, scope) => {
8657
9368
  if (child.type === "call") {
8658
9369
  found.push({ call: child, site: { file, scope, owner } });
8659
9370
  }
8660
9371
  },
8661
- into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : import_extractor11.SKIP_CHILDREN
9372
+ into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : import_extractor13.SKIP_CHILDREN
8662
9373
  });
8663
9374
  return found;
8664
9375
  }
@@ -8678,14 +9389,14 @@ function identifiersUnder(node, found = []) {
8678
9389
  found.push(node.text);
8679
9390
  return found;
8680
9391
  }
8681
- for (const child of node.namedChildren) {
9392
+ for (const child of children(node)) {
8682
9393
  if (child !== null && child.type !== "attribute" && child.type !== "subscript") {
8683
9394
  identifiersUnder(child, found);
8684
9395
  }
8685
9396
  }
8686
9397
  return found;
8687
9398
  }
8688
- function libraryUnit(target, options, binding = (0, import_behavioral_ir6.functionCallBinding)({
9399
+ function libraryUnit(target, options, binding = (0, import_behavioral_ir7.functionCallBinding)({
8689
9400
  transport: "in-process",
8690
9401
  recognition: "reachable"
8691
9402
  })) {
@@ -8779,10 +9490,7 @@ function positionalParameters(node, isMethod2) {
8779
9490
  }
8780
9491
  const out = [];
8781
9492
  let position = 0;
8782
- for (const param of parameters.namedChildren) {
8783
- if (param === null) {
8784
- continue;
8785
- }
9493
+ for (const param of children(parameters)) {
8786
9494
  const name = parameterName2(param);
8787
9495
  if (name === null) {
8788
9496
  continue;
@@ -8802,10 +9510,7 @@ function callParameterNames(node, isMethod2) {
8802
9510
  return [];
8803
9511
  }
8804
9512
  const names = [];
8805
- for (const param of parameters.namedChildren) {
8806
- if (param === null) {
8807
- continue;
8808
- }
9513
+ for (const param of children(parameters)) {
8809
9514
  const name = parameterName2(param);
8810
9515
  if (name === null) {
8811
9516
  continue;
@@ -8830,10 +9535,10 @@ function parameterName2(param) {
8830
9535
 
8831
9536
  // src/moduleSurface.ts
8832
9537
  function settlePythonModules(modules) {
8833
- return (0, import_extractor12.settleModules)(modules, ["__init__.py"]);
9538
+ return (0, import_extractor14.settleModules)(modules, ["__init__.py"]);
8834
9539
  }
8835
9540
  function moduleExportUnits(file, options) {
8836
- const module2 = (0, import_extractor12.moduleOfFile)(options.modules, file.file);
9541
+ const module2 = (0, import_extractor14.moduleOfFile)(options.modules, file.file);
8837
9542
  if (module2 === void 0) {
8838
9543
  return [];
8839
9544
  }
@@ -8855,8 +9560,8 @@ function moduleExportUnits(file, options) {
8855
9560
  exportPath: [...one.exportPath]
8856
9561
  },
8857
9562
  options,
8858
- (0, import_behavioral_ir7.moduleExportBinding)({
8859
- recognition: import_extractor12.MODULE_SURFACE_RECOGNITION,
9563
+ (0, import_behavioral_ir8.moduleExportBinding)({
9564
+ recognition: import_extractor14.MODULE_SURFACE_RECOGNITION,
8860
9565
  module: module2.name,
8861
9566
  exportName
8862
9567
  })
@@ -8902,7 +9607,7 @@ function exportsOf(file, node, exportName) {
8902
9607
  if (node.type !== "class_definition" || body === null) {
8903
9608
  return [];
8904
9609
  }
8905
- return body.namedChildren.flatMap((child) => {
9610
+ return children(body).flatMap((child) => {
8906
9611
  if (child === null) {
8907
9612
  return [];
8908
9613
  }
@@ -8975,6 +9680,10 @@ var PackPositions = class {
8975
9680
  const [packAt, patternAt] = id.split("/").map(Number);
8976
9681
  return this.packs[packAt ?? -1]?.discovery[patternAt ?? -1];
8977
9682
  }
9683
+ packOf(id) {
9684
+ const [packAt] = id.split("/").map(Number);
9685
+ return this.packs[packAt ?? -1];
9686
+ }
8978
9687
  formAt(at) {
8979
9688
  const pack = this.packs[at[0]];
8980
9689
  const pattern = pack?.discovery[at[1]];
@@ -9025,6 +9734,11 @@ function watchRouterIndex(index, db, positions, fileOfModule) {
9025
9734
  found
9026
9735
  );
9027
9736
  return found;
9737
+ },
9738
+ // The wrapper index notes what it reads through the mounts, in terms
9739
+ // it can look up again.
9740
+ mountsAbove(pattern, constructionKey) {
9741
+ return index.mountsAbove(pattern, constructionKey);
9028
9742
  }
9029
9743
  };
9030
9744
  }
@@ -9129,9 +9843,33 @@ function buildRouterIndex(files, packs, resolverOptions) {
9129
9843
  };
9130
9844
  }
9131
9845
  return NOT_ROUTER;
9846
+ },
9847
+ mountsAbove(pattern, constructionKey) {
9848
+ const index = byPattern.get(pattern);
9849
+ return index === void 0 ? null : hopsAbove(index, constructionKey);
9132
9850
  }
9133
9851
  };
9134
9852
  }
9853
+ function hopsAbove(index, key) {
9854
+ const hops = [];
9855
+ const seen = /* @__PURE__ */ new Set();
9856
+ let current = key;
9857
+ while (current !== null) {
9858
+ const construction = index.byValueKey.get(current);
9859
+ if (construction === void 0 || seen.has(current)) {
9860
+ return null;
9861
+ }
9862
+ seen.add(current);
9863
+ const states = index.mounts.get(construction) ?? [];
9864
+ const only = states.length === 1 ? states[0] : void 0;
9865
+ if (only?.kind !== "mounted" || only.written === void 0) {
9866
+ return null;
9867
+ }
9868
+ hops.push({ ...only.written, parentKey: only.parentValueKey });
9869
+ current = only.parentValueKey;
9870
+ }
9871
+ return hops;
9872
+ }
9135
9873
  function composedPrefixOf(index, construction, routerPath) {
9136
9874
  const ownPrefix = composedOwnPrefix(construction.prefix, index.composition);
9137
9875
  if (ownPrefix.kind === "abstain") {
@@ -9414,8 +10152,8 @@ function constructionStatement(stmt, module2, importModule) {
9414
10152
  if (stmt.type !== "expression_statement") {
9415
10153
  return null;
9416
10154
  }
9417
- const assignment2 = stmt.namedChildren.find(
9418
- (child) => child !== null && child.type === "assignment"
10155
+ const assignment2 = children(stmt).find(
10156
+ (child) => child.type === "assignment"
9419
10157
  );
9420
10158
  if (assignment2 === void 0) {
9421
10159
  return null;
@@ -9489,7 +10227,7 @@ function collectReturnedConstructions(bound, importModule, composition, index) {
9489
10227
  if (inner.type !== "return_statement") {
9490
10228
  continue;
9491
10229
  }
9492
- const returned = inner.namedChildren[0];
10230
+ const returned = inner.namedChild(0);
9493
10231
  if (returned?.type !== "call") {
9494
10232
  continue;
9495
10233
  }
@@ -9669,7 +10407,7 @@ function sequenceElementNames(node) {
9669
10407
  return null;
9670
10408
  }
9671
10409
  const names = [];
9672
- for (const child of node.namedChildren) {
10410
+ for (const child of children(node)) {
9673
10411
  if (child?.type !== "identifier") {
9674
10412
  return null;
9675
10413
  }
@@ -9882,8 +10620,9 @@ function recordMountStatement(stmt, position, scan) {
9882
10620
  scan.index.facts
9883
10621
  );
9884
10622
  const mounted = args[0] ?? (scan.composition.routerKeyword === void 0 ? void 0 : keywordArgs[scan.composition.routerKeyword]);
10623
+ const recorded = state.kind === "mounted" ? { ...state, written: { call: mountCall.call, file: scan.bound.file } } : state;
9885
10624
  for (const target of mountedConstructions(mounted, position, scan)) {
9886
- recordMount(scan.index, target, state);
10625
+ recordMount(scan.index, target, recorded);
9887
10626
  }
9888
10627
  }
9889
10628
  function mountStateOf(includerConstructorName, includer, keywordArgs, objectPrefix, composition, site, facts) {
@@ -10324,10 +11063,10 @@ function poetryPackages(pyproject) {
10324
11063
 
10325
11064
  // src/testCases.ts
10326
11065
  var import_node_path4 = __toESM(require("path"), 1);
10327
- var import_behavioral_ir8 = require("@suss/behavioral-ir");
10328
- var import_extractor13 = require("@suss/extractor");
11066
+ var import_behavioral_ir9 = require("@suss/behavioral-ir");
11067
+ var import_extractor15 = require("@suss/extractor");
10329
11068
  var import_resolution16 = require("@suss/resolution");
10330
- var import_values26 = require("@suss/values");
11069
+ var import_values27 = require("@suss/values");
10331
11070
  function pythonTestIndex(packs, context) {
10332
11071
  const patterns = packs.flatMap((pack) => pack.tests ?? []);
10333
11072
  return patterns.length === 0 ? null : new PythonTestIndex(patterns, context);
@@ -10344,7 +11083,7 @@ var PythonTestIndex = class {
10344
11083
  implied = /* @__PURE__ */ new Map();
10345
11084
  /** The tests in one file, when the file is collected and listed. */
10346
11085
  unitsIn(file, options) {
10347
- return this.testsIn(file).filter((test) => (0, import_extractor13.isListedTestFile)(file.file, test.pattern.files)).map((test) => this.testUnit(file, test, options));
11086
+ return this.testsIn(file).filter((test) => (0, import_extractor15.isListedTestFile)(file.file, test.pattern.files)).map((test) => this.testUnit(file, test, options));
10348
11087
  }
10349
11088
  /** What pytest calls before this function runs, when it is a test or a fixture. */
10350
11089
  impliedCalls(source) {
@@ -10394,7 +11133,7 @@ var PythonTestIndex = class {
10394
11133
  )
10395
11134
  ).flatMap(
10396
11135
  (marker) => positionalArguments(marker).flatMap((arg) => {
10397
- const name = (0, import_values26.literalOf)(evaluatedValue(arg, this.context.facts));
11136
+ const name = (0, import_values27.literalOf)(evaluatedValue(arg, this.context.facts));
10398
11137
  return name === null ? [] : [name];
10399
11138
  })
10400
11139
  );
@@ -10418,7 +11157,7 @@ var PythonTestIndex = class {
10418
11157
  ...raw,
10419
11158
  identity: {
10420
11159
  ...raw.identity,
10421
- name: (0, import_behavioral_ir8.testUnitName)(test.titles),
11160
+ name: (0, import_behavioral_ir9.testUnitName)(test.titles),
10422
11161
  nameKind: "label",
10423
11162
  kind: "test",
10424
11163
  exportName: null,
@@ -10430,7 +11169,7 @@ var PythonTestIndex = class {
10430
11169
  }
10431
11170
  patternFor(file) {
10432
11171
  return this.patterns.find(
10433
- (pattern) => (0, import_extractor13.matchesTestFileName)(file.file, pattern.filePatterns)
11172
+ (pattern) => (0, import_extractor15.matchesTestFileName)(file.file, pattern.filePatterns)
10434
11173
  ) ?? null;
10435
11174
  }
10436
11175
  testsIn(file) {
@@ -10808,11 +11547,11 @@ var PythonTestIndex = class {
10808
11547
  return null;
10809
11548
  }
10810
11549
  const written = oneLine(call.text);
10811
- const target = (0, import_values26.literalOf)(evaluatedValue(first, this.context.facts));
11550
+ const target = (0, import_values27.literalOf)(evaluatedValue(first, this.context.facts));
10812
11551
  if (target !== null) {
10813
11552
  return { ...this.dottedTarget(target), written };
10814
11553
  }
10815
- const member = second === void 0 ? null : (0, import_values26.literalOf)(evaluatedValue(second, this.context.facts));
11554
+ const member = second === void 0 ? null : (0, import_values27.literalOf)(evaluatedValue(second, this.context.facts));
10816
11555
  if (member === null) {
10817
11556
  return null;
10818
11557
  }
@@ -10950,8 +11689,8 @@ function fixtureDeclaration(definition, file, pattern, facts) {
10950
11689
  const renamed = keywordArgument(decorator, pattern.fixtureNameKeyword);
10951
11690
  const autouse = keywordArgument(decorator, pattern.autouseKeyword);
10952
11691
  return {
10953
- name: (renamed === null ? null : (0, import_values26.literalOf)(evaluatedValue(renamed, facts))) ?? defName,
10954
- autouse: autouse !== null && (0, import_values26.constantOf)(evaluatedValue(autouse, facts)) === true
11692
+ name: (renamed === null ? null : (0, import_values27.literalOf)(evaluatedValue(renamed, facts))) ?? defName,
11693
+ autouse: autouse !== null && (0, import_values27.constantOf)(evaluatedValue(autouse, facts)) === true
10955
11694
  };
10956
11695
  }
10957
11696
  function keywordArgument(call, name) {
@@ -10983,18 +11722,20 @@ function oneLine(text2) {
10983
11722
  }
10984
11723
 
10985
11724
  // src/version.ts
10986
- var import_extractor14 = require("@suss/extractor");
11725
+ var import_extractor16 = require("@suss/extractor");
10987
11726
  var ADAPTER_VERSION = "0.1.0";
10988
- var adapterStamp = (0, import_extractor14.createAdapterStamp)({
11727
+ var adapterStamp = (0, import_extractor16.createAdapterStamp)({
10989
11728
  moduleUrl: importMetaUrl,
10990
11729
  version: ADAPTER_VERSION
10991
11730
  });
10992
11731
 
10993
11732
  // src/wrappers.ts
10994
- var import_extractor15 = require("@suss/extractor");
11733
+ var import_extractor17 = require("@suss/extractor");
10995
11734
  var import_resolution17 = require("@suss/resolution");
10996
11735
  var EVERY_ROUTE = "wrapEvery ";
10997
11736
  var OWN_ROUTES = "wrapOwn ";
11737
+ var MOUNTED = "wrapMounted ";
11738
+ var MOUNTED_SEPARATOR = "\0";
10998
11739
  function describeRegistered(found) {
10999
11740
  return (found ?? []).map(
11000
11741
  ({ reference }) => `${reference.file}:${reference.name}:${reference.onThrow === true}`
@@ -11032,8 +11773,15 @@ var PythonWrapperIndex = class {
11032
11773
  /** By construction key: what a router or a blueprint registered. */
11033
11774
  ownRoutes = /* @__PURE__ */ new Map();
11034
11775
  unitsByKey = /* @__PURE__ */ new Map();
11776
+ /** By pack name: the app constructions its routes run under. */
11777
+ appCalls = /* @__PURE__ */ new Map();
11778
+ /** By wrapper reference: the parameters a dependency function declares, and its file. */
11779
+ dependencyParameterTable = /* @__PURE__ */ new Map();
11035
11780
  unitsByFile = /* @__PURE__ */ new Map();
11036
- /** 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
+ */
11037
11785
  onRegistered = null;
11038
11786
  /**
11039
11787
  * The same lookup `wrappersFor` noted under `id`, made again over this
@@ -11050,6 +11798,12 @@ var PythonWrapperIndex = class {
11050
11798
  this.ownRoutes.get(id.slice(OWN_ROUTES.length))
11051
11799
  );
11052
11800
  }
11801
+ if (id.startsWith(MOUNTED)) {
11802
+ const [packName, position, key] = id.slice(MOUNTED.length).split(MOUNTED_SEPARATOR);
11803
+ const pack = this.options.packs.find((one) => one.name === packName);
11804
+ const pattern = pack?.discovery[Number(position)];
11805
+ return pack === void 0 || pattern === void 0 || key === void 0 ? describeRegistered([]) : describeRegistered(this.registeredAbove(pack, pattern, key));
11806
+ }
11053
11807
  return null;
11054
11808
  }
11055
11809
  noted(kind, key, found) {
@@ -11091,14 +11845,65 @@ var PythonWrapperIndex = class {
11091
11845
  inFile.push(unit);
11092
11846
  this.unitsByFile.set(target.file.file, inFile);
11093
11847
  }
11094
- return {
11095
- reference: {
11096
- file: target.file.displayPath,
11097
- name: target.name,
11098
- ...isThrowForm(declared.form) ? { onThrow: true } : {}
11099
- },
11100
- form: declared.form
11848
+ const reference = {
11849
+ file: target.file.displayPath,
11850
+ name: target.name,
11851
+ ...isThrowForm(declared.form) ? { onThrow: true } : {}
11101
11852
  };
11853
+ if (declared.form.type === "dependency") {
11854
+ this.dependencyParameterTable.set(referenceKey(reference), {
11855
+ file: target.file.file,
11856
+ names: parameterNamesOf(target.node),
11857
+ declaresSource: declaresSourcedParameter(
11858
+ target.node,
11859
+ Object.keys(declared.pattern.parameterSources ?? {})
11860
+ )
11861
+ });
11862
+ }
11863
+ return { reference, form: declared.form };
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
+ }
11884
+ /**
11885
+ * The parameters the dependencies among these wrappers declare, and
11886
+ * whether one is declared through a parameter source such as `Query()`.
11887
+ * The library fills those from the request and validates them, the same
11888
+ * as parameters the route declares.
11889
+ */
11890
+ dependencyParameters(references) {
11891
+ const names = /* @__PURE__ */ new Set();
11892
+ let declaresSource = false;
11893
+ for (const reference of references) {
11894
+ const found = this.dependencyParameterTable.get(referenceKey(reference));
11895
+ if (found === void 0) {
11896
+ continue;
11897
+ }
11898
+ if (this.options.facts !== void 0) {
11899
+ (0, import_resolution17.noteKeyRead)(this.options.facts, found.file);
11900
+ }
11901
+ for (const name of found.names) {
11902
+ names.add(name);
11903
+ }
11904
+ declaresSource ||= found.declaresSource;
11905
+ }
11906
+ return { names, declaresSource };
11102
11907
  }
11103
11908
  wrappersFor(query) {
11104
11909
  const file = this.filesByPath.get(query.file);
@@ -11107,16 +11912,22 @@ var PythonWrapperIndex = class {
11107
11912
  pattern: query.pattern,
11108
11913
  form
11109
11914
  }));
11110
- if (file === void 0 || forms.length === 0) {
11915
+ if (file === void 0) {
11111
11916
  return [];
11112
11917
  }
11918
+ const decorators = this.decoratorLayers(query, file);
11919
+ if (forms.length === 0) {
11920
+ return decorators;
11921
+ }
11922
+ const own = this.ownRoutesOf(query);
11113
11923
  const found = [
11114
11924
  ...this.noted(
11115
11925
  EVERY_ROUTE,
11116
11926
  query.pack.name,
11117
11927
  this.everyRoute.get(query.pack.name)
11118
11928
  ),
11119
- ...this.ownRoutesOf(query)
11929
+ ...own === null ? [] : this.mountedAbove(query, own.key),
11930
+ ...own?.registered ?? []
11120
11931
  ];
11121
11932
  for (const declared of forms) {
11122
11933
  if (declared.form.type !== "dependency") {
@@ -11145,13 +11956,128 @@ var PythonWrapperIndex = class {
11145
11956
  seen.add(key);
11146
11957
  ordered.push(wrapper.reference);
11147
11958
  }
11148
- 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
+ );
12021
+ }
12022
+ /**
12023
+ * What the mounts above the route's router register on the way down:
12024
+ * the dependencies an `include_router` call lists, and those of each
12025
+ * router it is mounted onto, outermost first, the order they run in.
12026
+ */
12027
+ mountedAbove(query, key) {
12028
+ const position = query.pack.discovery.indexOf(query.pattern);
12029
+ return [
12030
+ ...this.noted(
12031
+ MOUNTED,
12032
+ [query.pack.name, position, key].join(MOUNTED_SEPARATOR),
12033
+ this.registeredAbove(query.pack, query.pattern, key)
12034
+ )
12035
+ ];
12036
+ }
12037
+ registeredAbove(pack, pattern, key) {
12038
+ const hops = this.options.routers?.mountsAbove(pattern, key) ?? null;
12039
+ if (hops === null) {
12040
+ return [];
12041
+ }
12042
+ const forms = (pattern.wrappers ?? []).flatMap(
12043
+ (form) => form.type === "dependency" ? [{ pack, pattern, form }] : []
12044
+ );
12045
+ const found = [];
12046
+ for (const hop of [...hops].reverse()) {
12047
+ found.push(
12048
+ ...hop.parentKey === null ? [] : this.ownRoutes.get(hop.parentKey) ?? []
12049
+ );
12050
+ const { keywordArgs } = readCallArguments(field(hop.call, "arguments"));
12051
+ for (const declared of forms) {
12052
+ const listed = keywordArgs[declared.form.keyword];
12053
+ const names = listed === void 0 ? [] : dependencyNamesIn(listed.node, declared.form);
12054
+ for (const name of names) {
12055
+ const target = this.functionCalled(hop.file, name);
12056
+ if (target !== null) {
12057
+ found.push(this.registered(target, declared));
12058
+ }
12059
+ }
12060
+ }
12061
+ }
12062
+ return found;
11149
12063
  }
11150
12064
  ownRoutesOf(query) {
12065
+ const match = this.ownRouterOf(query);
12066
+ return match === null ? null : {
12067
+ key: match.key,
12068
+ registered: this.noted(
12069
+ OWN_ROUTES,
12070
+ match.key,
12071
+ this.ownRoutes.get(match.key)
12072
+ )
12073
+ };
12074
+ }
12075
+ /** The construction call of the router a route is decorated on, when a registrar reads it. */
12076
+ ownRouterOf(query) {
11151
12077
  const site = this.objectSiteOf(query);
11152
12078
  const bound = site === null ? void 0 : this.filesByPath.get(site.file);
11153
12079
  if (site === null || bound === void 0) {
11154
- return [];
12080
+ return null;
11155
12081
  }
11156
12082
  if (this.options.facts !== void 0) {
11157
12083
  (0, import_resolution17.noteKeyRead)(this.options.facts, site.file);
@@ -11165,12 +12091,46 @@ var PythonWrapperIndex = class {
11165
12091
  { pack: query.pack, pattern: query.pattern, form }
11166
12092
  );
11167
12093
  if (match !== null && match.registrar.covers === "ownRoutes") {
11168
- return [
11169
- ...this.noted(OWN_ROUTES, match.key, this.ownRoutes.get(match.key))
11170
- ];
12094
+ return match;
11171
12095
  }
11172
12096
  }
11173
- return [];
12097
+ return null;
12098
+ }
12099
+ /** Records the app a pack's routes run under, for the keywords written on its construction. */
12100
+ recordApp(pack, call, file) {
12101
+ const calls = this.appCalls.get(pack) ?? [];
12102
+ if (!calls.some((one) => one.call === call)) {
12103
+ calls.push({ call, file });
12104
+ }
12105
+ this.appCalls.set(pack, calls);
12106
+ }
12107
+ /**
12108
+ * Every call that registers something for a route on the way to it:
12109
+ * the app's construction, the mount calls and the routers they mount
12110
+ * onto, and the route's own router. A caller reads a keyword off each.
12111
+ */
12112
+ callsAbove(query) {
12113
+ const own = this.ownRouterOf(query);
12114
+ const hops = own === null ? [] : this.options.routers?.mountsAbove(query.pattern, own.key) ?? [];
12115
+ const parents = hops.flatMap((hop) => {
12116
+ const node = hop.parentKey === null ? null : nodeOfResolutionKey(hop.parentKey, this.options.facts);
12117
+ return node === null ? [] : [node];
12118
+ });
12119
+ const files = [
12120
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.file),
12121
+ ...hops.map((hop) => hop.file)
12122
+ ];
12123
+ if (this.options.facts !== void 0) {
12124
+ for (const file of files) {
12125
+ (0, import_resolution17.noteKeyRead)(this.options.facts, file);
12126
+ }
12127
+ }
12128
+ return [
12129
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.call),
12130
+ ...hops.map((hop) => hop.call),
12131
+ ...parents,
12132
+ ...own === null ? [] : [own.call]
12133
+ ];
11174
12134
  }
11175
12135
  /**
11176
12136
  * Where the decorator's object was built: the route's own file, the file
@@ -11215,6 +12175,38 @@ var PythonWrapperIndex = class {
11215
12175
  return this.unitsByFile.get(file) ?? [];
11216
12176
  }
11217
12177
  };
12178
+ function referenceKey(reference) {
12179
+ return `${reference.file}::${reference.name}`;
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
+ }
12196
+ function parameterNamesOf(definition) {
12197
+ return children(field(definition, "parameters")).flatMap((parameter) => {
12198
+ const named = parameterNameAndType(parameter);
12199
+ return named === null ? [] : [named.name];
12200
+ });
12201
+ }
12202
+ function declaresSourcedParameter(definition, sources) {
12203
+ return children(field(definition, "parameters")).some((parameter) => {
12204
+ const value = field(parameter, "value");
12205
+ const callee = value?.type === "call" ? field(value, "function") : null;
12206
+ const name = callee?.type === "attribute" ? field(callee, "attribute")?.text : callee?.text;
12207
+ return name !== void 0 && sources.includes(name);
12208
+ });
12209
+ }
11218
12210
  function isThrowForm(form) {
11219
12211
  return form.type === "decoratedWrapper" && form.throwParam !== void 0;
11220
12212
  }
@@ -11261,8 +12253,8 @@ function registerFrom(file, declared, index) {
11261
12253
  }
11262
12254
  function registerConstructorDependencies(file, declared, form, index) {
11263
12255
  const scope = file.module.moduleScope;
11264
- for (const stmt of file.root.namedChildren) {
11265
- const assignment2 = stmt?.type === "expression_statement" ? stmt.namedChildren.find((child) => child?.type === "assignment") : void 0;
12256
+ for (const stmt of children(file.root)) {
12257
+ const assignment2 = stmt?.type === "expression_statement" ? children(stmt).find((child) => child?.type === "assignment") : void 0;
11266
12258
  const left = assignment2 === void 0 || assignment2 === null ? null : field(assignment2, "left");
11267
12259
  if (left?.type !== "identifier") {
11268
12260
  continue;
@@ -11277,6 +12269,9 @@ function registerConstructorDependencies(file, declared, form, index) {
11277
12269
  if (match === null) {
11278
12270
  continue;
11279
12271
  }
12272
+ if (match.registrar.covers === "everyRoute") {
12273
+ index.recordApp(declared.pack.name, match.call, file.file);
12274
+ }
11280
12275
  const { keywordArgs } = readCallArguments(field(match.call, "arguments"));
11281
12276
  const listed = keywordArgs[form.keyword];
11282
12277
  if (listed === void 0) {
@@ -11294,7 +12289,7 @@ function coverageOf(match, pack) {
11294
12289
  return match.registrar.covers === "everyRoute" ? { kind: "everyRoute", pack: pack.name } : { kind: "ownRoutes", key: match.key };
11295
12290
  }
11296
12291
  function registerDecorated(file, declared, form, index) {
11297
- (0, import_extractor15.walkDescendants)(file.root, file.module.moduleScope, {
12292
+ (0, import_extractor17.walkDescendants)(file.root, file.module.moduleScope, children, {
11298
12293
  at: (node, scope) => {
11299
12294
  if (node.type !== "decorated_definition") {
11300
12295
  return;
@@ -11330,7 +12325,7 @@ function registerDecorated(file, declared, form, index) {
11330
12325
  });
11331
12326
  }
11332
12327
  function dependencyNamesIn(node, form) {
11333
- const calls = node.type === "list" ? node.namedChildren : [node];
12328
+ const calls = node.type === "list" ? children(node) : [node];
11334
12329
  const names = [];
11335
12330
  for (const call of calls) {
11336
12331
  const name = call === null ? null : dependencyNameOf(call, form);
@@ -11349,7 +12344,7 @@ function dependencyNameOf(node, form) {
11349
12344
  if (calleeName3 === null || !form.callees.includes(calleeName3)) {
11350
12345
  return null;
11351
12346
  }
11352
- const first = field(node, "arguments")?.namedChildren[0];
12347
+ const first = field(node, "arguments")?.namedChild(0);
11353
12348
  return first?.type === "identifier" ? first.text : null;
11354
12349
  }
11355
12350
  function decoratorDependencies(classification, form) {
@@ -11367,7 +12362,7 @@ function parameterDependencies(definitionNode, form) {
11367
12362
  if (direct !== null) {
11368
12363
  return direct;
11369
12364
  }
11370
- for (const child of node.namedChildren) {
12365
+ for (const child of children(node)) {
11371
12366
  const found = findIn(child, depth + 1);
11372
12367
  if (found !== null) {
11373
12368
  return found;
@@ -11375,10 +12370,7 @@ function parameterDependencies(definitionNode, form) {
11375
12370
  }
11376
12371
  return null;
11377
12372
  };
11378
- for (const param of field(definitionNode, "parameters")?.namedChildren ?? []) {
11379
- if (param === null) {
11380
- continue;
11381
- }
12373
+ for (const param of children(field(definitionNode, "parameters"))) {
11382
12374
  const inDefault = field(param, "value");
11383
12375
  const found = inDefault === null ? findIn(field(param, "type"), 0) : dependencyNameOf(inDefault, form);
11384
12376
  if (found !== null) {
@@ -11388,15 +12380,42 @@ function parameterDependencies(definitionNode, form) {
11388
12380
  return names;
11389
12381
  }
11390
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) {
11391
12409
  const { file, node, name, exportPath } = target;
11392
12410
  const body = field(node, "body");
11393
12411
  const range = rangeOf(node);
11394
12412
  const raised = raisedResponses(body, {
11395
- calls: declared.pattern.responseStatusCalls ?? [],
12413
+ calls: pattern.responseStatusCalls ?? [],
11396
12414
  module: file.module,
11397
- facts: options.facts
12415
+ facts: options.facts,
12416
+ constants: pattern.statusCodeConstants ?? {}
11398
12417
  });
11399
- const continuations = continuationStatements(node, declared.form);
12418
+ const continuations = reading.continuations;
11400
12419
  const terminals = bodyTerminals(body, raised, continuations);
11401
12420
  const effects = invocationEffects(node, options.facts);
11402
12421
  const extra = recognizedBodyEffects(
@@ -11405,7 +12424,7 @@ function wrapperUnit(target, declared, options) {
11405
12424
  options.storageFor(file),
11406
12425
  options.facts
11407
12426
  );
11408
- const delegate = (at) => ({
12427
+ const delegate = (at, to = null) => ({
11409
12428
  terminal: {
11410
12429
  kind: "delegate",
11411
12430
  statusCode: null,
@@ -11414,18 +12433,18 @@ function wrapperUnit(target, declared, options) {
11414
12433
  message: null,
11415
12434
  component: null,
11416
12435
  renderTree: null,
11417
- delegateTarget: null,
12436
+ delegateTarget: to,
11418
12437
  emitEvent: null,
11419
12438
  location: rangeOf(at)
11420
12439
  },
11421
12440
  location: rangeOf(at)
11422
12441
  });
11423
12442
  const respond = (statement) => returnBranch(statement, {
11424
- pattern: declared.pattern,
11425
- responseShape: { reading: import_extractor15.absentReading },
12443
+ pattern,
12444
+ responseShape: { reading: import_extractor17.absentReading },
11426
12445
  declaredStatus: {
11427
- reading: import_extractor15.absentReading,
11428
- ...declared.pattern.defaultStatusCode !== void 0 ? { libraryDefault: declared.pattern.defaultStatusCode } : {}
12446
+ reading: import_extractor17.absentReading,
12447
+ ...pattern.defaultStatusCode !== void 0 ? { libraryDefault: pattern.defaultStatusCode } : {}
11429
12448
  },
11430
12449
  module: file.module,
11431
12450
  facts: options.facts
@@ -11437,7 +12456,10 @@ function wrapperUnit(target, declared, options) {
11437
12456
  location: rangeOf(terminal.statement)
11438
12457
  };
11439
12458
  }
11440
- 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)) {
11441
12463
  return delegate(terminal.statement);
11442
12464
  }
11443
12465
  return respond(terminal.statement);
@@ -11454,7 +12476,7 @@ function wrapperUnit(target, declared, options) {
11454
12476
  return {
11455
12477
  identity: {
11456
12478
  name,
11457
- nameKind: "binding",
12479
+ nameKind: reading.nameKind,
11458
12480
  kind: "middleware",
11459
12481
  file: file.displayPath,
11460
12482
  range,
@@ -11478,7 +12500,7 @@ function returnHandsOn(statement, form) {
11478
12500
  return false;
11479
12501
  }
11480
12502
  if (form.returnedValueResponds === true) {
11481
- const returned = statement.namedChildren[0];
12503
+ const returned = statement.namedChild(0);
11482
12504
  return returned === void 0 || returned === null || returned.type === "none";
11483
12505
  }
11484
12506
  return true;
@@ -11487,32 +12509,52 @@ function continuationStatements(node, form) {
11487
12509
  if (form.type !== "decoratedWrapper" || form.continuationParam === void 0) {
11488
12510
  return [];
11489
12511
  }
11490
- const params = (field(node, "parameters")?.namedChildren ?? []).filter(
11491
- (param2) => param2 !== null
11492
- );
12512
+ const params = children(field(node, "parameters"));
11493
12513
  const param = params[form.continuationParam];
11494
12514
  const continuation = param === void 0 ? null : field(param, "name")?.text ?? (param.type === "identifier" ? param.text : null);
11495
- if (continuation === null) {
11496
- return [];
11497
- }
12515
+ return continuation === null ? [] : callsThrough(node, continuation);
12516
+ }
12517
+ function callsThrough(node, callee) {
11498
12518
  const body = field(node, "body");
11499
12519
  if (body === null) {
11500
12520
  return [];
11501
12521
  }
11502
12522
  const found = [];
11503
12523
  for (const call of bodyCalls(body)) {
11504
- const callee = field(call, "function");
11505
- 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;
11506
12526
  if (statement !== null) {
11507
12527
  found.push(statement);
11508
12528
  }
11509
12529
  }
11510
12530
  return found;
11511
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
+ }
11512
12554
 
11513
12555
  // src/project.ts
11514
12556
  function keptPythonParses() {
11515
- return new import_extractor16.KeptParses(({ root }) => root.tree.delete());
12557
+ return new import_extractor18.KeptParses(({ root }) => root.tree.delete());
11516
12558
  }
11517
12559
  async function parsePythonAhead(files, kept) {
11518
12560
  for (const file of files) {
@@ -11601,16 +12643,16 @@ function factsForFile(options) {
11601
12643
  }
11602
12644
  async function extractPythonProject(options) {
11603
12645
  const { roots, unreadManifests } = rootsOfRun(options);
11604
- const timer = options.onTiming !== void 0 ? (0, import_extractor16.createTimer)() : (0, import_extractor16.noopTimer)();
12646
+ const timer = options.onTiming !== void 0 ? (0, import_extractor18.createTimer)() : (0, import_extractor18.noopTimer)();
11605
12647
  const cacheDir = adapterStamp.declineWhenRunFromSource(
11606
- 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)
11607
12649
  );
11608
- const layer = (0, import_extractor16.createCacheLayer)(cacheDir);
12650
+ const layer = (0, import_extractor18.createCacheLayer)(cacheDir);
11609
12651
  const packsDigest = `${adapterStamp.packsDigest(
11610
12652
  options.packs.map(
11611
12653
  (pack) => pack.version !== void 0 ? { name: pack.name, version: pack.version } : { name: pack.name }
11612
12654
  )
11613
- )}|${(0, import_extractor16.extractionConfigStamp)({
12655
+ )}|${(0, import_extractor18.extractionConfigStamp)({
11614
12656
  gapHandling: options.gapHandling,
11615
12657
  workspaceRoot: options.workspaceRoot,
11616
12658
  projectRoot: options.projectRoot,
@@ -11620,17 +12662,20 @@ async function extractPythonProject(options) {
11620
12662
  })}`;
11621
12663
  const input = {
11622
12664
  files: cacheDir === null ? [] : options.files,
11623
- adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor16.runDigest)(packsDigest, options.packs, options.files)
12665
+ adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor18.runDigest)(packsDigest, options.packs, options.files)
11624
12666
  };
11625
12667
  const lookup = await timer.timeAsync(
11626
12668
  "cache.lookup",
11627
12669
  () => layer.lookup(input)
11628
12670
  );
12671
+ const composed = (stored) => (0, import_extractor18.composeWrappers)(stored, {
12672
+ gapHandling: options.gapHandling ?? "permissive"
12673
+ });
11629
12674
  if (lookup.kind === "hit") {
11630
12675
  options.onCacheDiagnostic?.(lookup.diagnostic);
11631
12676
  options.onTiming?.(timer.report());
11632
12677
  return {
11633
- summaries: lookup.summaries,
12678
+ summaries: composed(lookup.summaries),
11634
12679
  facts: new import_datalog2.Database(),
11635
12680
  roots,
11636
12681
  unreadManifests
@@ -11645,7 +12690,7 @@ async function extractPythonProject(options) {
11645
12690
  options.onCacheDiagnostic?.({ kind: "hit" });
11646
12691
  options.onTiming?.(timer.report());
11647
12692
  return {
11648
- summaries: plan.allSummaries(),
12693
+ summaries: composed(plan.allSummaries()),
11649
12694
  facts: new import_datalog2.Database(),
11650
12695
  roots,
11651
12696
  unreadManifests
@@ -11682,7 +12727,7 @@ async function runPython(options, roots, cache, previous) {
11682
12727
  const db = new import_datalog2.Database();
11683
12728
  const summaries = [];
11684
12729
  const gapHandling = options.gapHandling ?? "permissive";
11685
- const tallies = (0, import_extractor8.createPackTallies)(options.packs);
12730
+ const tallies = (0, import_extractor10.createPackTallies)(options.packs);
11686
12731
  const ledger = cacheDir === null ? null : new import_resolution18.DependencyLedger(options.files);
11687
12732
  const log = ledger === null ? null : new import_resolution18.FactLog(db, ledger);
11688
12733
  if (ledger !== null) {
@@ -11790,7 +12835,8 @@ async function runPython(options, roots, cache, previous) {
11790
12835
  roots,
11791
12836
  facts: needsValues ? db : void 0,
11792
12837
  definitions,
11793
- storageFor
12838
+ storageFor,
12839
+ routers: plainRouterIndex
11794
12840
  })
11795
12841
  );
11796
12842
  const importedDefinition2 = importedDefinitionLookup(db, bound);
@@ -11826,15 +12872,10 @@ async function runPython(options, roots, cache, previous) {
11826
12872
  });
11827
12873
  let registering = null;
11828
12874
  if (ledger !== null) {
11829
- wrapperIndex.onRegistered = (target, declared) => {
11830
- const form = positions.positionOf(declared);
11831
- if (registering !== null && form !== void 0) {
11832
- registering.push({
11833
- key: nodeId(target.file.file, target.node),
11834
- name: target.name,
11835
- exportPath: target.exportPath,
11836
- form
11837
- });
12875
+ wrapperIndex.onRegistered = (target, how) => {
12876
+ const stored = storedRegistration(target, how, positions);
12877
+ if (registering !== null && stored !== null) {
12878
+ registering.push(stored);
11838
12879
  }
11839
12880
  };
11840
12881
  }
@@ -11847,12 +12888,11 @@ async function runPython(options, roots, cache, previous) {
11847
12888
  if (record !== void 0) {
11848
12889
  discoveries.set(file, record);
11849
12890
  for (const registration of record.registrations) {
11850
- const declared = positions.formAt(registration.form);
11851
- const target = storedFunction(registration, definitions, filesByPath);
11852
- if (declared === void 0 || target === null) {
11853
- throw new ReplayFailed(registration.key);
11854
- }
11855
- wrapperIndex.registered(target, declared);
12891
+ registerAgain(registration, {
12892
+ positions,
12893
+ wrapperIndex,
12894
+ target: storedFunction(registration, definitions, filesByPath)
12895
+ });
11856
12896
  }
11857
12897
  continue;
11858
12898
  }
@@ -11937,11 +12977,11 @@ async function runPython(options, roots, cache, previous) {
11937
12977
  const assemble = (raw, boundFile) => {
11938
12978
  const summary = timer.time(
11939
12979
  "summarize",
11940
- () => (0, import_extractor16.assembleSummary)(raw, { gapHandling })
12980
+ () => (0, import_extractor18.assembleSummary)(raw, { gapHandling })
11941
12981
  );
11942
12982
  summaries.push(summary);
11943
12983
  assembledHere.add(summary);
11944
- (0, import_extractor8.tallyUnit)(tallies, raw.boundaryBinding?.recognition);
12984
+ (0, import_extractor10.tallyUnit)(tallies, raw.boundaryBinding?.recognition);
11945
12985
  const span = raw.identity.span;
11946
12986
  const key = span === void 0 ? null : `${boundFile.file}:${span.start}-${span.end}`;
11947
12987
  if (key !== null) {
@@ -11958,7 +12998,7 @@ async function runPython(options, roots, cache, previous) {
11958
12998
  const summary = structuredClone(unit.summary);
11959
12999
  summariesReused += 1;
11960
13000
  summaries.push(summary);
11961
- (0, import_extractor8.tallyUnit)(tallies, unit.recognition);
13001
+ (0, import_extractor10.tallyUnit)(tallies, unit.recognition);
11962
13002
  if (unit.seedKey !== void 0) {
11963
13003
  seedFrom(unit.seedKey, boundFile, summary);
11964
13004
  }
@@ -12016,12 +13056,13 @@ async function runPython(options, roots, cache, previous) {
12016
13056
  const placeWhatItReached = (summary, key) => {
12017
13057
  if (gapHandling !== "silent") {
12018
13058
  summary.gaps.push(
12019
- ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir9.unfollowedCallGap)
13059
+ ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir10.unfollowedCallGap)
12020
13060
  );
12021
13061
  }
12022
- (0, import_behavioral_ir9.placeCalls)(summary, reached.targetsByKey.get(key));
12023
- (0, import_behavioral_ir9.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
12024
- (0, import_behavioral_ir9.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
13062
+ (0, import_behavioral_ir10.placeCalls)(summary, reached.targetsByKey.get(key));
13063
+ (0, import_behavioral_ir10.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
13064
+ (0, import_behavioral_ir10.placePossibleCalls)(summary, reached.possibleTargetsByKey.get(key));
13065
+ (0, import_behavioral_ir10.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
12025
13066
  };
12026
13067
  for (const [key, owners] of summariesBySeed) {
12027
13068
  for (const summary of owners) {
@@ -12038,7 +13079,7 @@ async function runPython(options, roots, cache, previous) {
12038
13079
  }
12039
13080
  }
12040
13081
  if (gapHandling !== "silent") {
12041
- (0, import_behavioral_ir9.recordParameterGaps)(
13082
+ (0, import_behavioral_ir10.recordParameterGaps)(
12042
13083
  reached.parameterCallsByKey,
12043
13084
  summariesBySeed,
12044
13085
  reached.passedPositions
@@ -12062,8 +13103,8 @@ async function runPython(options, roots, cache, previous) {
12062
13103
  }
12063
13104
  const summary = timer.time(
12064
13105
  "summarize",
12065
- () => (0, import_extractor16.assembleSummary)(
12066
- (0, import_extractor16.moduleInitStructure)({
13106
+ () => (0, import_extractor18.assembleSummary)(
13107
+ (0, import_extractor18.moduleInitStructure)({
12067
13108
  name: import_node_path5.default.basename(displayPath),
12068
13109
  file: displayPath,
12069
13110
  range: rangeOf(boundFile.root),
@@ -12072,7 +13113,7 @@ async function runPython(options, roots, cache, previous) {
12072
13113
  ...charged(
12073
13114
  boundFile.file,
12074
13115
  () => moduleLoadInvocationEffects(boundFile.root, db)
12075
- ).map(import_extractor16.effectToIR)
13116
+ ).map(import_extractor18.effectToIR)
12076
13117
  ]
12077
13118
  }),
12078
13119
  { gapHandling }
@@ -12092,34 +13133,38 @@ async function runPython(options, roots, cache, previous) {
12092
13133
  throw new ReplayFailed("");
12093
13134
  }
12094
13135
  const resolvedImports = (0, import_resolution18.importedFilesByFile)(db, displayPathOf);
12095
- (0, import_extractor16.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);
12096
- (0, import_extractor16.stampModules)(summaries, declaredModules, options.workspaceRoot);
13136
+ (0, import_extractor18.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);
13137
+ (0, import_extractor18.stampModules)(summaries, declaredModules, options.workspaceRoot);
12097
13138
  const idRoot = options.projectRoot ?? options.workspaceRoot;
12098
13139
  for (const summary of summaries) {
12099
13140
  const absoluteFile = options.workspaceRoot === void 0 ? summary.location.file : import_node_path5.default.resolve(options.workspaceRoot, summary.location.file);
12100
- summary.identity.id = (0, import_behavioral_ir9.summaryIdFromParts)({
13141
+ summary.identity.id = (0, import_behavioral_ir10.summaryIdFromParts)({
12101
13142
  workspace: void 0,
12102
13143
  file: idRoot === void 0 ? absoluteFile : import_node_path5.default.relative(idRoot, absoluteFile),
12103
13144
  name: summary.identity.name,
12104
13145
  exportPath: summary.identity.exportPath
12105
13146
  });
12106
13147
  }
12107
- (0, import_behavioral_ir9.disambiguateSummaryIds)(summaries);
12108
- (0, import_behavioral_ir9.linkCallsToSummaries)(summaries);
13148
+ (0, import_behavioral_ir10.disambiguateSummaryIds)(summaries);
13149
+ (0, import_behavioral_ir10.linkCallsToSummaries)(summaries);
12109
13150
  const composed = timer.time("summarize", () => {
12110
13151
  markExitCodeFunctions(summaries, exits, db, options.workspaceRoot);
12111
- return (0, import_extractor16.composeWrappers)(summaries, { gapHandling });
13152
+ return (0, import_extractor18.composeWrappers)(summaries, { gapHandling });
12112
13153
  });
13154
+ const walkedFiles = new Map(
13155
+ bound.map(({ file, displayPath }) => [displayPath, file])
13156
+ );
12113
13157
  await timer.timeAsync("cache.write", async () => {
12114
- if (cacheDir === null || composed.length === 0) {
13158
+ if (cacheDir === null || summaries.length === 0) {
12115
13159
  return;
12116
13160
  }
12117
13161
  try {
12118
13162
  await cache.layer.write(
12119
13163
  cache.input,
12120
- composed,
13164
+ summaries,
12121
13165
  log === null || ledger === null || overBudget ? void 0 : pythonAttribution({
12122
- composed,
13166
+ summaries,
13167
+ walkedFileOf: (locationFile) => walkedFiles.get(locationFile),
12123
13168
  discoveries,
12124
13169
  charges,
12125
13170
  unitDrafts,
@@ -12174,6 +13219,39 @@ async function runPython(options, roots, cache, previous) {
12174
13219
  options.onTiming?.(timer.report());
12175
13220
  return { summaries: composed, facts: db };
12176
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
+ }
12177
13255
  function pythonAttribution(args) {
12178
13256
  const walkedByFile = (0, import_resolution18.walkRecordsByFile)({
12179
13257
  scans: args.reached.scans,
@@ -12202,7 +13280,10 @@ function pythonAttribution(args) {
12202
13280
  }
12203
13281
  };
12204
13282
  });
12205
- return { roots, owners: args.composed.map(() => []) };
13283
+ return {
13284
+ roots,
13285
+ owners: (0, import_extractor18.ownersWhereWritten)(args.summaries, args.walkedFileOf)
13286
+ };
12206
13287
  }
12207
13288
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
12208
13289
  "__pycache__",
@@ -12233,7 +13314,7 @@ function indexDefinitions(into, file, node) {
12233
13314
  if (isFunction(node)) {
12234
13315
  into.set(nodeId(file, node), node);
12235
13316
  }
12236
- for (const child of node.namedChildren) {
13317
+ for (const child of children(node)) {
12237
13318
  if (child !== null) {
12238
13319
  indexDefinitions(into, file, child);
12239
13320
  }
@@ -12499,7 +13580,7 @@ function importSitesIn(root) {
12499
13580
  found.push({ module: entry.module, name: entry.name, line });
12500
13581
  }
12501
13582
  }
12502
- for (const child of node.namedChildren) {
13583
+ for (const child of children(node)) {
12503
13584
  if (child !== null) {
12504
13585
  visit(child);
12505
13586
  }