@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.js CHANGED
@@ -82,10 +82,7 @@ function stringLiteralValue(node) {
82
82
  return null;
83
83
  }
84
84
  let content = "";
85
- for (const child of node.namedChildren) {
86
- if (child === null) {
87
- continue;
88
- }
85
+ for (const child of children(node)) {
89
86
  if (child.type === "string_content") {
90
87
  content += stringContentValue(child);
91
88
  continue;
@@ -97,7 +94,29 @@ function stringLiteralValue(node) {
97
94
  return content;
98
95
  }
99
96
  function children(node) {
100
- return node.namedChildren.filter((child) => child !== null);
97
+ if (node === null || node === void 0) {
98
+ return [];
99
+ }
100
+ const found = [];
101
+ const count = node.namedChildCount;
102
+ for (let index = 0; index < count; index++) {
103
+ const child = node.namedChild(index);
104
+ if (child !== null) {
105
+ found.push(child);
106
+ }
107
+ }
108
+ return found;
109
+ }
110
+ function allChildren(node) {
111
+ const found = [];
112
+ const count = node.childCount;
113
+ for (let index = 0; index < count; index++) {
114
+ const child = node.child(index);
115
+ if (child !== null) {
116
+ found.push(child);
117
+ }
118
+ }
119
+ return found;
101
120
  }
102
121
  function bodyStatements(body) {
103
122
  return children(body);
@@ -129,11 +148,59 @@ function stripDecorators(node) {
129
148
  if (definition === null) {
130
149
  throw new Error("decorated_definition with no definition field");
131
150
  }
132
- const decorators = node.namedChildren.filter(
133
- (child) => child !== null && child.type === "decorator"
151
+ const decorators = children(node).filter(
152
+ (child) => child.type === "decorator"
134
153
  );
135
154
  return { definition, decorators };
136
155
  }
156
+ var isChild = (fieldName) => (parent, child) => field(parent, fieldName)?.id === child.id;
157
+ var always = () => true;
158
+ var asCondition = isChild("condition");
159
+ var isConditionalTest = (parent, child) => parent.namedChild(1)?.id === child.id;
160
+ var VALUE_USED_UNDER = {
161
+ assignment: isChild("right"),
162
+ augmented_assignment: isChild("right"),
163
+ named_expression: isChild("value"),
164
+ attribute: isChild("object"),
165
+ subscript: always,
166
+ argument_list: always,
167
+ keyword_argument: isChild("value"),
168
+ pair: always,
169
+ list: always,
170
+ tuple: always,
171
+ set: always,
172
+ dictionary: always,
173
+ interpolation: always,
174
+ comparison_operator: always,
175
+ binary_operator: always,
176
+ unary_operator: always,
177
+ not_operator: always,
178
+ if_statement: asCondition,
179
+ elif_clause: asCondition,
180
+ while_statement: asCondition,
181
+ conditional_expression: isConditionalTest,
182
+ assert_statement: always,
183
+ for_statement: isChild("right"),
184
+ for_in_clause: isChild("right"),
185
+ with_item: always,
186
+ as_pattern: always
187
+ };
188
+ var PASSES_VALUE_ON = {
189
+ parenthesized_expression: always,
190
+ await: always,
191
+ boolean_operator: always,
192
+ conditional_expression: (parent, child) => !isConditionalTest(parent, child)
193
+ };
194
+ function resultUsedAsValue(node) {
195
+ let child = node;
196
+ let parent = node.parent;
197
+ while (parent !== null && PASSES_VALUE_ON[parent.type]?.(parent, child) === true) {
198
+ child = parent;
199
+ parent = parent.parent;
200
+ }
201
+ const used = parent === null ? void 0 : VALUE_USED_UNDER[parent.type];
202
+ return parent !== null && used !== void 0 && used(parent, child);
203
+ }
137
204
  var NodeSet = class extends IdSet {
138
205
  };
139
206
  var NodeMap = class extends IdMap {
@@ -195,7 +262,7 @@ function bindWithStatement(stmt, scope, ctx) {
195
262
  }
196
263
  }
197
264
  function asPatternsIn(stmt, found = []) {
198
- for (const child of stmt.namedChildren) {
265
+ for (const child of children(stmt)) {
199
266
  if (child === null || child.type === "block") {
200
267
  continue;
201
268
  }
@@ -224,12 +291,12 @@ function bindDeleteStatement(stmt, scope) {
224
291
  return;
225
292
  }
226
293
  if (node.type === "expression_list") {
227
- for (const child of node.namedChildren) {
294
+ for (const child of children(node)) {
228
295
  unbind(child);
229
296
  }
230
297
  }
231
298
  };
232
- for (const child of stmt.namedChildren) {
299
+ for (const child of children(stmt)) {
233
300
  unbind(child);
234
301
  }
235
302
  }
@@ -270,10 +337,7 @@ function bindImportStatement(stmt, scope) {
270
337
  function relativeImportParts(node) {
271
338
  let relativeLevel = 0;
272
339
  let module = "";
273
- for (const child of node.namedChildren) {
274
- if (child === null) {
275
- continue;
276
- }
340
+ for (const child of children(node)) {
277
341
  if (child.type === "import_prefix") {
278
342
  relativeLevel += child.text.length;
279
343
  continue;
@@ -290,8 +354,8 @@ function bindImportFromStatement(stmt, scope, ctx) {
290
354
  return;
291
355
  }
292
356
  const { module, relativeLevel } = moduleNode.type === "relative_import" ? relativeImportParts(moduleNode) : { module: moduleNode.text, relativeLevel: 0 };
293
- const hasWildcard = stmt.namedChildren.some(
294
- (child) => child !== null && child.type === "wildcard_import"
357
+ const hasWildcard = children(stmt).some(
358
+ (child) => child.type === "wildcard_import"
295
359
  );
296
360
  if (hasWildcard) {
297
361
  ctx.openImports.push(
@@ -349,10 +413,7 @@ function bindFunctionDefinition(stmt, scope, ctx) {
349
413
  }
350
414
  }
351
415
  function bindParameters(parametersNode, functionScope) {
352
- for (const param of parametersNode.namedChildren) {
353
- if (param === null) {
354
- continue;
355
- }
416
+ for (const param of children(parametersNode)) {
356
417
  const name = parameterName(param);
357
418
  if (name !== null) {
358
419
  bindName(functionScope, name, { kind: "parameter" });
@@ -369,9 +430,7 @@ function parameterName(param) {
369
430
  "list_splat_pattern",
370
431
  "dictionary_splat_pattern"
371
432
  )) {
372
- const inner = param.namedChildren.find(
373
- (child) => child !== null && child.type === "identifier"
374
- );
433
+ const inner = children(param).find((child) => child.type === "identifier");
375
434
  return inner?.text ?? null;
376
435
  }
377
436
  if (isType(param, "default_parameter", "typed_default_parameter")) {
@@ -398,8 +457,8 @@ function bindClassDefinition(stmt, scope, ctx) {
398
457
  }
399
458
  }
400
459
  function bindExpressionStatement(stmt, scope) {
401
- const assignment2 = stmt.namedChildren.find(
402
- (child) => child !== null && child.type === "assignment"
460
+ const assignment2 = children(stmt).find(
461
+ (child) => child.type === "assignment"
403
462
  );
404
463
  if (assignment2 === void 0 || assignment2 === null) {
405
464
  return;
@@ -416,15 +475,15 @@ function bindExpressionStatement(stmt, scope) {
416
475
  }
417
476
  }
418
477
  function bindGlobalStatement(stmt, scope) {
419
- for (const child of stmt.namedChildren) {
420
- if (child !== null && child.type === "identifier") {
478
+ for (const child of children(stmt)) {
479
+ if (child.type === "identifier") {
421
480
  scope.bindings.set(child.text, { kind: "global" });
422
481
  }
423
482
  }
424
483
  }
425
484
  function bindNonlocalStatement(stmt, scope) {
426
- for (const child of stmt.namedChildren) {
427
- if (child !== null && child.type === "identifier") {
485
+ for (const child of children(stmt)) {
486
+ if (child.type === "identifier") {
428
487
  scope.bindings.set(child.text, { kind: "nonlocal" });
429
488
  }
430
489
  }
@@ -573,18 +632,9 @@ function shapeFromName(name, scope, ctx) {
573
632
  if (DICT_NAMES.has(name)) {
574
633
  return { type: "dictionary", values: { type: "unknown" } };
575
634
  }
576
- const binding = resolveName(scope, name);
577
- if (binding?.kind === "classDef") {
578
- return recordShapeRef(name, binding.node, ctx);
579
- }
580
- if (binding?.kind === "import" || binding?.kind === "importFrom") {
581
- const imported = ctx.importedDefinition?.(scope, name) ?? null;
582
- if (imported?.node.type === "class_definition") {
583
- if (!ctx.scopeMaps.includes(imported.scopeFor)) {
584
- ctx.scopeMaps.push(imported.scopeFor);
585
- }
586
- return recordShapeRef(name, imported.node, ctx);
587
- }
635
+ const classNode = projectClassNamed(name, scope, ctx);
636
+ if (classNode !== null) {
637
+ return recordShapeRef(name, classNode, ctx);
588
638
  }
589
639
  const alias = aliasValueOf(name, scope, ctx);
590
640
  if (alias !== null) {
@@ -592,6 +642,23 @@ function shapeFromName(name, scope, ctx) {
592
642
  }
593
643
  return { type: "ref", name };
594
644
  }
645
+ function projectClassNamed(name, scope, ctx) {
646
+ const binding = resolveName(scope, name);
647
+ if (binding?.kind === "classDef") {
648
+ return binding.node;
649
+ }
650
+ if (binding?.kind !== "import" && binding?.kind !== "importFrom") {
651
+ return null;
652
+ }
653
+ const imported = ctx.importedDefinition?.(scope, name) ?? null;
654
+ if (imported?.node.type !== "class_definition") {
655
+ return null;
656
+ }
657
+ if (!ctx.scopeMaps.includes(imported.scopeFor)) {
658
+ ctx.scopeMaps.push(imported.scopeFor);
659
+ }
660
+ return imported.node;
661
+ }
595
662
  function shapeFromAliasValue(name, alias, ctx) {
596
663
  if (ctx.expanding.has(alias.node.id)) {
597
664
  return { type: "ref", name };
@@ -618,8 +685,9 @@ function shapeFromSubscript(node, scope, ctx) {
618
685
  );
619
686
  }
620
687
  function annotationTarget(annotation) {
621
- if (annotation.type === "type" && annotation.namedChildren[0]) {
622
- return annotationTarget(annotation.namedChildren[0]);
688
+ const inner = annotation.type === "type" ? annotation.namedChild(0) : null;
689
+ if (inner !== null) {
690
+ return annotationTarget(inner);
623
691
  }
624
692
  if (annotation.type === "binary_operator") {
625
693
  const named = [field(annotation, "left"), field(annotation, "right")].find(
@@ -628,7 +696,7 @@ function annotationTarget(annotation) {
628
696
  return named === void 0 || named === null ? null : annotationTarget(named);
629
697
  }
630
698
  if (annotation.type === "generic_type") {
631
- const outer = annotation.namedChildren[0];
699
+ const outer = annotation.namedChild(0);
632
700
  const first = genericTypeArgs(annotation)[0];
633
701
  if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
634
702
  return annotationTarget(first);
@@ -645,12 +713,10 @@ function typeNameOf(annotation) {
645
713
  return target.type === "identifier" ? target.text : stringLiteralValue(target);
646
714
  }
647
715
  function genericTypeArgs(node) {
648
- const typeParameter = node.namedChildren.find(
649
- (child) => child !== null && child.type === "type_parameter"
716
+ const typeParameter = children(node).find(
717
+ (child) => child.type === "type_parameter"
650
718
  );
651
- return typeParameter?.namedChildren.filter(
652
- (child) => child !== null && child.type === "type"
653
- ) ?? [];
719
+ return children(typeParameter).filter((child) => child.type === "type");
654
720
  }
655
721
  function shapeFromGenericType(node, scope, ctx) {
656
722
  const base = node.namedChild(0);
@@ -702,22 +768,44 @@ function recordShapeRef(name, classNode, ctx) {
702
768
  return ref;
703
769
  }
704
770
  ctx.definitions.set(key, null);
771
+ ctx.definitions.set(key, {
772
+ type: "record",
773
+ properties: classFields(classNode, ctx, /* @__PURE__ */ new Set())
774
+ });
775
+ return ref;
776
+ }
777
+ function classFields(classNode, ctx, visiting) {
705
778
  const bodyNode = field(classNode, "body");
706
779
  const classScope = scopeOfNode(ctx, classNode);
707
- ctx.definitions.set(
708
- key,
709
- bodyNode !== null && classScope !== void 0 ? recordShapeOf(bodyNode, classScope, ctx) : { type: "record", properties: {} }
780
+ if (bodyNode === null || classScope === void 0 || visiting.has(classNode.id)) {
781
+ return {};
782
+ }
783
+ visiting.add(classNode.id);
784
+ const inherited = baseClassNames(classNode).flatMap((name) => {
785
+ const base = projectClassNamed(name, classScope, ctx);
786
+ return base === null ? [] : [classFields(base, ctx, visiting)];
787
+ });
788
+ const own = recordShapeOf(bodyNode, classScope, ctx);
789
+ return Object.assign(
790
+ {},
791
+ ...inherited.reverse(),
792
+ own.type === "record" ? own.properties : {}
793
+ );
794
+ }
795
+ function baseClassNames(classNode) {
796
+ const superclasses = field(classNode, "superclasses");
797
+ return children(superclasses).flatMap(
798
+ (child) => child.type === "identifier" ? [child.text] : []
710
799
  );
711
- return ref;
712
800
  }
713
801
  function recordShapeOf(bodyNode, classScope, ctx) {
714
802
  const properties = {};
715
- for (const stmt of bodyNode.namedChildren) {
803
+ for (const stmt of children(bodyNode)) {
716
804
  if (stmt === null || stmt.type !== "expression_statement") {
717
805
  continue;
718
806
  }
719
- const assignment2 = stmt.namedChildren.find(
720
- (child) => child !== null && child.type === "assignment"
807
+ const assignment2 = children(stmt).find(
808
+ (child) => child.type === "assignment"
721
809
  );
722
810
  if (assignment2 === void 0 || assignment2 === null) {
723
811
  continue;
@@ -727,6 +815,9 @@ function recordShapeOf(bodyNode, classScope, ctx) {
727
815
  if (left === null || left.type !== "identifier" || typeNode === null) {
728
816
  continue;
729
817
  }
818
+ if (typeNameOf(typeNode) === "ClassVar") {
819
+ continue;
820
+ }
730
821
  properties[left.text] = annotationToShape(typeNode, classScope, ctx);
731
822
  }
732
823
  return { type: "record", properties };
@@ -743,8 +834,10 @@ import { nodeOfKey, noteKeyRead } from "@suss/resolution";
743
834
  import { Evaluator, force, literalOf as literalOf2 } from "@suss/values";
744
835
 
745
836
  // src/facts/resolve.ts
837
+ import { isTestFile } from "@suss/behavioral-ir";
746
838
  import {
747
839
  allocationSitesOf,
840
+ answersFromTestsLeftOut,
748
841
  askResolution,
749
842
  askResolutionUnder,
750
843
  askSources,
@@ -802,7 +895,13 @@ function constructionSites(db, classKey) {
802
895
  }
803
896
  function writtenValueOf(db, key) {
804
897
  resolveWritten(db, [key]);
805
- return sharedWrittenValueOf(db, key, (keys) => resolveWritten(db, keys));
898
+ return sharedWrittenValueOf(
899
+ db,
900
+ key,
901
+ (keys) => resolveWritten(db, keys),
902
+ void 0,
903
+ answersFromTestsLeftOut(key, isTestFile)
904
+ );
806
905
  }
807
906
  function writtenValuesOf(db, key) {
808
907
  resolveWritten(db, [key]);
@@ -886,10 +985,14 @@ function objectReturnedBy(db, callKey) {
886
985
 
887
986
  // src/facts/values.ts
888
987
  import {
988
+ addEntryRows,
989
+ addTemplateRows,
889
990
  NAMED_STORE_NAME,
890
991
  RECEIVER_STORE_NAME,
992
+ SPREAD_ENTRY,
891
993
  startsAtName,
892
994
  UNPLACED_STORE_NAME,
995
+ VALUE_ENTRY,
893
996
  valueLeftByWrites,
894
997
  writesRunInOrder
895
998
  } from "@suss/resolution";
@@ -900,6 +1003,8 @@ var CONVERTING_BUILTINS = /* @__PURE__ */ new Set([
900
1003
  "str",
901
1004
  "float"
902
1005
  ]);
1006
+ var ATTRIBUTE_GETTER = "getattr";
1007
+ var POSITION_COUNTER = "enumerate";
903
1008
 
904
1009
  // src/facts/values.ts
905
1010
  function nodeId(filePath, node) {
@@ -1071,8 +1176,7 @@ function emitConversion(emitter, call, callKey) {
1071
1176
  return;
1072
1177
  }
1073
1178
  const name = callee.text;
1074
- const builtin = nameKey(emitter.filePath, emitter.enclosing, name) === nameId(emitter.filePath, name) && !emitter.moduleNames().has(name);
1075
- if (builtin) {
1179
+ if (isBuiltin(emitter, name)) {
1076
1180
  add(emitter, "converts", callKey, valueKey(emitter, first.node), name);
1077
1181
  }
1078
1182
  }
@@ -1121,6 +1225,7 @@ function emitSequence(emitter, sequence) {
1121
1225
  const objectKey = nodeId(emitter.filePath, sequence);
1122
1226
  add(emitter, "objectValue", objectKey);
1123
1227
  let position = 0;
1228
+ const entries = [];
1124
1229
  for (const element of children(sequence)) {
1125
1230
  add(
1126
1231
  emitter,
@@ -1130,12 +1235,21 @@ function emitSequence(emitter, sequence) {
1130
1235
  valueKey(emitter, element)
1131
1236
  );
1132
1237
  position += 1;
1238
+ if (element.type !== "comment") {
1239
+ entries.push(writtenEntry(emitter, element));
1240
+ }
1133
1241
  }
1242
+ addEntryRows(rowsOf(emitter), objectKey, entries);
1134
1243
  }
1135
1244
  function emitDictionary(emitter, dictionary) {
1136
1245
  const objectKey = nodeId(emitter.filePath, dictionary);
1137
1246
  add(emitter, "objectValue", objectKey);
1247
+ const entries = [];
1138
1248
  for (const pair of children(dictionary)) {
1249
+ if (SPREAD_TYPES.has(pair.type)) {
1250
+ entries.push(writtenEntry(emitter, pair));
1251
+ continue;
1252
+ }
1139
1253
  if (pair.type !== "pair") {
1140
1254
  continue;
1141
1255
  }
@@ -1144,6 +1258,7 @@ function emitDictionary(emitter, dictionary) {
1144
1258
  if (key === null || value === null) {
1145
1259
  continue;
1146
1260
  }
1261
+ entries.push(writtenEntry(emitter, value));
1147
1262
  if (key.type !== "string") {
1148
1263
  add(emitter, "holdsUnderKey", objectKey, valueKey(emitter, value));
1149
1264
  continue;
@@ -1156,6 +1271,15 @@ function emitDictionary(emitter, dictionary) {
1156
1271
  valueKey(emitter, value)
1157
1272
  );
1158
1273
  }
1274
+ addEntryRows(rowsOf(emitter), objectKey, entries);
1275
+ }
1276
+ var SPREAD_TYPES = /* @__PURE__ */ new Set(["list_splat", "dictionary_splat"]);
1277
+ function writtenEntry(emitter, entry) {
1278
+ const unpacked = SPREAD_TYPES.has(entry.type) ? children(entry)[0] : void 0;
1279
+ return unpacked === void 0 ? { key: valueKey(emitter, entry), form: VALUE_ENTRY } : { key: valueKey(emitter, unpacked), form: SPREAD_ENTRY };
1280
+ }
1281
+ function rowsOf(emitter) {
1282
+ return (relation, row) => add(emitter, relation, ...row);
1159
1283
  }
1160
1284
  function emitAttribute(emitter, attribute) {
1161
1285
  const object = field(attribute, "object");
@@ -1220,13 +1344,50 @@ function emitKeyedRead(emitter, site, container, key) {
1220
1344
  if (WRITTEN_KEY_TYPES.has(key.type)) {
1221
1345
  return;
1222
1346
  }
1223
- add(
1224
- emitter,
1225
- "readsKeyed",
1226
- nodeId(emitter.filePath, site),
1227
- valueKey(emitter, container),
1228
- valueKey(emitter, key)
1347
+ const siteKey = nodeId(emitter.filePath, site);
1348
+ const containerKey = valueKey(emitter, container);
1349
+ add(emitter, "readsKeyed", siteKey, containerKey, valueKey(emitter, key));
1350
+ add(emitter, "choosesEntry", siteKey, containerKey);
1351
+ }
1352
+ function emitAttributeChoice(emitter, call) {
1353
+ const callee = field(call, "function");
1354
+ const [target, name] = callArguments(call);
1355
+ if (callee?.type !== "identifier" || callee.text !== ATTRIBUTE_GETTER || !isBuiltin(emitter, callee.text) || target?.kind !== "positional" || name?.kind !== "positional") {
1356
+ return;
1357
+ }
1358
+ const siteKey = nodeId(emitter.filePath, call);
1359
+ const targetKey = valueKey(emitter, target.node);
1360
+ const written = plainStringOf(name.node);
1361
+ if (written !== null) {
1362
+ add(emitter, "choosesNamedEntry", siteKey, targetKey, written);
1363
+ return;
1364
+ }
1365
+ const ends = writtenEnds(name.node);
1366
+ if (ends === null) {
1367
+ add(emitter, "choosesEntry", siteKey, targetKey);
1368
+ return;
1369
+ }
1370
+ add(emitter, "choosesEntryLike", siteKey, targetKey, ends.start, ends.end);
1371
+ }
1372
+ function writtenEnds(node) {
1373
+ if (node.type !== "string") {
1374
+ return null;
1375
+ }
1376
+ const parts = children(node).filter(
1377
+ (part) => part.type !== "string_start" && part.type !== "string_end"
1229
1378
  );
1379
+ const written = (part) => part.type === "string_content";
1380
+ const firstComputed = parts.findIndex((part) => !written(part));
1381
+ if (firstComputed === -1) {
1382
+ return null;
1383
+ }
1384
+ const lastComputed = parts.length - 1 - [...parts].reverse().findIndex((part) => !written(part));
1385
+ const start = parts.slice(0, firstComputed).map((part) => part.text).join("");
1386
+ const end = parts.slice(lastComputed + 1).map((part) => part.text).join("");
1387
+ return start === "" && end === "" ? null : { start, end };
1388
+ }
1389
+ function isBuiltin(emitter, name) {
1390
+ return nameKey(emitter.filePath, emitter.enclosing, name) === nameId(emitter.filePath, name) && !emitter.moduleNames().has(name);
1230
1391
  }
1231
1392
  function emitWrittenKeyRead(emitter, site, container, key) {
1232
1393
  const name = plainStringOf(key);
@@ -1362,6 +1523,86 @@ function emitComprehensionElement(emitter, comprehension) {
1362
1523
  );
1363
1524
  }
1364
1525
  }
1526
+ var PAIR_TARGET_TYPES = /* @__PURE__ */ new Set(["pattern_list", "tuple_pattern"]);
1527
+ function emitElementVariables(emitter, clause) {
1528
+ const target = field(clause, "left");
1529
+ const over = field(clause, "right");
1530
+ if (target === null || over === null) {
1531
+ return;
1532
+ }
1533
+ const names = PAIR_TARGET_TYPES.has(target.type) ? children(target) : [];
1534
+ const [position, element] = names;
1535
+ const counted = countedCollection(emitter, over);
1536
+ if (counted !== null && names.length === 2 && position?.type === "identifier" && element !== void 0) {
1537
+ emitElementTaken(emitter, element, valueKey(emitter, counted), position);
1538
+ return;
1539
+ }
1540
+ emitElementTaken(emitter, target, valueKey(emitter, over), null);
1541
+ }
1542
+ function emitElementTaken(emitter, target, overKey, position) {
1543
+ const variables = target.type === "identifier" ? [{ node: target, part: null }] : partsTakenApart(target);
1544
+ for (const variable of variables) {
1545
+ const key = valueKey(emitter, variable.node);
1546
+ if (variable.part === null) {
1547
+ add(emitter, "elementOf", key, overKey);
1548
+ continue;
1549
+ }
1550
+ add(emitter, "elementPartOf", key, overKey, variable.part);
1551
+ }
1552
+ const [first] = variables;
1553
+ if (position !== null && first !== void 0) {
1554
+ add(
1555
+ emitter,
1556
+ "positionOf",
1557
+ valueKey(emitter, position),
1558
+ valueKey(emitter, first.node)
1559
+ );
1560
+ }
1561
+ }
1562
+ function partsTakenApart(target) {
1563
+ const parts = [];
1564
+ if (!PAIR_TARGET_TYPES.has(target.type)) {
1565
+ return parts;
1566
+ }
1567
+ for (const [position, part] of children(target).entries()) {
1568
+ if (part.type === "list_splat_pattern") {
1569
+ break;
1570
+ }
1571
+ if (part.type === "identifier") {
1572
+ parts.push({ node: part, part: String(position) });
1573
+ }
1574
+ }
1575
+ return parts;
1576
+ }
1577
+ function countedCollection(emitter, over) {
1578
+ const callee = over.type === "call" ? field(over, "function") : null;
1579
+ const args = field(over, "arguments");
1580
+ const [first] = callArguments(over);
1581
+ if (callee?.type !== "identifier" || callee.text !== POSITION_COUNTER || !isBuiltin(emitter, POSITION_COUNTER) || args === null || writtenArgumentCount(args) !== 1 || first?.kind !== "positional") {
1582
+ return null;
1583
+ }
1584
+ return first.node;
1585
+ }
1586
+ function interpolatedPieces(emitter, string2) {
1587
+ const parts = children(string2);
1588
+ if (!parts.some((part) => part.type === "interpolation")) {
1589
+ return [];
1590
+ }
1591
+ return parts.flatMap((part) => {
1592
+ if (part.type === "string_content") {
1593
+ return [{ text: stringContentValue(part) }];
1594
+ }
1595
+ const expression = part.type === "interpolation" ? field(part, "expression") ?? children(part)[0] : void 0;
1596
+ return expression === void 0 ? [] : [{ hole: valueKey(emitter, expression) }];
1597
+ });
1598
+ }
1599
+ function adjacentPieces(emitter, joined2) {
1600
+ return children(joined2).filter((part) => part.type !== "comment").map((part) => ({ hole: valueKey(emitter, part) }));
1601
+ }
1602
+ var TEMPLATE_PIECES = {
1603
+ string: interpolatedPieces,
1604
+ concatenated_string: adjacentPieces
1605
+ };
1365
1606
  function emitExpressionFact(emitter, child, type) {
1366
1607
  if (type === "call") {
1367
1608
  emitCall(emitter, child);
@@ -1369,6 +1610,7 @@ function emitExpressionFact(emitter, child, type) {
1369
1610
  if (mapping !== null) {
1370
1611
  emitKeyedRead(emitter, child, mapping.container, mapping.key);
1371
1612
  }
1613
+ emitAttributeChoice(emitter, child);
1372
1614
  }
1373
1615
  if (type === "subscript") {
1374
1616
  const index = singleSubscript(child);
@@ -1398,6 +1640,13 @@ function emitExpressionFact(emitter, child, type) {
1398
1640
  if (COMPREHENSION_TYPES.has(type)) {
1399
1641
  emitComprehensionElement(emitter, child);
1400
1642
  }
1643
+ if (type === "for_in_clause") {
1644
+ emitElementVariables(emitter, child);
1645
+ }
1646
+ const pieces = TEMPLATE_PIECES[type]?.(emitter, child) ?? [];
1647
+ if (pieces.length > 0) {
1648
+ addTemplateRows(rowsOf(emitter), nodeId(emitter.filePath, child), pieces);
1649
+ }
1401
1650
  const branches = fallbackBranchesOf(child, type);
1402
1651
  if (branches !== null) {
1403
1652
  for (const branch of branches) {
@@ -1430,10 +1679,10 @@ function emitExpressionFacts(emitter, node) {
1430
1679
  });
1431
1680
  }
1432
1681
  function yieldedValue(node) {
1433
- if (node.children.some((child) => child?.type === "from")) {
1682
+ if (allChildren(node).some((child) => child.type === "from")) {
1434
1683
  return null;
1435
1684
  }
1436
- return node.namedChildren.find((child) => child?.type !== "comment") ?? null;
1685
+ return children(node).find((child) => child?.type !== "comment") ?? null;
1437
1686
  }
1438
1687
  function emitFunctionFacts(emitter, fn, classKey) {
1439
1688
  const funcKey = nodeId(emitter.filePath, fn);
@@ -1505,7 +1754,7 @@ function emitFunctionFacts(emitter, fn, classKey) {
1505
1754
  const stores = /* @__PURE__ */ new Map();
1506
1755
  const visit = (child, type) => {
1507
1756
  if (type === "return_statement") {
1508
- const returned = child.namedChildren[0];
1757
+ const returned = child.namedChild(0);
1509
1758
  if (returned != null) {
1510
1759
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1511
1760
  statesReturn = true;
@@ -1916,6 +2165,9 @@ function readUnderName(emitter, statement, funcKey, accessed) {
1916
2165
  function emitNestedDefinitions(emitter, node) {
1917
2166
  for (const child of children(node)) {
1918
2167
  const type = child.type;
2168
+ if (type === "decorated_definition") {
2169
+ emitDecoratorCalls(emitter, child);
2170
+ }
1919
2171
  if (FUNCTION_TYPES.has(type)) {
1920
2172
  emitFunctionFacts(emitter, child);
1921
2173
  if (emitter.enclosing !== null) {
@@ -1935,6 +2187,20 @@ function emitNestedDefinitions(emitter, node) {
1935
2187
  emitNestedDefinitions(emitter, child);
1936
2188
  }
1937
2189
  }
2190
+ function emitDecoratorCalls(emitter, decorated) {
2191
+ const { definition, decorators } = stripDecorators(decorated);
2192
+ let applied = nodeId(emitter.filePath, definition);
2193
+ for (const decorator of [...decorators].reverse()) {
2194
+ const [expression] = children(decorator);
2195
+ if (expression === void 0) {
2196
+ continue;
2197
+ }
2198
+ const callKey = nodeId(emitter.filePath, decorator);
2199
+ add(emitter, "call", callKey, valueKey(emitter, expression));
2200
+ add(emitter, "callArg", callKey, "0", applied);
2201
+ applied = callKey;
2202
+ }
2203
+ }
1938
2204
  var readingsByTree = /* @__PURE__ */ new WeakMap();
1939
2205
  function scopeReadingOf(fn, given) {
1940
2206
  const tree = fn.tree;
@@ -2543,9 +2809,7 @@ function stringExpression(node) {
2543
2809
  }
2544
2810
  function comparisonExpression(node) {
2545
2811
  const operands = children(node);
2546
- const operators = node.children.filter(
2547
- (child) => child !== null && !child.isNamed
2548
- );
2812
+ const operators = allChildren(node).filter((child) => !child.isNamed);
2549
2813
  const operator = operators[0];
2550
2814
  if (operands.length !== 2 || operators.length !== 1 || operator === void 0) {
2551
2815
  return { kind: "opaque" };
@@ -2639,6 +2903,7 @@ var STATEMENT_TYPES = {
2639
2903
  kind: "return",
2640
2904
  value: children(node)[0] ?? null
2641
2905
  }),
2906
+ raise_statement: (node) => caughtInSameFunction(node) ? OPAQUE : { kind: "throw" },
2642
2907
  try_statement: (node) => ({
2643
2908
  kind: "block",
2644
2909
  body: [
@@ -2727,6 +2992,19 @@ function blockStatements(block) {
2727
2992
  function isRoot(node) {
2728
2993
  return ROOT_TYPES.has(node.type);
2729
2994
  }
2995
+ var EXCEPT_CLAUSE_TYPES = /* @__PURE__ */ new Set(["except_clause", "except_group_clause"]);
2996
+ function caughtInSameFunction(node) {
2997
+ let child = node;
2998
+ let parent = node.parent;
2999
+ while (parent !== null && !isRoot(parent)) {
3000
+ if (parent.type === "try_statement" && field(parent, "body")?.id === child.id && children(parent).some((clause) => EXCEPT_CLAUSE_TYPES.has(clause.type))) {
3001
+ return true;
3002
+ }
3003
+ child = parent;
3004
+ parent = parent.parent;
3005
+ }
3006
+ return false;
3007
+ }
2730
3008
  function isPathStatement(node, parent) {
2731
3009
  return parent.type === "module" || parent.type === "block" || node.type === "elif_clause";
2732
3010
  }
@@ -3139,6 +3417,10 @@ function writtenNodeOf(node, db) {
3139
3417
  const bound = db === void 0 ? void 0 : projects.get(db);
3140
3418
  return bound?.context.writtenTo(node) ?? null;
3141
3419
  }
3420
+ function calledDefinition(call, db) {
3421
+ const bound = db === void 0 ? void 0 : projects.get(db);
3422
+ return bound?.context.callable(call) ?? null;
3423
+ }
3142
3424
  function resolutionKeyOf(node, db) {
3143
3425
  const bound = db === void 0 ? void 0 : projects.get(db);
3144
3426
  return bound?.keyOf(node) ?? null;
@@ -3361,7 +3643,7 @@ function argShapeOf(node) {
3361
3643
  if (node.type === "list") {
3362
3644
  return {
3363
3645
  kind: "list",
3364
- items: node.namedChildren.filter((child) => child !== null).map(readArg)
3646
+ items: children(node).map(readArg)
3365
3647
  };
3366
3648
  }
3367
3649
  return { kind: "other" };
@@ -3372,10 +3654,7 @@ function readCallArguments(argumentList) {
3372
3654
  if (argumentList === null) {
3373
3655
  return { args, keywordArgs };
3374
3656
  }
3375
- for (const child of argumentList.namedChildren) {
3376
- if (child === null) {
3377
- continue;
3378
- }
3657
+ for (const child of children(argumentList)) {
3379
3658
  if (child.type === "keyword_argument") {
3380
3659
  const nameNode = field(child, "name");
3381
3660
  const valueNode = field(child, "value");
@@ -3501,10 +3780,10 @@ function unwrapDecorator(decoratorNode, facts) {
3501
3780
  }
3502
3781
  function passesParametersThrough(def, inner) {
3503
3782
  const params = field(def, "parameters");
3504
- const names = (params?.namedChildren ?? []).filter((child) => child?.type === "identifier").map((child) => child.text);
3783
+ const names = children(params).filter((child) => child.type === "identifier").map((child) => child.text);
3505
3784
  const argumentList = field(inner, "arguments");
3506
- const positional = (argumentList?.namedChildren ?? []).filter(
3507
- (child) => child !== null && child.type !== "keyword_argument"
3785
+ const positional = children(argumentList).filter(
3786
+ (child) => child.type !== "keyword_argument"
3508
3787
  );
3509
3788
  return positional.length === names.length && positional.every(
3510
3789
  (argument, at) => argument.type === "identifier" && argument.text === names[at]
@@ -3527,8 +3806,14 @@ import {
3527
3806
  import { constantOf as constantOf5, literalOf as literalOf6, pathOf as pathOf2 } from "@suss/values";
3528
3807
 
3529
3808
  // src/clientCalls.ts
3530
- import { hasNameHole, restBinding } from "@suss/behavioral-ir";
3531
- import { pathOf } from "@suss/values";
3809
+ import {
3810
+ hasNameHole,
3811
+ restBinding,
3812
+ statusMembersOf,
3813
+ testsStatus
3814
+ } from "@suss/behavioral-ir";
3815
+ import { redirectDeliveryWhenSet } from "@suss/extractor";
3816
+ import { hostOf, pathOf, scalarOf } from "@suss/values";
3532
3817
 
3533
3818
  // src/paths/bodyBranches.ts
3534
3819
  import { enumerateOrDegrade as enumerateOrDegrade2, guardsHoldOn } from "@suss/extractor";
@@ -3549,7 +3834,7 @@ var COMPARISONS = {
3549
3834
  "<=": "lte"
3550
3835
  };
3551
3836
  function operatorText(node) {
3552
- return node.children.filter((child) => child !== null && !child.isNamed).map((child) => child.text).join(" ");
3837
+ return allChildren(node).filter((child) => !child.isNamed).map((child) => child.text).join(" ");
3553
3838
  }
3554
3839
  function valueRefOf(node, facts, inputs) {
3555
3840
  const value = evaluatedValue(node, facts);
@@ -3601,7 +3886,7 @@ function nullCheckOf(subject, negated2, facts, inputs) {
3601
3886
  };
3602
3887
  }
3603
3888
  function comparisonOf(node, facts, inputs) {
3604
- const [left, right] = node.namedChildren.filter(
3889
+ const [left, right] = children(node).filter(
3605
3890
  (child) => child !== null
3606
3891
  );
3607
3892
  if (left === void 0 || right === void 0) {
@@ -3630,14 +3915,14 @@ function comparisonOf(node, facts, inputs) {
3630
3915
  }
3631
3916
  function predicateOf(node, facts, inputs) {
3632
3917
  if (node.type === "parenthesized_expression") {
3633
- const inner = node.namedChildren[0];
3918
+ const inner = node.namedChild(0);
3634
3919
  return inner == null ? opaqueOf(node) : predicateOf(inner, facts, inputs);
3635
3920
  }
3636
3921
  if (node.type === "comparison_operator") {
3637
3922
  return comparisonOf(node, facts, inputs);
3638
3923
  }
3639
3924
  if (node.type === "not_operator") {
3640
- const operand2 = field(node, "argument") ?? node.namedChildren[0];
3925
+ const operand2 = field(node, "argument") ?? node.namedChild(0);
3641
3926
  return operand2 == null ? opaqueOf(node) : { type: "negation", operand: predicateOf(operand2, facts, inputs) };
3642
3927
  }
3643
3928
  if (node.type === "identifier" || node.type === "attribute" || inputs?.has(node.id) === true) {
@@ -3652,11 +3937,11 @@ function predicateOf(node, facts, inputs) {
3652
3937
  var SUBJECT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "subscript"]);
3653
3938
  function operandsOf(node) {
3654
3939
  if (node.type === "parenthesized_expression" || node.type === "not_operator") {
3655
- const inner = field(node, "argument") ?? node.namedChildren[0];
3940
+ const inner = field(node, "argument") ?? node.namedChild(0);
3656
3941
  return inner == null ? [] : operandsOf(inner);
3657
3942
  }
3658
3943
  if (node.type === "comparison_operator") {
3659
- return node.namedChildren.filter(
3944
+ return children(node).filter(
3660
3945
  (child) => child !== null && SUBJECT_TYPES.has(child.type)
3661
3946
  );
3662
3947
  }
@@ -3682,7 +3967,7 @@ var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set(["function_definition", "lam
3682
3967
  function exitKindOf(node, thrown) {
3683
3968
  let sawReturn = false;
3684
3969
  const raises = (current) => {
3685
- for (const child of current.namedChildren) {
3970
+ for (const child of children(current)) {
3686
3971
  if (child === null || NESTED_FUNCTION_TYPES.has(child.type)) {
3687
3972
  continue;
3688
3973
  }
@@ -3710,12 +3995,10 @@ function blockStatements2(block) {
3710
3995
  if (block === null) {
3711
3996
  return [];
3712
3997
  }
3713
- return block.namedChildren.filter((child) => child !== null);
3998
+ return children(block);
3714
3999
  }
3715
4000
  function childBlock(node) {
3716
- return node.namedChildren.find(
3717
- (child) => child !== null && child.type === "block"
3718
- ) ?? null;
4001
+ return children(node).find((child) => child.type === "block") ?? null;
3719
4002
  }
3720
4003
  var Lowerer = class {
3721
4004
  // tree-sitter hands back a fresh wrapper object each time a child is
@@ -3764,7 +4047,7 @@ var Lowerer = class {
3764
4047
  return [nested];
3765
4048
  }
3766
4049
  lowerIf(node) {
3767
- const clauses = node.namedChildren.filter(
4050
+ const clauses = children(node).filter(
3768
4051
  (child) => child !== null && (child.type === "elif_clause" || child.type === "else_clause")
3769
4052
  );
3770
4053
  return {
@@ -3776,10 +4059,10 @@ var Lowerer = class {
3776
4059
  };
3777
4060
  }
3778
4061
  lowerTry(node) {
3779
- const excepts = node.namedChildren.filter(
4062
+ const excepts = children(node).filter(
3780
4063
  (child) => child !== null && (child.type === "except_clause" || child.type === "except_group_clause")
3781
4064
  );
3782
- const finallyClause = node.namedChildren.find(
4065
+ const finallyClause = children(node).find(
3783
4066
  (child) => child !== null && child.type === "finally_clause"
3784
4067
  ) ?? null;
3785
4068
  return {
@@ -3795,7 +4078,7 @@ var Lowerer = class {
3795
4078
  (child) => child.type === "case_clause"
3796
4079
  );
3797
4080
  const groups = cases.map((clause) => {
3798
- const pattern = clause.namedChildren.find(
4081
+ const pattern = children(clause).find(
3799
4082
  (child) => child !== null && child.type === "case_pattern"
3800
4083
  ) ?? null;
3801
4084
  return {
@@ -3876,7 +4159,7 @@ function literalArgOf(node, facts) {
3876
4159
  return null;
3877
4160
  }
3878
4161
  function bodyCalls(node, found = []) {
3879
- for (const child of node.namedChildren) {
4162
+ for (const child of children(node)) {
3880
4163
  if (child === null || NESTED_DEFINITION_TYPES.has(child.type)) {
3881
4164
  continue;
3882
4165
  }
@@ -3888,7 +4171,7 @@ function bodyCalls(node, found = []) {
3888
4171
  return found;
3889
4172
  }
3890
4173
  function moduleLoadCalls(node, found = []) {
3891
- for (const child of node.namedChildren) {
4174
+ for (const child of children(node)) {
3892
4175
  if (child === null || !runsAtModuleLoad(child)) {
3893
4176
  continue;
3894
4177
  }
@@ -3928,7 +4211,7 @@ function argumentNodes(call) {
3928
4211
  if (args === null) {
3929
4212
  return [];
3930
4213
  }
3931
- return args.namedChildren.filter((child) => child !== null).map(
4214
+ return children(args).map(
3932
4215
  (child) => child.type === "keyword_argument" ? field(child, "value") ?? child : child
3933
4216
  );
3934
4217
  }
@@ -3939,6 +4222,9 @@ function calleeText(call) {
3939
4222
  return field(call, "function")?.text ?? call.text;
3940
4223
  }
3941
4224
  function invocationEffects(definitionNode, facts) {
4225
+ return invocationSites(definitionNode, facts).map((site) => site.effect);
4226
+ }
4227
+ function invocationSites(definitionNode, facts) {
3942
4228
  const body = field(definitionNode, "body");
3943
4229
  if (body === null) {
3944
4230
  return [];
@@ -3946,14 +4232,18 @@ function invocationEffects(definitionNode, facts) {
3946
4232
  return invocationEffectsIn(body, bodyCalls(body), facts);
3947
4233
  }
3948
4234
  function moduleLoadInvocationEffects(moduleNode, facts) {
3949
- return invocationEffectsIn(moduleNode, moduleLoadCalls(moduleNode), facts);
4235
+ return invocationEffectsIn(
4236
+ moduleNode,
4237
+ moduleLoadCalls(moduleNode),
4238
+ facts
4239
+ ).map((site) => site.effect);
3950
4240
  }
3951
4241
  var ASKED_ABOUT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "call"]);
3952
4242
  function askedNodesUnder(node, found = []) {
3953
4243
  if (ASKED_ABOUT_TYPES.has(node.type)) {
3954
4244
  found.push(node);
3955
4245
  }
3956
- for (const child of node.namedChildren) {
4246
+ for (const child of children(node)) {
3957
4247
  if (child !== null && !NESTED_DEFINITION_TYPES.has(child.type)) {
3958
4248
  askedNodesUnder(child, found);
3959
4249
  }
@@ -4027,11 +4317,14 @@ function invocationEffectsIn(body, written, facts) {
4027
4317
  (condition) => predicateOf(condition, facts, inputs)
4028
4318
  );
4029
4319
  return {
4030
- type: "invocation",
4031
- callee: calleeText(call),
4032
- args: argsOf(call, facts),
4033
- async: false,
4034
- ...conditions.length > 0 ? { preconditions: conditions } : {}
4320
+ call,
4321
+ effect: {
4322
+ type: "invocation",
4323
+ callee: calleeText(call),
4324
+ args: argsOf(call, facts),
4325
+ async: false,
4326
+ ...conditions.length > 0 ? { preconditions: conditions } : {}
4327
+ }
4035
4328
  };
4036
4329
  });
4037
4330
  }
@@ -4066,7 +4359,7 @@ function statedBy(statement, name) {
4066
4359
  }
4067
4360
  if (statement.type === "as_pattern") {
4068
4361
  const alias = field(statement, "alias");
4069
- const value = statement.namedChildren[0] ?? null;
4362
+ const value = statement.namedChild(0) ?? null;
4070
4363
  const callee = value?.type === "call" ? field(value, "function") : null;
4071
4364
  return alias?.text === name ? callee : null;
4072
4365
  }
@@ -4262,7 +4555,7 @@ function importedFunctionMatch(call, options) {
4262
4555
  return null;
4263
4556
  }
4264
4557
  const args = field(call, "arguments");
4265
- const first = args?.namedChildren.find((child) => child !== null) ?? null;
4558
+ const first = children(args)[0] ?? null;
4266
4559
  const statement = first === null ? null : writtenStatement(first, void 0, options.facts);
4267
4560
  if (statement === null) {
4268
4561
  return null;
@@ -4626,7 +4919,7 @@ function columnComparisons(node) {
4626
4919
  const args = field(node, "arguments");
4627
4920
  return (args === null ? [] : children(args)).flatMap(columnComparisons);
4628
4921
  }
4629
- const operator = node.children.find((child) => !child?.isNamed);
4922
+ const operator = allChildren(node).find((child) => !child.isNamed);
4630
4923
  const [left, right, ...rest] = children(node);
4631
4924
  if (node.type !== "comparison_operator" || operator?.type !== "==" || left === void 0 || right === void 0 || rest.length > 0) {
4632
4925
  return [];
@@ -4645,13 +4938,37 @@ function modelColumn(node) {
4645
4938
  }
4646
4939
  return field(node, "attribute")?.text ?? null;
4647
4940
  }
4648
- function selectorOf(chain, valueMethods) {
4941
+ function selectorOf(chain, valueMethods, model) {
4942
+ const picking = laterCalls(chain).filter(
4943
+ (call) => !valueMethods.includes(methodNameOf(call))
4944
+ );
4649
4945
  return [
4650
- ...new Set(
4651
- laterCalls(chain).filter((call) => !valueMethods.includes(methodNameOf(call))).flatMap((call) => pickedBy(call).map((one) => one.name))
4652
- )
4946
+ .../* @__PURE__ */ new Set([
4947
+ ...picking.flatMap((call) => pickedBy(call).map((one) => one.name)),
4948
+ ...model === null ? [] : picking.flatMap((call) => columnsComparedBy(call, model))
4949
+ ])
4653
4950
  ];
4654
4951
  }
4952
+ var COMBINED_TESTS = ["and_", "or_", "not_"];
4953
+ function columnsComparedBy(call, model) {
4954
+ const args = field(call, "arguments");
4955
+ return (args === null ? [] : children(args)).flatMap(
4956
+ (argument) => columnsCompared(argument, model)
4957
+ );
4958
+ }
4959
+ function columnsCompared(node, model) {
4960
+ if (node.type === "call" && COMBINED_TESTS.includes(methodNameOf(node))) {
4961
+ return columnsComparedBy(node, model);
4962
+ }
4963
+ if (node.type !== "comparison_operator") {
4964
+ return [];
4965
+ }
4966
+ return children(node).flatMap((side) => {
4967
+ const object = side.type === "attribute" ? field(side, "object") : null;
4968
+ const column = object?.text === model ? field(side, "attribute") : null;
4969
+ return column === null ? [] : [column.text];
4970
+ });
4971
+ }
4655
4972
  var TABLE_METHODS = ["select_from", "join"];
4656
4973
  function modelOf(chain, options) {
4657
4974
  const fromRoot = modelArgument(chain.root, chain.root, options);
@@ -4716,14 +5033,15 @@ function isClassName(name) {
4716
5033
  }
4717
5034
  function effectFor2(pattern, chain, operation, options) {
4718
5035
  const valueMethods = pattern.valueMethods ?? [];
4719
- const picked = selectorOf(chain, valueMethods);
5036
+ const model = modelOf(chain, options);
5037
+ const picked = selectorOf(chain, valueMethods, model);
4720
5038
  const effect = {
4721
5039
  type: "interaction",
4722
5040
  binding: storageBinding2({
4723
5041
  recognition: "python-storage",
4724
5042
  storageSystem: pattern.storageSystem,
4725
5043
  scope: "default",
4726
- container: modelOf(chain, options)
5044
+ container: model
4727
5045
  }),
4728
5046
  callee: chain.last.text,
4729
5047
  interaction: {
@@ -4999,10 +5317,7 @@ function literalAt(key, facts) {
4999
5317
 
5000
5318
  // src/paths/bodyBranches.ts
5001
5319
  function returnStatements(node, found = []) {
5002
- for (const child of node?.namedChildren ?? []) {
5003
- if (child === null) {
5004
- continue;
5005
- }
5320
+ for (const child of children(node)) {
5006
5321
  if (child.type === "function_definition" || child.type === "lambda") {
5007
5322
  continue;
5008
5323
  }
@@ -5059,7 +5374,8 @@ function enumerateBodyBranches(options) {
5059
5374
  const enumerated = enumerateOrDegrade2(
5060
5375
  {
5061
5376
  statements: lowered.statements,
5062
- terminalsByStmt: lowered.terminalsByStmt
5377
+ terminalsByStmt: lowered.terminalsByStmt,
5378
+ ...options.keepsArms === void 0 ? {} : { keepsArms: options.keepsArms }
5063
5379
  },
5064
5380
  statements
5065
5381
  );
@@ -5096,66 +5412,296 @@ function enumerateBodyBranches(options) {
5096
5412
  return branches;
5097
5413
  }
5098
5414
 
5099
- // src/clientCalls.ts
5100
- function clientCallUnits(root, module, pack, pattern, options) {
5101
- const units = [];
5102
- for (const definition of functionDefinitions(root)) {
5103
- const name = field(definition, "name")?.text;
5104
- if (name === void 0) {
5105
- continue;
5106
- }
5107
- for (const call of bodyCalls(definition)) {
5108
- for (const request of requestCalls(call, pattern, module, options)) {
5109
- units.push(
5110
- clientUnit(definition, name, request, pattern, pack, options)
5111
- );
5112
- }
5113
- }
5415
+ // src/paths/raisedResponses.ts
5416
+ import { constantOf as constantOf4 } from "@suss/values";
5417
+ function statusNumberOf(node, options) {
5418
+ const value = constantOf4(evaluatedValue(node, options.facts));
5419
+ if (typeof value === "number") {
5420
+ return value;
5114
5421
  }
5115
- return units;
5422
+ const origin = originOf(node, options.module);
5423
+ return origin === null ? null : options.constants?.[`${origin.module}.${origin.name}`] ?? null;
5116
5424
  }
5117
- function clientCallReceivers(root, module) {
5425
+ var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
5426
+ function raisedResponses(body, options) {
5427
+ if (options.calls.length === 0) {
5428
+ return [];
5429
+ }
5430
+ return raisesIn(body, options);
5431
+ }
5432
+ function raisesIn(body, options) {
5433
+ if (body === null) {
5434
+ return [];
5435
+ }
5118
5436
  const found = [];
5119
- for (const definition of functionDefinitions(root)) {
5120
- for (const call of bodyCalls(definition)) {
5121
- const callee = field(call, "function");
5122
- if (callee === null || originOf(callee, module) !== null) {
5437
+ const walk = (node) => {
5438
+ for (const child of children(node)) {
5439
+ if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
5123
5440
  continue;
5124
5441
  }
5125
- const object = field(callee, "object");
5126
- if (object !== null && field(callee, "attribute") !== null) {
5127
- found.push(object);
5442
+ const response = raisedResponseOf(child, options);
5443
+ if (response !== null) {
5444
+ found.push(response);
5128
5445
  }
5446
+ walk(child);
5129
5447
  }
5130
- }
5448
+ };
5449
+ walk(body);
5131
5450
  return found;
5132
5451
  }
5133
- function functionDefinitions(node, found = []) {
5134
- for (const child of children(node)) {
5135
- if (isFunction(child)) {
5136
- found.push(child);
5137
- }
5138
- functionDefinitions(child, found);
5452
+ var ENDING_EXPRESSION = {
5453
+ raise_statement: (statement) => statement.namedChild(0) ?? null,
5454
+ expression_statement: (statement) => {
5455
+ const only = statement.namedChild(0);
5456
+ return only?.type === "call" ? only : null;
5139
5457
  }
5140
- return found;
5141
- }
5142
- function requestCalls(call, pattern, module, options) {
5143
- const callee = field(call, "function");
5144
- const attribute = callee === null ? null : calledAttribute(callee, pattern, module, options.facts);
5145
- if (attribute === null) {
5146
- return [];
5458
+ };
5459
+ function raisedResponseOf(statement, options) {
5460
+ const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
5461
+ if (expression === null) {
5462
+ return null;
5147
5463
  }
5148
- const plain = requestCall(call, attribute, pattern, options);
5149
- if (plain !== null && !hasNameHole(plain.path)) {
5150
- return [plain];
5464
+ const thrownByCall = statement.type !== "raise_statement";
5465
+ const prebuilt = thrownByCall ? null : prebuiltCallOf(expression, options);
5466
+ if (prebuilt !== null) {
5467
+ return {
5468
+ statement,
5469
+ thrownByCall,
5470
+ terminal: terminalOf(
5471
+ statement,
5472
+ calleeOf(prebuilt.call, prebuilt.call),
5473
+ prebuilt.call,
5474
+ prebuilt.declared,
5475
+ prebuilt.options
5476
+ )
5477
+ };
5151
5478
  }
5152
- const byBoundary = /* @__PURE__ */ new Map();
5153
- for (const site of constructionSitesOf(call, options.facts)) {
5154
- const stated = requestCall(call, attribute, pattern, options, site);
5479
+ const call = expression.type === "call" ? expression : null;
5480
+ const callee = calleeOf(expression, call);
5481
+ const declared = declaredCallFor(callee, options);
5482
+ if (declared === void 0 && thrownByCall) {
5483
+ return null;
5484
+ }
5485
+ return {
5486
+ statement,
5487
+ thrownByCall,
5488
+ terminal: terminalOf(statement, callee, call, declared, options)
5489
+ };
5490
+ }
5491
+ function prebuiltCallOf(expression, options) {
5492
+ if (expression.type !== "identifier" && expression.type !== "attribute") {
5493
+ return null;
5494
+ }
5495
+ const written = writtenNodeOf(expression, options.facts);
5496
+ if (written === null || written.type !== "call") {
5497
+ return null;
5498
+ }
5499
+ const inItsFile = { ...options, module: moduleOf(written) };
5500
+ const declared = declaredCallFor(calleeOf(written, written), inItsFile);
5501
+ return declared === void 0 ? null : { call: written, declared, options: inItsFile };
5502
+ }
5503
+ function returnedResponseStatus(statement, options) {
5504
+ const returned = statement.namedChild(0);
5505
+ if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
5506
+ return null;
5507
+ }
5508
+ const callee = field(returned, "function");
5509
+ if (callee === null) {
5510
+ return null;
5511
+ }
5512
+ const declared = declaredCallFor(callee, options);
5513
+ return declared === void 0 ? null : statusOf(declared, returned, options);
5514
+ }
5515
+ function calleeOf(expression, call) {
5516
+ return call === null ? expression : field(call, "function") ?? expression;
5517
+ }
5518
+ function declaredCallFor(callee, options) {
5519
+ const origin = originOf(callee, options.module);
5520
+ if (origin === null) {
5521
+ return void 0;
5522
+ }
5523
+ const qualified = `${origin.module}.${origin.name}`;
5524
+ return options.calls.find((call) => call.callee === qualified);
5525
+ }
5526
+ function terminalOf(statement, callee, call, declared, options) {
5527
+ return {
5528
+ kind: "throw",
5529
+ statusCode: declared === void 0 ? null : statusOf(declared, call, options),
5530
+ body: null,
5531
+ exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
5532
+ message: call === null ? null : writtenMessageOf(call),
5533
+ ...declared === void 0 ? {} : { producesResponse: true },
5534
+ component: null,
5535
+ renderTree: null,
5536
+ delegateTarget: null,
5537
+ emitEvent: null,
5538
+ location: rangeOf(statement)
5539
+ };
5540
+ }
5541
+ function lastSegmentOf(dotted) {
5542
+ const segment = dotted.split(".").pop();
5543
+ return segment === void 0 || segment === "" ? null : segment;
5544
+ }
5545
+ function statusOf(declared, call, options) {
5546
+ const written = call === null ? null : statusArgumentOf(declared, call);
5547
+ if (written === null) {
5548
+ return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
5549
+ }
5550
+ const value = statusNumberOf(written, options);
5551
+ return value === null ? { type: "dynamic", sourceText: written.text } : { type: "literal", value };
5552
+ }
5553
+ function argumentsOf2(call) {
5554
+ return children(field(call, "arguments"));
5555
+ }
5556
+ function statusArgumentOf(declared, call) {
5557
+ const args = argumentsOf2(call);
5558
+ const keyed = args.find(
5559
+ (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
5560
+ );
5561
+ if (keyed !== void 0) {
5562
+ return field(keyed, "value");
5563
+ }
5564
+ if (declared.statusArgument === void 0) {
5565
+ return null;
5566
+ }
5567
+ const positional = args.filter((arg) => arg.type !== "keyword_argument");
5568
+ return positional[declared.statusArgument] ?? null;
5569
+ }
5570
+ function writtenMessageOf(call) {
5571
+ for (const arg of argumentsOf2(call)) {
5572
+ const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
5573
+ const literal = value === null ? null : stringLiteralValue(value);
5574
+ if (literal !== null) {
5575
+ return literal;
5576
+ }
5577
+ }
5578
+ return null;
5579
+ }
5580
+
5581
+ // src/responseHandOff.ts
5582
+ import { helperPathsOnVariables, splitAtHandOffs } from "@suss/extractor";
5583
+ function throughResponseHelpers(branches, sites, options) {
5584
+ const handOffs = [];
5585
+ for (const { call, effect } of sites) {
5586
+ const helper = helperBranchesAt(call, options);
5587
+ if (helper !== null) {
5588
+ handOffs.push({
5589
+ helper,
5590
+ preconditions: effect.preconditions,
5591
+ line: rangeOf(call).start,
5592
+ at: rangeOf(call)
5593
+ });
5594
+ }
5595
+ }
5596
+ return handOffs.length === 0 ? [...branches] : branches.flatMap((branch) => splitAtHandOffs(branch, handOffs));
5597
+ }
5598
+ function helperBranchesAt(call, options) {
5599
+ const handed = callArguments(call).filter(
5600
+ (argument) => argument.node.type === "identifier" && refersToResponse(argument.node, options)
5601
+ );
5602
+ if (handed.length === 0) {
5603
+ return null;
5604
+ }
5605
+ const helper = calledDefinition(call, options.facts);
5606
+ if (helper === null || helper.type !== "function_definition") {
5607
+ return null;
5608
+ }
5609
+ const passed = /* @__PURE__ */ new Map();
5610
+ const parameters = parameterShapes(helper, isMethod(helper) ? 1 : 0);
5611
+ for (const argument of handed) {
5612
+ const parameter = parameters.find(
5613
+ (one) => argument.kind === "positional" ? one.position === argument.position : one.name === argument.name
5614
+ );
5615
+ if (parameter !== void 0) {
5616
+ passed.set(parameter.name, argument.node.text);
5617
+ }
5618
+ }
5619
+ return passed.size === 0 ? null : helperPathsOnVariables(options.readHelper(helper), passed);
5620
+ }
5621
+ function refersToResponse(name, options) {
5622
+ const written = awaited(writtenNodeOf(name, options.facts));
5623
+ if (written === null) {
5624
+ return false;
5625
+ }
5626
+ if (sameNode(written, options.request)) {
5627
+ return true;
5628
+ }
5629
+ const callee = written.type === "call" ? field(written, "function") : null;
5630
+ const method = callee === null ? null : field(callee, "attribute");
5631
+ const receiver = callee === null ? null : field(callee, "object");
5632
+ if (method === null || receiver?.type !== "identifier" || !options.bodyMethods.includes(method.text)) {
5633
+ return false;
5634
+ }
5635
+ const parsedFrom = awaited(writtenNodeOf(receiver, options.facts));
5636
+ return parsedFrom !== null && sameNode(parsedFrom, options.request);
5637
+ }
5638
+ function awaited(node) {
5639
+ return node?.type === "await" ? node.namedChild(0) ?? null : node;
5640
+ }
5641
+ function sameNode(a, b) {
5642
+ return a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.tree === b.tree;
5643
+ }
5644
+
5645
+ // src/clientCalls.ts
5646
+ function clientCallUnits(root, module, pack, pattern, options) {
5647
+ const units = [];
5648
+ for (const definition of functionDefinitions(root)) {
5649
+ const name = field(definition, "name")?.text;
5650
+ if (name === void 0) {
5651
+ continue;
5652
+ }
5653
+ for (const call of bodyCalls(definition)) {
5654
+ for (const request of requestCalls(call, pattern, module, options)) {
5655
+ units.push(
5656
+ clientUnit(definition, name, request, pattern, pack, options)
5657
+ );
5658
+ }
5659
+ }
5660
+ }
5661
+ return units;
5662
+ }
5663
+ function clientCallReceivers(root, module) {
5664
+ const found = [];
5665
+ for (const definition of functionDefinitions(root)) {
5666
+ for (const call of bodyCalls(definition)) {
5667
+ const callee = field(call, "function");
5668
+ if (callee === null || originOf(callee, module) !== null) {
5669
+ continue;
5670
+ }
5671
+ const object = field(callee, "object");
5672
+ if (object !== null && field(callee, "attribute") !== null) {
5673
+ found.push(object);
5674
+ }
5675
+ }
5676
+ }
5677
+ return found;
5678
+ }
5679
+ function functionDefinitions(node, found = []) {
5680
+ for (const child of children(node)) {
5681
+ if (isFunction(child)) {
5682
+ found.push(child);
5683
+ }
5684
+ functionDefinitions(child, found);
5685
+ }
5686
+ return found;
5687
+ }
5688
+ function requestCalls(call, pattern, module, options) {
5689
+ const callee = field(call, "function");
5690
+ const attribute = callee === null ? null : calledAttribute(callee, pattern, module, options.facts);
5691
+ if (attribute === null) {
5692
+ return [];
5693
+ }
5694
+ const plain = requestCall(call, attribute, pattern, options);
5695
+ if (plain !== null && !hasNameHole(plain.path)) {
5696
+ return [plain];
5697
+ }
5698
+ const byBoundary = /* @__PURE__ */ new Map();
5699
+ for (const site of constructionSitesOf(call, options.facts)) {
5700
+ const stated = requestCall(call, attribute, pattern, options, site);
5155
5701
  if (stated === null) {
5156
5702
  continue;
5157
5703
  }
5158
- const key = `${stated.method} ${stated.path}`;
5704
+ const key = `${stated.method} ${stated.host ?? ""} ${stated.path}`;
5159
5705
  if (!byBoundary.has(key)) {
5160
5706
  byBoundary.set(key, stated);
5161
5707
  }
@@ -5166,16 +5712,33 @@ function requestCalls(call, pattern, module, options) {
5166
5712
  return plain === null ? [] : [plain];
5167
5713
  }
5168
5714
  function requestCall(call, attribute, pattern, options, site) {
5715
+ const stated = methodAndPath(call, attribute, pattern, options, site);
5716
+ if (stated === null) {
5717
+ return null;
5718
+ }
5719
+ const redirectDelivery = redirectDeliveryAt(call, pattern, options, site);
5720
+ return redirectDelivery === void 0 ? stated : { ...stated, redirectDelivery };
5721
+ }
5722
+ function redirectDeliveryAt(call, pattern, options, site) {
5723
+ const option = pattern.response?.redirectOption;
5724
+ const argument = option === void 0 ? null : argumentAt(call, null, option.name);
5725
+ if (option === void 0 || argument === null) {
5726
+ return void 0;
5727
+ }
5728
+ const value = scalarOf(evaluatedValue(argument, options.facts, site));
5729
+ return value === null ? void 0 : redirectDeliveryWhenSet(option, value);
5730
+ }
5731
+ function methodAndPath(call, attribute, pattern, options, site) {
5169
5732
  const verb = pattern.verbAttributeNames[attribute];
5170
5733
  if (verb !== void 0) {
5171
- const path9 = urlAt(
5734
+ const target2 = urlAt(
5172
5735
  call,
5173
5736
  pattern.url.position,
5174
5737
  pattern.url.keyword,
5175
5738
  options,
5176
5739
  site
5177
5740
  );
5178
- return path9 === null ? null : { method: verb, path: path9, range: rangeOf(call) };
5741
+ return target2 === null ? null : { method: verb, ...target2, range: rangeOf(call), node: call };
5179
5742
  }
5180
5743
  const methodCall = pattern.methodCall;
5181
5744
  if (methodCall === void 0 || methodCall.attribute !== attribute) {
@@ -5190,14 +5753,19 @@ function requestCall(call, attribute, pattern, options, site) {
5190
5753
  if (method === null) {
5191
5754
  return null;
5192
5755
  }
5193
- const path8 = urlAt(
5756
+ const target = urlAt(
5194
5757
  call,
5195
5758
  methodCall.urlPosition,
5196
5759
  pattern.url.keyword,
5197
5760
  options,
5198
5761
  site
5199
5762
  );
5200
- return path8 === null ? null : { method: method.toUpperCase(), path: path8, range: rangeOf(call) };
5763
+ return target === null ? null : {
5764
+ method: method.toUpperCase(),
5765
+ ...target,
5766
+ range: rangeOf(call),
5767
+ node: call
5768
+ };
5201
5769
  }
5202
5770
  function calledAttribute(callee, pattern, module, facts) {
5203
5771
  const origin = originOf(callee, module);
@@ -5230,7 +5798,9 @@ function urlAt(call, position, keyword, options, site) {
5230
5798
  if (argument === null) {
5231
5799
  return null;
5232
5800
  }
5233
- return pathOf(evaluatedValue(argument, options.facts, site)) ?? null;
5801
+ const value = evaluatedValue(argument, options.facts, site);
5802
+ const path8 = pathOf(value);
5803
+ return path8 === void 0 ? null : { path: path8, host: hostOf(value) };
5234
5804
  }
5235
5805
  function argumentAt(call, position, keyword) {
5236
5806
  const args = field(call, "arguments");
@@ -5248,25 +5818,33 @@ function argumentAt(call, position, keyword) {
5248
5818
  }
5249
5819
  positional.push(child);
5250
5820
  }
5251
- return positional[position] ?? null;
5821
+ return position === null ? null : positional[position] ?? null;
5252
5822
  }
5253
- function callerBranches(definition, range, facts) {
5823
+ function callerBranches(definition, reading) {
5824
+ const range = rangeOf(definition);
5254
5825
  const body = field(definition, "body");
5255
- const effects = invocationEffects(definition, facts);
5256
- const terminals = bodyTerminals(body, []);
5257
- if (body === null || terminals.length === 0) {
5826
+ const sites = invocationSites(definition, reading.facts);
5827
+ const effects = sites.map((site) => site.effect);
5828
+ if (body === null) {
5258
5829
  return [handsBack(range, effects)];
5259
5830
  }
5831
+ const raised = raisesIn(body, {
5832
+ calls: [],
5833
+ module: moduleOf(definition),
5834
+ facts: reading.facts
5835
+ });
5260
5836
  const branches = enumerateBodyBranches({
5261
5837
  body,
5262
- terminals,
5263
- raised: [],
5838
+ terminals: bodyTerminals(body, raised),
5839
+ raised,
5264
5840
  effects,
5265
- branchOf: (found) => found.type === "raise" ? { terminal: found.terminal, location: rangeOf(found.statement) } : handsBackAt(rangeOf(found.statement)),
5841
+ branchOf: (found2) => found2.type === "raise" ? { terminal: found2.terminal, location: rangeOf(found2.statement) } : handsBackAt(rangeOf(found2.statement)),
5266
5842
  fallthrough: handsBackAt(range),
5267
- facts
5843
+ facts: reading.facts,
5844
+ keepsArms: (condition) => testsStatus(predicateOf(condition, reading.facts), reading.statusMembers)
5268
5845
  });
5269
- return branches.length === 0 ? [handsBack(range, effects)] : branches;
5846
+ const found = branches.length === 0 ? [handsBack(range, effects)] : branches;
5847
+ return reading.handOff === void 0 ? found : throughResponseHelpers(found, sites, reading.handOff);
5270
5848
  }
5271
5849
  function handsBackAt(range) {
5272
5850
  return {
@@ -5302,11 +5880,24 @@ function responseAccessors(pattern) {
5302
5880
  ...response.body === void 0 ? {} : { bodyAccessors: response.body },
5303
5881
  ...response.statusCode === void 0 ? {} : { statusAccessors: response.statusCode },
5304
5882
  ...response.success === void 0 ? {} : { successAccessors: response.success },
5305
- ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery }
5883
+ ...response.failureDelivery === void 0 ? {} : { failureDelivery: response.failureDelivery },
5884
+ ...response.redirectDelivery === void 0 ? {} : { redirectDelivery: response.redirectDelivery }
5306
5885
  };
5307
5886
  }
5308
5887
  function clientUnit(definition, name, request, pattern, pack, options) {
5309
5888
  const range = rangeOf(definition);
5889
+ const accessors = responseAccessors(pattern);
5890
+ const statusMembers = statusMembersOf(accessors);
5891
+ const reading = {
5892
+ facts: options.facts,
5893
+ statusMembers,
5894
+ handOff: {
5895
+ request: request.node,
5896
+ bodyMethods: accessors.bodyAccessors ?? [],
5897
+ facts: options.facts,
5898
+ readHelper: (helper) => callerBranches(helper, { facts: options.facts, statusMembers })
5899
+ }
5900
+ };
5310
5901
  return {
5311
5902
  identity: {
5312
5903
  name,
@@ -5322,11 +5913,13 @@ function clientUnit(definition, name, request, pattern, pack, options) {
5322
5913
  transport: pack.protocol,
5323
5914
  method: request.method,
5324
5915
  path: request.path,
5916
+ host: request.host,
5325
5917
  recognition: pack.name
5326
5918
  }),
5327
5919
  parameters: [],
5328
- branches: callerBranches(definition, range, options.facts),
5329
- ...responseAccessors(pattern),
5920
+ branches: callerBranches(definition, reading),
5921
+ ...accessors,
5922
+ ...request.redirectDelivery === void 0 ? {} : { redirectDelivery: request.redirectDelivery },
5330
5923
  bodyContent: "statements",
5331
5924
  dependencyCalls: [],
5332
5925
  declaredContract: null
@@ -5549,7 +6142,7 @@ function isTestedAt(parent, child, subject, seen) {
5549
6142
  }
5550
6143
  function conditionOf(node) {
5551
6144
  if (node.type === "conditional_expression") {
5552
- return node.namedChildren[1] ?? null;
6145
+ return node.namedChild(1) ?? null;
5553
6146
  }
5554
6147
  return CONDITIONED_TYPES.has(node.type) ? field(node, "condition") : null;
5555
6148
  }
@@ -5572,7 +6165,7 @@ function isPresentAt(node, subject) {
5572
6165
  }
5573
6166
  function isBranchWherePresent(parent, child, subject) {
5574
6167
  if (parent.type === "conditional_expression") {
5575
- const [whenTrue, test2, whenFalse] = parent.namedChildren;
6168
+ const [whenTrue, test2, whenFalse] = children(parent);
5576
6169
  return test2 !== void 0 && test2 !== null && (whenTrue?.id === child.id && isPresentWhen(test2, true, subject) || whenFalse?.id === child.id && isPresentWhen(test2, false, subject));
5577
6170
  }
5578
6171
  if (parent.type === "boolean_operator") {
@@ -5610,7 +6203,7 @@ function followsExitWhenAbsent(parent, child, subject) {
5610
6203
  if (parent.type !== "block" && parent.type !== "module") {
5611
6204
  return false;
5612
6205
  }
5613
- const statements = parent.namedChildren;
6206
+ const statements = children(parent);
5614
6207
  const at = statements.findIndex((statement) => statement?.id === child.id);
5615
6208
  return statements.slice(0, at).some(
5616
6209
  (statement) => statement !== null && leavesWhenAbsent(statement, subject)
@@ -5654,7 +6247,7 @@ function continuationRaises(from) {
5654
6247
  let parent = current.parent;
5655
6248
  while (parent !== null) {
5656
6249
  if (parent.type === "block" || parent.type === "module") {
5657
- const statements = parent.namedChildren;
6250
+ const statements = children(parent);
5658
6251
  const at = statements.findIndex((one) => one?.id === current.id);
5659
6252
  const exit = firstExit(statements.slice(at + 1));
5660
6253
  if (exit !== null) {
@@ -5698,7 +6291,7 @@ function exitOf(statement) {
5698
6291
  if (LEAVING_TYPES.has(statement.type)) {
5699
6292
  return "leave";
5700
6293
  }
5701
- return statement.type === "block" ? firstExit(statement.namedChildren) : null;
6294
+ return statement.type === "block" ? firstExit(children(statement)) : null;
5702
6295
  }
5703
6296
  function isPresentWhen(test, outcome, subject) {
5704
6297
  const expression = peelParens(test);
@@ -5738,7 +6331,7 @@ function isPresentAfterComparison(comparison, outcome, subject) {
5738
6331
  }
5739
6332
  function comparisonParts(comparison) {
5740
6333
  const operators = fields(comparison, "operators");
5741
- const operands = comparison.namedChildren.filter(
6334
+ const operands = children(comparison).filter(
5742
6335
  (child) => child !== null && !operators.some((one) => one.id === child.id)
5743
6336
  );
5744
6337
  const [left, right] = operands;
@@ -5761,7 +6354,7 @@ function comparedWithNone(comparison) {
5761
6354
  }
5762
6355
  var NONE_OPERATORS = /* @__PURE__ */ new Set(["is", "is not", "==", "!="]);
5763
6356
  function peelParens(node) {
5764
- const inner = node.type === "parenthesized_expression" ? node.namedChildren[0] : null;
6357
+ const inner = node.type === "parenthesized_expression" ? node.namedChild(0) : null;
5765
6358
  return inner === null || inner === void 0 ? node : peelParens(inner);
5766
6359
  }
5767
6360
  function climbParens(node) {
@@ -5819,7 +6412,7 @@ function envReadEffects(root, module, facts) {
5819
6412
  const slots = [];
5820
6413
  const readsHelpers = facts !== void 0 && sitesByDb.has(facts);
5821
6414
  const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
5822
- walkDescendants(root, startScope, {
6415
+ walkDescendants(root, startScope, children, {
5823
6416
  at: (node, scope) => {
5824
6417
  const syntax = envReadSyntaxAt(node, scope);
5825
6418
  if (syntax !== null) {
@@ -5878,7 +6471,7 @@ function hasFallback(node, spelling, scope) {
5878
6471
  }
5879
6472
  function envFactsIn(filePath, root, module) {
5880
6473
  const found = { sites: [], objects: [] };
5881
- walkDescendants(root, module.moduleScope, {
6474
+ walkDescendants(root, module.moduleScope, children, {
5882
6475
  at: (node, scope) => {
5883
6476
  if (isEnviron(node, scope) && handsOnward(node)) {
5884
6477
  found.objects.push(keyOf(filePath, node));
@@ -6096,137 +6689,6 @@ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6096
6689
  );
6097
6690
  }
6098
6691
 
6099
- // src/paths/raisedResponses.ts
6100
- import { constantOf as constantOf4 } from "@suss/values";
6101
- var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
6102
- function raisedResponses(body, options) {
6103
- if (body === null || options.calls.length === 0) {
6104
- return [];
6105
- }
6106
- const found = [];
6107
- const walk = (node) => {
6108
- for (const child of node.namedChildren) {
6109
- if (child === null || NESTED_DEFINITION_TYPES2.has(child.type)) {
6110
- continue;
6111
- }
6112
- const response = raisedResponseOf(child, options);
6113
- if (response !== null) {
6114
- found.push(response);
6115
- }
6116
- walk(child);
6117
- }
6118
- };
6119
- walk(body);
6120
- return found;
6121
- }
6122
- var ENDING_EXPRESSION = {
6123
- raise_statement: (statement) => statement.namedChildren[0] ?? null,
6124
- expression_statement: (statement) => {
6125
- const only = statement.namedChildren[0];
6126
- return only?.type === "call" ? only : null;
6127
- }
6128
- };
6129
- function raisedResponseOf(statement, options) {
6130
- const expression = ENDING_EXPRESSION[statement.type]?.(statement) ?? null;
6131
- if (expression === null) {
6132
- return null;
6133
- }
6134
- const thrownByCall = statement.type !== "raise_statement";
6135
- const call = expression.type === "call" ? expression : null;
6136
- const callee = calleeOf(expression, call);
6137
- const declared = declaredCallFor(callee, options);
6138
- if (declared === void 0 && thrownByCall) {
6139
- return null;
6140
- }
6141
- return {
6142
- statement,
6143
- thrownByCall,
6144
- terminal: terminalOf(statement, callee, call, declared, options.facts)
6145
- };
6146
- }
6147
- function returnedResponseStatus(statement, options) {
6148
- const returned = statement.namedChildren[0];
6149
- if (options.calls.length === 0 || returned === void 0 || returned === null || returned.type !== "call") {
6150
- return null;
6151
- }
6152
- const callee = field(returned, "function");
6153
- if (callee === null) {
6154
- return null;
6155
- }
6156
- const declared = declaredCallFor(callee, options);
6157
- return declared === void 0 ? null : statusOf(declared, returned, options.facts);
6158
- }
6159
- function calleeOf(expression, call) {
6160
- return call === null ? expression : field(call, "function") ?? expression;
6161
- }
6162
- function declaredCallFor(callee, options) {
6163
- const origin = originOf(callee, options.module);
6164
- if (origin === null) {
6165
- return void 0;
6166
- }
6167
- const qualified = `${origin.module}.${origin.name}`;
6168
- return options.calls.find((call) => call.callee === qualified);
6169
- }
6170
- function terminalOf(statement, callee, call, declared, facts) {
6171
- return {
6172
- kind: "throw",
6173
- statusCode: declared === void 0 ? null : statusOf(declared, call, facts),
6174
- body: null,
6175
- exceptionType: lastSegmentOf(declared?.callee ?? callee.text),
6176
- message: call === null ? null : writtenMessageOf(call),
6177
- ...declared === void 0 ? {} : { producesResponse: true },
6178
- component: null,
6179
- renderTree: null,
6180
- delegateTarget: null,
6181
- emitEvent: null,
6182
- location: rangeOf(statement)
6183
- };
6184
- }
6185
- function lastSegmentOf(dotted) {
6186
- const segment = dotted.split(".").pop();
6187
- return segment === void 0 || segment === "" ? null : segment;
6188
- }
6189
- function statusOf(declared, call, facts) {
6190
- const written = call === null ? null : statusArgumentOf(declared, call);
6191
- if (written === null) {
6192
- return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
6193
- }
6194
- const value = constantOf4(evaluatedValue(written, facts));
6195
- if (typeof value === "number") {
6196
- return { type: "literal", value };
6197
- }
6198
- return { type: "dynamic", sourceText: written.text };
6199
- }
6200
- function argumentsOf2(call) {
6201
- return (field(call, "arguments")?.namedChildren ?? []).filter(
6202
- (child) => child !== null
6203
- );
6204
- }
6205
- function statusArgumentOf(declared, call) {
6206
- const args = argumentsOf2(call);
6207
- const keyed = args.find(
6208
- (arg) => arg.type === "keyword_argument" && field(arg, "name")?.text === declared.statusKeyword
6209
- );
6210
- if (keyed !== void 0) {
6211
- return field(keyed, "value");
6212
- }
6213
- if (declared.statusArgument === void 0) {
6214
- return null;
6215
- }
6216
- const positional = args.filter((arg) => arg.type !== "keyword_argument");
6217
- return positional[declared.statusArgument] ?? null;
6218
- }
6219
- function writtenMessageOf(call) {
6220
- for (const arg of argumentsOf2(call)) {
6221
- const value = arg.type === "keyword_argument" ? field(arg, "value") : arg;
6222
- const literal = value === null ? null : stringLiteralValue(value);
6223
- if (literal !== null) {
6224
- return literal;
6225
- }
6226
- }
6227
- return null;
6228
- }
6229
-
6230
6692
  // src/paths/returnedShape.ts
6231
6693
  var MAX_DEPTH = 6;
6232
6694
  var SCALARS = {
@@ -6286,7 +6748,7 @@ function shapeOfReturned(node, depth = 0) {
6286
6748
  return { type: "unknown" };
6287
6749
  }
6288
6750
  function returnedBodyShape(statement) {
6289
- const returned = statement.namedChildren[0];
6751
+ const returned = statement.namedChild(0);
6290
6752
  if (returned == null) {
6291
6753
  return null;
6292
6754
  }
@@ -6310,7 +6772,7 @@ function isStdlibModule(node, scope, module) {
6310
6772
  const binding = resolveName(scope, node.text);
6311
6773
  return binding?.kind === "import" && binding.module === module && binding.relativeLevel === 0;
6312
6774
  }
6313
- function isBuiltin(node, scope, name) {
6775
+ function isBuiltin2(node, scope, name) {
6314
6776
  return node.type === "identifier" && node.text === name && resolveName(scope, name) === null;
6315
6777
  }
6316
6778
  function isStdlibMember(node, scope, module, name) {
@@ -6332,7 +6794,7 @@ var DEFERRED_BODY_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lamb
6332
6794
  function streamWriteEffects(root, module) {
6333
6795
  const found = [];
6334
6796
  const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
6335
- walkDescendants2(root, startScope, {
6797
+ walkDescendants2(root, startScope, children, {
6336
6798
  at: (node, scope) => {
6337
6799
  if (node.type !== "call") {
6338
6800
  return;
@@ -6358,7 +6820,7 @@ function streamWriteAt(call, scope) {
6358
6820
  }
6359
6821
  const written = callArguments(call);
6360
6822
  const positional = written.filter((argument) => argument.kind === "positional").map((argument) => argument.node);
6361
- if (callee.type === "identifier" && isBuiltin(callee, scope, "print")) {
6823
+ if (callee.type === "identifier" && isBuiltin2(callee, scope, "print")) {
6362
6824
  const file = written.find(
6363
6825
  (argument) => argument.kind === "keyword" && argument.name === "file"
6364
6826
  );
@@ -6419,7 +6881,7 @@ function jsonDumped(node, scope) {
6419
6881
  // src/discovery.ts
6420
6882
  function decoratedNodes(root) {
6421
6883
  const found = [];
6422
- walkDescendants3(root, null, {
6884
+ walkDescendants3(root, null, children, {
6423
6885
  at: (node) => {
6424
6886
  if (node.type !== "decorated_definition") {
6425
6887
  return;
@@ -6802,7 +7264,7 @@ function readRouterPrefix(pattern, classification, module, options, range) {
6802
7264
  }
6803
7265
  return absentReading;
6804
7266
  }
6805
- function readStatusCode(pattern, classification) {
7267
+ function readStatusCode(pattern, classification, options) {
6806
7268
  if (pattern.statusCodeKeyword === void 0) {
6807
7269
  return absentReading;
6808
7270
  }
@@ -6810,11 +7272,15 @@ function readStatusCode(pattern, classification) {
6810
7272
  if (arg === void 0) {
6811
7273
  return absentReading;
6812
7274
  }
6813
- if (arg.kind === "number") {
6814
- return writtenReading(arg.value, classification.range);
7275
+ const status = statusNumberOf(arg.node, {
7276
+ ...options,
7277
+ constants: pattern.statusCodeConstants ?? {}
7278
+ });
7279
+ if (status !== null) {
7280
+ return writtenReading(status, classification.range);
6815
7281
  }
6816
7282
  return unreadableReading(
6817
- "The status this route's decorator states is not a literal number, so the response claims no status",
7283
+ "The status this route's decorator states does not come to a number here, so the response claims no status",
6818
7284
  classification.range
6819
7285
  );
6820
7286
  }
@@ -6835,11 +7301,14 @@ function statusFromReturnedValue(node, facts) {
6835
7301
  return digits === null ? null : Number.parseInt(digits[1], 10);
6836
7302
  }
6837
7303
  function statusOfReturn(statement, facts) {
6838
- const returned = statement.namedChildren[0];
7304
+ const returned = statement.namedChild(0);
6839
7305
  if (returned?.type !== "expression_list") {
6840
7306
  return { kind: "none" };
6841
7307
  }
6842
- const status = statusFromReturnedValue(returned.namedChildren[1], facts);
7308
+ const status = statusFromReturnedValue(
7309
+ returned.namedChild(1) ?? void 0,
7310
+ facts
7311
+ );
6843
7312
  return status === null ? { kind: "unreadable" } : { kind: "status", value: status };
6844
7313
  }
6845
7314
  function returnStatusReading(statement, ctx) {
@@ -6859,6 +7328,22 @@ function returnStatusReading(statement, ctx) {
6859
7328
  function returnBranch(statement, ctx) {
6860
7329
  const range = rangeOf(statement);
6861
7330
  const writtenBody = returnedBodyShape(statement);
7331
+ return responseBranchAt(
7332
+ range,
7333
+ returnStatusReading(statement, ctx),
7334
+ writtenBody === null ? ctx.responseShape.reading : writtenReading(writtenBody, range),
7335
+ ctx
7336
+ );
7337
+ }
7338
+ function fallthroughBranch(definitionNode, ctx) {
7339
+ return responseBranchAt(
7340
+ rangeOf(definitionNode),
7341
+ ctx.pattern.statusFromReturnedTuple === true ? absentReading : ctx.declaredStatus.reading,
7342
+ ctx.responseShape.reading,
7343
+ ctx
7344
+ );
7345
+ }
7346
+ function responseBranchAt(range, status, body, ctx) {
6862
7347
  return {
6863
7348
  terminal: {
6864
7349
  kind: "response",
@@ -6875,12 +7360,10 @@ function returnBranch(statement, ctx) {
6875
7360
  location: range
6876
7361
  },
6877
7362
  statusCodeReading: {
6878
- reading: returnStatusReading(statement, ctx),
7363
+ reading: status,
6879
7364
  ...ctx.declaredStatus.libraryDefault !== void 0 ? { libraryDefault: ctx.declaredStatus.libraryDefault } : {}
6880
7365
  },
6881
- bodyShapeReading: {
6882
- reading: writtenBody === null ? ctx.responseShape.reading : writtenReading(writtenBody, range)
6883
- },
7366
+ bodyShapeReading: { reading: body },
6884
7367
  location: range
6885
7368
  };
6886
7369
  }
@@ -6901,11 +7384,12 @@ function branchesPerTerminal(options) {
6901
7384
  const raised = raisedResponses(body, {
6902
7385
  calls: options.responseStatusCalls,
6903
7386
  module: options.ctx.module,
6904
- facts: options.ctx.facts
7387
+ facts: options.ctx.facts,
7388
+ constants: options.ctx.pattern.statusCodeConstants ?? {}
6905
7389
  });
6906
7390
  const terminals = bodyTerminals(body, raised);
6907
7391
  const soleReturnWritesBody = terminals.length === 1 && terminals[0]?.type === "return" && returnedBodyShape(terminals[0].statement) !== null;
6908
- if (terminals.length < 2 && !soleReturnWritesBody) {
7392
+ if (terminals.length === 0) {
6909
7393
  return null;
6910
7394
  }
6911
7395
  const branches = enumerateBodyBranches({
@@ -6914,6 +7398,7 @@ function branchesPerTerminal(options) {
6914
7398
  raised,
6915
7399
  effects: options.effects,
6916
7400
  branchOf: (found) => terminalBranchOf(found, options.ctx),
7401
+ fallthrough: fallthroughBranch(options.definitionNode, options.ctx),
6917
7402
  facts: options.ctx.facts
6918
7403
  });
6919
7404
  return branches.length > 1 || soleReturnWritesBody ? branches : null;
@@ -6922,7 +7407,8 @@ function statusOfReturnIn(statement, pattern, options) {
6922
7407
  const constructed = returnedResponseStatus(statement, {
6923
7408
  calls: pattern.responseConstructors ?? [],
6924
7409
  module: options.module,
6925
- facts: options.facts
7410
+ facts: options.facts,
7411
+ constants: pattern.statusCodeConstants ?? {}
6926
7412
  });
6927
7413
  if (constructed !== null) {
6928
7414
  return constructed.type === "literal" ? { kind: "status", value: constructed.value } : { kind: "unreadable" };
@@ -7016,6 +7502,25 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7016
7502
  module.scopeFor,
7017
7503
  options.importedDefinition ?? null
7018
7504
  );
7505
+ const wrappers = wrappersAround(
7506
+ pattern,
7507
+ pack,
7508
+ classification,
7509
+ functionNode,
7510
+ module,
7511
+ options
7512
+ );
7513
+ const dependencyParameters = options.wrappers?.dependencyParameters(
7514
+ wrappers
7515
+ ) ?? { names: /* @__PURE__ */ new Set(), declaresSource: false };
7516
+ const declaredStatuses = declaredStatusesOf(
7517
+ pattern,
7518
+ pack,
7519
+ classification,
7520
+ functionNode,
7521
+ module,
7522
+ options
7523
+ );
7019
7524
  return readRoutePaths(
7020
7525
  pattern,
7021
7526
  readPathArgument(classification, options),
@@ -7028,6 +7533,8 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7028
7533
  pack,
7029
7534
  name: functionName,
7030
7535
  exportPath: [functionName],
7536
+ dependencyParameters,
7537
+ declaredStatuses,
7031
7538
  method: verb,
7032
7539
  routePath: entry.routePath,
7033
7540
  ...entry.mount !== void 0 ? { mount: entry.mount } : {},
@@ -7044,7 +7551,10 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7044
7551
  responseShape: readResponseModel(pattern, classification, module, ctx),
7045
7552
  statusCode: defaultedStatus(
7046
7553
  declaredOrReturnedStatus(
7047
- readStatusCode(pattern, classification),
7554
+ readStatusCode(pattern, classification, {
7555
+ module,
7556
+ facts: options.facts
7557
+ }),
7048
7558
  readReturnedStatus(pattern, functionNode, {
7049
7559
  module,
7050
7560
  facts: options.facts
@@ -7055,14 +7565,7 @@ function functionRouteUnits(pattern, pack, verb, classification, functionNode, m
7055
7565
  definitionsCtx: ctx,
7056
7566
  storage: options.storage,
7057
7567
  importedDefinition: options.importedDefinition,
7058
- wrappers: wrappersAround(
7059
- pattern,
7060
- pack,
7061
- classification,
7062
- functionNode,
7063
- module,
7064
- options
7065
- )
7568
+ wrappers
7066
7569
  },
7067
7570
  options
7068
7571
  )
@@ -7081,6 +7584,38 @@ function wrappersAround(pattern, pack, classification, definitionNode, module, o
7081
7584
  definitionNode
7082
7585
  });
7083
7586
  }
7587
+ function declaredStatusesOf(pattern, pack, classification, definitionNode, module, options) {
7588
+ const keyword = pattern.responsesKeyword;
7589
+ if (keyword === void 0) {
7590
+ return [];
7591
+ }
7592
+ const above = options.wrappers?.callsAbove({
7593
+ pack,
7594
+ pattern,
7595
+ file: options.absoluteFile ?? options.filePath,
7596
+ module,
7597
+ classification,
7598
+ definitionNode
7599
+ }) ?? [];
7600
+ const dictionaries = [
7601
+ classification.keywordArgs[keyword]?.node,
7602
+ ...above.map(
7603
+ (call) => readCallArguments(field(call, "arguments")).keywordArgs[keyword]?.node
7604
+ )
7605
+ ].filter((node) => node?.type === "dictionary");
7606
+ const statuses = dictionaries.flatMap(
7607
+ (dictionary) => children(dictionary).flatMap((pair) => {
7608
+ const key = pair?.type === "pair" ? field(pair, "key") : null;
7609
+ const status = key === null ? null : statusNumberOf(key, {
7610
+ module: moduleOf(key),
7611
+ facts: options.facts,
7612
+ constants: pattern.statusCodeConstants ?? {}
7613
+ });
7614
+ return status === null ? [] : [status];
7615
+ })
7616
+ );
7617
+ return [...new Set(statuses)].sort((a, b) => a - b);
7618
+ }
7084
7619
  function readResponseShape(declared, readAnnotation) {
7085
7620
  if (declared.kind === "written") {
7086
7621
  return { reading: declared, passedOver: [] };
@@ -7164,6 +7699,8 @@ function buildRouteUnit(options) {
7164
7699
  pathParamNames: template?.paramNames ?? null,
7165
7700
  requestBodyFromAnnotatedClass,
7166
7701
  injectedCallees,
7702
+ injectedTypes: new Set(pattern.injectedParameterTypes ?? []),
7703
+ module,
7167
7704
  sources: pattern.parameterSources ?? {},
7168
7705
  aliasKeyword: pattern.parameterAliasKeyword
7169
7706
  },
@@ -7220,6 +7757,22 @@ function buildRouteUnit(options) {
7220
7757
  };
7221
7758
  branches.push(withBodyEffects(bare, extra));
7222
7759
  }
7760
+ if (branches.length > 0) {
7761
+ branches.push(
7762
+ ...validationFailureBranches(
7763
+ pattern,
7764
+ readsTheRequest(
7765
+ parameters,
7766
+ template,
7767
+ options.dependencyParameters ?? {
7768
+ names: /* @__PURE__ */ new Set(),
7769
+ declaresSource: false
7770
+ }
7771
+ ),
7772
+ rangeOf(definitionNode)
7773
+ )
7774
+ );
7775
+ }
7223
7776
  const bodyNode = field(definitionNode, "body");
7224
7777
  return {
7225
7778
  identity: {
@@ -7251,9 +7804,51 @@ function buildRouteUnit(options) {
7251
7804
  ...collectedDefinitions(ctx) !== null ? { definitions: collectedDefinitions(ctx) } : {},
7252
7805
  ...options.mount !== void 0 ? { mount: options.mount } : {},
7253
7806
  ...options.wrappers !== void 0 && options.wrappers.length > 0 ? { wrappers: options.wrappers } : {},
7254
- ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling }
7807
+ ...pack.requestSpelling === void 0 ? {} : { requestSpelling: pack.requestSpelling },
7808
+ ...options.declaredStatuses !== void 0 && options.declaredStatuses.length > 0 ? { declaredStatuses: options.declaredStatuses } : {}
7255
7809
  };
7256
7810
  }
7811
+ function readsTheRequest(parameters, template, dependencies) {
7812
+ if (dependencies.declaresSource || parameters.some((parameter) => parameter.role !== null)) {
7813
+ return true;
7814
+ }
7815
+ return [...template?.paramNames ?? []].some(
7816
+ (name) => dependencies.names.has(name)
7817
+ );
7818
+ }
7819
+ function validationFailureBranches(pattern, readsRequest, range) {
7820
+ const status = pattern.validationFailureStatus;
7821
+ if (status === void 0 || !readsRequest) {
7822
+ return [];
7823
+ }
7824
+ return [
7825
+ {
7826
+ conditions: [
7827
+ {
7828
+ sourceText: "request input fails validation",
7829
+ structured: null,
7830
+ polarity: "positive",
7831
+ source: "earlyThrow"
7832
+ }
7833
+ ],
7834
+ terminal: {
7835
+ kind: "response",
7836
+ statusCode: { type: "literal", value: status },
7837
+ body: null,
7838
+ exceptionType: null,
7839
+ message: null,
7840
+ component: null,
7841
+ renderTree: null,
7842
+ delegateTarget: null,
7843
+ emitEvent: null,
7844
+ location: range
7845
+ },
7846
+ effects: [],
7847
+ location: range,
7848
+ isDefault: false
7849
+ }
7850
+ ];
7851
+ }
7257
7852
  function unreadRoleReadings(parameters, range) {
7258
7853
  if (!parameters.some((parameter) => parameter.role === null)) {
7259
7854
  return [];
@@ -7320,10 +7915,7 @@ function readParameters(definitionNode, reading, skipReceiverParam) {
7320
7915
  }
7321
7916
  const out = [];
7322
7917
  let position = 0;
7323
- for (const param of parametersNode.namedChildren) {
7324
- if (param === null) {
7325
- continue;
7326
- }
7918
+ for (const param of children(parametersNode)) {
7327
7919
  if (skipReceiverParam && position === 0 && isReceiverParam(param)) {
7328
7920
  position += 1;
7329
7921
  continue;
@@ -7347,7 +7939,7 @@ function readParameter(param, reading, position) {
7347
7939
  const { name, typeNode } = info;
7348
7940
  const shape = typeNode !== null ? annotationToShape(typeNode, reading.scope, reading.ctx) : null;
7349
7941
  const typeText = typeNode !== null ? typeNode.text : null;
7350
- if (isInjectedParameter(param, reading)) {
7942
+ if (isInjectedParameter(param, reading) || isInjectedType(typeNode, reading)) {
7351
7943
  return { name, position, role: null, typeText };
7352
7944
  }
7353
7945
  const declared = declaringCall(
@@ -7427,6 +8019,14 @@ function calleeName2(call) {
7427
8019
  function isInjectedParameter(param, reading) {
7428
8020
  return declaringCall(param, reading.injectedCallees, reading) !== null;
7429
8021
  }
8022
+ function isInjectedType(typeNode, reading) {
8023
+ if (typeNode === null || reading.injectedTypes.size === 0) {
8024
+ return false;
8025
+ }
8026
+ const written = typeNode.type === "type" ? typeNode.namedChild(0) ?? null : typeNode;
8027
+ const origin = written === null ? null : originOf(written, reading.module);
8028
+ return origin !== null && reading.injectedTypes.has(`${origin.module}.${origin.name}`);
8029
+ }
7430
8030
  function declaringCall(param, callees, reading) {
7431
8031
  if (callees.size === 0) {
7432
8032
  return null;
@@ -7660,6 +8260,7 @@ import {
7660
8260
  placeArgTargets as placeArgTargets2,
7661
8261
  placeCalleeParameters as placeCalleeParameters2,
7662
8262
  placeCalls as placeCalls2,
8263
+ placePossibleCalls as placePossibleCalls2,
7663
8264
  recordParameterGaps as recordParameterGaps2,
7664
8265
  summaryIdFromParts,
7665
8266
  unfollowedCallGap as unfollowedCallGap2
@@ -7671,10 +8272,12 @@ import {
7671
8272
  createCacheLayer,
7672
8273
  createTimer,
7673
8274
  effectToIR,
8275
+ extractionCacheDirFor,
7674
8276
  extractionConfigStamp,
7675
8277
  KeptParses,
7676
8278
  moduleInitStructure,
7677
8279
  noopTimer,
8280
+ ownersWhereWritten,
7678
8281
  runDigest,
7679
8282
  stampModuleImports,
7680
8283
  stampModules
@@ -7724,7 +8327,7 @@ var DEFERRED_BODY_TYPES3 = /* @__PURE__ */ new Set(["function_definition", "lamb
7724
8327
  function exitSites(root, module) {
7725
8328
  const found = [];
7726
8329
  const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
7727
- walkDescendants4(root, startScope, {
8330
+ walkDescendants4(root, startScope, children, {
7728
8331
  at: (node, scope) => {
7729
8332
  const site = exitSiteAt(node, scope);
7730
8333
  if (site !== null) {
@@ -7741,7 +8344,7 @@ function exitSites(root, module) {
7741
8344
  return found;
7742
8345
  }
7743
8346
  function exitSiteAt(statement, scope) {
7744
- const expression = statement.namedChildren[0] ?? null;
8347
+ const expression = statement.namedChild(0) ?? null;
7745
8348
  if (expression === null) {
7746
8349
  return null;
7747
8350
  }
@@ -7755,12 +8358,12 @@ function exitSiteAt(statement, scope) {
7755
8358
  return endsTheProcess(callee, scope) ? { statement, code, raised: false } : null;
7756
8359
  }
7757
8360
  if (statement.type === "raise_statement") {
7758
- return isBuiltin(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
8361
+ return isBuiltin2(callee, scope, "SystemExit") ? { statement, code, raised: true } : null;
7759
8362
  }
7760
8363
  return null;
7761
8364
  }
7762
8365
  function endsTheProcess(callee, scope) {
7763
- return isStdlibMember(callee, scope, "sys", "exit") || isBuiltin(callee, scope, "exit") || isBuiltin(callee, scope, "quit");
8366
+ return isStdlibMember(callee, scope, "sys", "exit") || isBuiltin2(callee, scope, "exit") || isBuiltin2(callee, scope, "quit");
7764
8367
  }
7765
8368
  function firstArgument(call) {
7766
8369
  return callArguments(call).find(
@@ -7921,7 +8524,9 @@ import {
7921
8524
  placeArgTargets,
7922
8525
  placeCalleeParameters,
7923
8526
  placeCalls,
8527
+ placePossibleCalls,
7924
8528
  recordParameterGaps,
8529
+ StopList,
7925
8530
  TargetPlacements,
7926
8531
  unfollowedCallGap,
7927
8532
  worthRecording
@@ -7944,7 +8549,7 @@ function argparseFlagReads(definition, module, parameterNames2) {
7944
8549
  const parsers = /* @__PURE__ */ new Map();
7945
8550
  const handedTo = /* @__PURE__ */ new Map();
7946
8551
  const startScope = module.scopeFor.get(definition.id) ?? module.moduleScope;
7947
- walkDescendants5(definition, startScope, {
8552
+ walkDescendants5(definition, startScope, children, {
7948
8553
  at: (node, scope) => {
7949
8554
  noteParser(node, scope, parsers);
7950
8555
  noteArgument(node, parsers);
@@ -8020,7 +8625,9 @@ function methodCalledOn(call) {
8020
8625
  import {
8021
8626
  calleeOutcomeOf,
8022
8627
  calleeOutcomes,
8628
+ dispatchOutcomes,
8023
8629
  noteKeyRead as noteKeyRead3,
8630
+ RECEIVER_STORE_NAME as RECEIVER_STORE_NAME2,
8024
8631
  writtenSourcesOf
8025
8632
  } from "@suss/resolution";
8026
8633
  var STOP_FOR = {
@@ -8051,11 +8658,77 @@ function resolveCallee2(call, site, ctx, read) {
8051
8658
  return spelling;
8052
8659
  }
8053
8660
  const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key);
8661
+ if (selfCallInMixin(call, outcome, site, ctx)) {
8662
+ return { kind: "stopped", reason: "selfCallInMixin" };
8663
+ }
8054
8664
  return asCallee(outcome, site.owner, ctx);
8055
8665
  }
8666
+ function selfCallInMixin(call, outcome, site, ctx) {
8667
+ if (outcome.kind !== "callerSupplied" || !outcome.key.startsWith(`${site.owner}#`)) {
8668
+ return false;
8669
+ }
8670
+ const callee = readThrough(field(call, "function"));
8671
+ const receiver = callee?.type === "attribute" ? field(callee, "object") : null;
8672
+ const name = callee === null ? null : field(callee, "attribute")?.text;
8673
+ if (receiver?.type !== "identifier" || name === void 0 || name === null) {
8674
+ return false;
8675
+ }
8676
+ if (outcome.key !== `${site.owner}#${receiver.text}`) {
8677
+ return false;
8678
+ }
8679
+ return ctx.facts.lookup("instanceOf", 0, outcome.key).some((row) => {
8680
+ const classKey = String(row[1]);
8681
+ return ctx.facts.has("plainClass", [classKey]) && !instancesHaveAttribute(ctx.facts, classKey, name);
8682
+ });
8683
+ }
8684
+ var FALLBACK_ATTRIBUTE = "__getattr__";
8685
+ function instancesHaveAttribute(facts, classKey, name) {
8686
+ return facts.lookup("holdsProperty", 0, classKey).some((member) => {
8687
+ if (String(member[1]) === FALLBACK_ATTRIBUTE) {
8688
+ return true;
8689
+ }
8690
+ return facts.lookup("storesProperty", 0, String(member[2])).some(
8691
+ (store) => String(store[1]) === name && store[3] === RECEIVER_STORE_NAME2
8692
+ );
8693
+ });
8694
+ }
8056
8695
  function namedOutcomes(keys, ctx) {
8057
8696
  return calleeOutcomes(ctx.facts, [...new Set(keys)]);
8058
8697
  }
8698
+ function dispatchedCalls(calls, ctx) {
8699
+ const byKey = new Map(
8700
+ calls.map(({ call, site }) => [nodeId(site.file.file, call), call])
8701
+ );
8702
+ const found = /* @__PURE__ */ new Map();
8703
+ for (const [key, outcome] of dispatchOutcomes(ctx.facts, [...byKey.keys()])) {
8704
+ const call = byKey.get(key);
8705
+ if (call === void 0) {
8706
+ continue;
8707
+ }
8708
+ const targets = /* @__PURE__ */ new Map();
8709
+ for (const fn of outcome.functions) {
8710
+ for (const target of reachedThrough(fn, outcome.through, ctx)) {
8711
+ targets.set(nodeId(target.file.file, target.node), target);
8712
+ }
8713
+ }
8714
+ found.set(call.id, {
8715
+ targets: [...targets.values()],
8716
+ certain: outcome.certain && outcome.functions.every((fn) => !isLambda(fn, ctx)),
8717
+ unresolved: outcome.unresolved
8718
+ });
8719
+ }
8720
+ return found;
8721
+ }
8722
+ function reachedThrough(key, through, ctx) {
8723
+ const keys = isLambda(key, ctx) ? through.get(key) ?? [] : [key];
8724
+ return keys.flatMap((one) => {
8725
+ const target = isLambda(one, ctx) ? null : functionAt(one, ctx);
8726
+ return target === null ? [] : [target];
8727
+ });
8728
+ }
8729
+ function isLambda(key, ctx) {
8730
+ return ctx.definitions.get(key)?.type === "lambda";
8731
+ }
8059
8732
  function functionNamed(nameKey2, ctx, read) {
8060
8733
  if (assignedElsewhere(ctx.facts, nameKey2)) {
8061
8734
  return null;
@@ -8274,6 +8947,7 @@ function reachedFunctions(seeds, options) {
8274
8947
  const targetsByKey = /* @__PURE__ */ new Map();
8275
8948
  const stopsByKey = /* @__PURE__ */ new Map();
8276
8949
  const argTargetsByKey = /* @__PURE__ */ new Map();
8950
+ const possibleTargetsByKey = /* @__PURE__ */ new Map();
8277
8951
  const parameterCallsByKey = /* @__PURE__ */ new Map();
8278
8952
  const callsByKey = /* @__PURE__ */ new Map();
8279
8953
  const passedPositions = /* @__PURE__ */ new Set();
@@ -8336,6 +9010,7 @@ function reachedFunctions(seeds, options) {
8336
9010
  }
8337
9011
  targetsByKey.set(key, scan.targets);
8338
9012
  argTargetsByKey.set(key, scan.argTargets);
9013
+ possibleTargetsByKey.set(key, scan.possibleTargets);
8339
9014
  if (scan.parameterCalls.length > 0) {
8340
9015
  parameterCallsByKey.set(key, scan.parameterCalls);
8341
9016
  }
@@ -8386,6 +9061,7 @@ function reachedFunctions(seeds, options) {
8386
9061
  }
8387
9062
  placeCalls(summary2, targetsByKey.get(key));
8388
9063
  placeArgTargets(summary2, argTargetsByKey.get(key));
9064
+ placePossibleCalls(summary2, possibleTargetsByKey.get(key));
8389
9065
  placeCalleeParameters(summary2, parameterCallsByKey.get(key));
8390
9066
  return summary2;
8391
9067
  };
@@ -8409,6 +9085,7 @@ function reachedFunctions(seeds, options) {
8409
9085
  targetsByKey,
8410
9086
  stopsByKey,
8411
9087
  argTargetsByKey,
9088
+ possibleTargetsByKey,
8412
9089
  parameterCallsByKey,
8413
9090
  callsByKey,
8414
9091
  passedPositions,
@@ -8432,24 +9109,42 @@ function chargeOf(charges, key) {
8432
9109
  }
8433
9110
  return charge;
8434
9111
  }
9112
+ function dispatchedAmong(written, spellings, ctx) {
9113
+ return dispatchedCalls(
9114
+ written.filter(({ call, site }) => {
9115
+ if (!ENTRY_CALLEE_TYPES.has(field(call, "function")?.type ?? "")) {
9116
+ return false;
9117
+ }
9118
+ const outcome = resolveCallee2(call, site, ctx, spellings);
9119
+ return outcome.kind === "stopped" && outcome.reason !== "callerSupplied";
9120
+ }),
9121
+ ctx
9122
+ );
9123
+ }
9124
+ var ENTRY_CALLEE_TYPES = /* @__PURE__ */ new Set(["subscript", "call", "identifier"]);
8435
9125
  function batchedReads(round, ctx) {
8436
9126
  const bodies = round.flatMap(
8437
9127
  (entry) => "source" in entry ? [{ key: entry.key, source: entry.source, ...bodyOf(entry.source) }] : []
8438
9128
  );
8439
- const spellings = calleeSpellings(
8440
- bodies.flatMap((body) => body.written),
8441
- ctx
8442
- );
9129
+ const written = bodies.flatMap((body) => body.written);
9130
+ const spellings = calleeSpellings(written, ctx);
8443
9131
  const passed = namedOutcomes(
8444
9132
  bodies.flatMap(
8445
- ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
9133
+ ({ source, written: written2 }) => written2.flatMap(({ call }) => passedNameKeys(source.file, call))
8446
9134
  ),
8447
9135
  ctx
8448
9136
  );
9137
+ const dispatched = dispatchedAmong(written, spellings, ctx);
8449
9138
  return new Map(
8450
9139
  bodies.map((body) => [
8451
9140
  body.key,
8452
- { body: body.body, written: body.written, spellings, passed }
9141
+ {
9142
+ body: body.body,
9143
+ written: body.written,
9144
+ spellings,
9145
+ passed,
9146
+ dispatched
9147
+ }
8453
9148
  ])
8454
9149
  );
8455
9150
  }
@@ -8480,13 +9175,15 @@ function scanOnce(source, key, ctx, options, read, charges) {
8480
9175
  }
8481
9176
  function ownReads(source, ctx) {
8482
9177
  const body = bodyOf(source);
9178
+ const spellings = calleeSpellings(body.written, ctx);
8483
9179
  return {
8484
9180
  ...body,
8485
- spellings: calleeSpellings(body.written, ctx),
9181
+ spellings,
8486
9182
  passed: namedOutcomes(
8487
9183
  body.written.flatMap(({ call }) => passedNameKeys(source.file, call)),
8488
9184
  ctx
8489
- )
9185
+ ),
9186
+ dispatched: dispatchedAmong(body.written, spellings, ctx)
8490
9187
  };
8491
9188
  }
8492
9189
  function storedScan(scan) {
@@ -8502,6 +9199,10 @@ function storedScan(scan) {
8502
9199
  callee,
8503
9200
  [...byPosition]
8504
9201
  ]),
9202
+ possibleTargets: [...scan.possibleTargets].map(([callee, places]) => [
9203
+ callee,
9204
+ [...places]
9205
+ ]),
8505
9206
  parameterCalls: [...scan.parameterCalls],
8506
9207
  passedPositions: [...scan.passedPositions]
8507
9208
  };
@@ -8517,6 +9218,7 @@ function scanFromStored(stored, functionFor) {
8517
9218
  new Map(byPosition)
8518
9219
  ])
8519
9220
  ),
9221
+ possibleTargets: new Map(stored.possibleTargets),
8520
9222
  parameterCalls: stored.parameterCalls,
8521
9223
  passedPositions: new Set(stored.passedPositions)
8522
9224
  };
@@ -8545,6 +9247,7 @@ var EMPTY_SCAN = {
8545
9247
  stops: [],
8546
9248
  targets: /* @__PURE__ */ new Map(),
8547
9249
  argTargets: /* @__PURE__ */ new Map(),
9250
+ possibleTargets: /* @__PURE__ */ new Map(),
8548
9251
  parameterCalls: [],
8549
9252
  passedPositions: /* @__PURE__ */ new Set()
8550
9253
  };
@@ -8602,14 +9305,14 @@ function passedNameKeys(file, call) {
8602
9305
  if (args === null) {
8603
9306
  return [];
8604
9307
  }
8605
- return args.namedChildren.flatMap((arg) => {
9308
+ return children(args).flatMap((arg) => {
8606
9309
  const key = passedNameKeyOf(file, arg);
8607
9310
  return key === null ? [] : [key];
8608
9311
  });
8609
9312
  }
8610
9313
  function scanBody(source, ctx, recognized, read, implied) {
8611
9314
  const followed = [];
8612
- const stops = [];
9315
+ const stops = new StopList();
8613
9316
  const placements = new TargetPlacements();
8614
9317
  const parameterCalls = [];
8615
9318
  const passedPositions = /* @__PURE__ */ new Set();
@@ -8621,12 +9324,25 @@ function scanBody(source, ctx, recognized, read, implied) {
8621
9324
  return EMPTY_SCAN;
8622
9325
  }
8623
9326
  const ownParameters = callParameterNames(node, source.exportPath.length > 1);
9327
+ const follow = (target) => {
9328
+ const key = keyOf2(target);
9329
+ if (!seen.has(key)) {
9330
+ seen.add(key);
9331
+ followed.push(target);
9332
+ }
9333
+ };
8624
9334
  const recordPassedArgs = (call, callee, calleeKey2) => {
8625
9335
  const args = field(call, "arguments");
8626
9336
  if (args === null) {
8627
9337
  return;
8628
9338
  }
8629
- args.namedChildren.forEach((arg, position) => {
9339
+ children(args).forEach((arg, position) => {
9340
+ if (arg.type === "lambda") {
9341
+ if (calleeKey2 !== null) {
9342
+ passedPositions.add(`${calleeKey2}#${position}`);
9343
+ }
9344
+ return;
9345
+ }
8630
9346
  const nameKey2 = passedNameKeyOf(file, arg);
8631
9347
  if (nameKey2 === null) {
8632
9348
  return;
@@ -8635,11 +9351,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8635
9351
  if (resolved === null) {
8636
9352
  return;
8637
9353
  }
8638
- const key = keyOf2(resolved);
8639
- if (!seen.has(key)) {
8640
- seen.add(key);
8641
- followed.push(resolved);
8642
- }
9354
+ follow(resolved);
8643
9355
  placements.placeArg(callee, position, {
8644
9356
  file: resolved.file.displayPath,
8645
9357
  span: spanOf(resolved.node)
@@ -8649,9 +9361,30 @@ function scanBody(source, ctx, recognized, read, implied) {
8649
9361
  }
8650
9362
  });
8651
9363
  };
9364
+ const recordPossible = (call, callee, dispatched) => {
9365
+ dispatched.targets.forEach(follow);
9366
+ placements.placePossible(
9367
+ callee,
9368
+ dispatched.targets.map((target) => ({
9369
+ file: target.file.displayPath,
9370
+ span: spanOf(target.node)
9371
+ }))
9372
+ );
9373
+ recordPassedArgs(call, callee, null);
9374
+ if (dispatched.unresolved > 0 && !recognized.has(call.id)) {
9375
+ stops.add({ callee, reason: "unsettledValue" });
9376
+ }
9377
+ };
8652
9378
  const record = (call, site) => {
8653
9379
  const callee = calleeText(call);
8654
- const outcome = resolveCallee2(call, site, ctx, spellings);
9380
+ const settled = resolveCallee2(call, site, ctx, spellings);
9381
+ const dispatched = settled.kind === "stopped" ? read.dispatched.get(call.id) : void 0;
9382
+ const [only] = dispatched?.targets ?? [];
9383
+ if (dispatched !== void 0 && only !== void 0 && !dispatched.certain) {
9384
+ recordPossible(call, callee, dispatched);
9385
+ return;
9386
+ }
9387
+ const outcome = only === void 0 ? settled : { kind: "followed", target: only };
8655
9388
  placeCallee(placements, callee, outcome, call, file);
8656
9389
  recordPassedArgs(
8657
9390
  call,
@@ -8659,10 +9392,12 @@ function scanBody(source, ctx, recognized, read, implied) {
8659
9392
  outcome.kind === "followed" ? keyOf2(outcome.target) : null
8660
9393
  );
8661
9394
  if (outcome.kind === "stopped") {
8662
- const stopKey = `${outcome.reason}:${callee}`;
8663
- if (!seen.has(stopKey) && !recognized.has(call.id) && worthRecording(outcome.reason)) {
8664
- seen.add(stopKey);
8665
- stops.push({ callee, reason: outcome.reason });
9395
+ if (!recognized.has(call.id) && worthRecording(outcome.reason)) {
9396
+ stops.add({
9397
+ callee,
9398
+ reason: outcome.reason,
9399
+ ...resultUsedAsValue(call) ? { usedAsValue: true } : {}
9400
+ });
8666
9401
  }
8667
9402
  if (outcome.reason === "callerSupplied" && !parameterCallsSeen.has(callee)) {
8668
9403
  const parameterIndex = ownParameters.indexOf(callee);
@@ -8673,11 +9408,7 @@ function scanBody(source, ctx, recognized, read, implied) {
8673
9408
  }
8674
9409
  return;
8675
9410
  }
8676
- const key = keyOf2(outcome.target);
8677
- if (!seen.has(key)) {
8678
- seen.add(key);
8679
- followed.push(outcome.target);
8680
- }
9411
+ follow(outcome.target);
8681
9412
  };
8682
9413
  for (const { call, site } of written) {
8683
9414
  record(call, site);
@@ -8687,24 +9418,21 @@ function scanBody(source, ctx, recognized, read, implied) {
8687
9418
  file: target.file.displayPath,
8688
9419
  span: spanOf(target.node)
8689
9420
  });
8690
- const key = keyOf2(target);
8691
- if (!seen.has(key)) {
8692
- seen.add(key);
8693
- followed.push(target);
8694
- }
9421
+ follow(target);
8695
9422
  }
8696
9423
  return {
8697
9424
  followed,
8698
- stops,
9425
+ stops: stops.stops,
8699
9426
  targets: placements.targets,
8700
9427
  argTargets: placements.argTargets,
9428
+ possibleTargets: placements.possibleTargets,
8701
9429
  parameterCalls,
8702
9430
  passedPositions
8703
9431
  };
8704
9432
  }
8705
9433
  function callsWritten(file, body, outer, owner, limit) {
8706
9434
  const found = [];
8707
- walkDescendants6(body, outer, {
9435
+ walkDescendants6(body, outer, children, {
8708
9436
  at: (child, scope) => {
8709
9437
  if (child.type === "call") {
8710
9438
  found.push({ call: child, site: { file, scope, owner } });
@@ -8730,7 +9458,7 @@ function identifiersUnder(node, found = []) {
8730
9458
  found.push(node.text);
8731
9459
  return found;
8732
9460
  }
8733
- for (const child of node.namedChildren) {
9461
+ for (const child of children(node)) {
8734
9462
  if (child !== null && child.type !== "attribute" && child.type !== "subscript") {
8735
9463
  identifiersUnder(child, found);
8736
9464
  }
@@ -8831,10 +9559,7 @@ function positionalParameters(node, isMethod2) {
8831
9559
  }
8832
9560
  const out = [];
8833
9561
  let position = 0;
8834
- for (const param of parameters.namedChildren) {
8835
- if (param === null) {
8836
- continue;
8837
- }
9562
+ for (const param of children(parameters)) {
8838
9563
  const name = parameterName2(param);
8839
9564
  if (name === null) {
8840
9565
  continue;
@@ -8854,10 +9579,7 @@ function callParameterNames(node, isMethod2) {
8854
9579
  return [];
8855
9580
  }
8856
9581
  const names = [];
8857
- for (const param of parameters.namedChildren) {
8858
- if (param === null) {
8859
- continue;
8860
- }
9582
+ for (const param of children(parameters)) {
8861
9583
  const name = parameterName2(param);
8862
9584
  if (name === null) {
8863
9585
  continue;
@@ -8954,7 +9676,7 @@ function exportsOf(file, node, exportName) {
8954
9676
  if (node.type !== "class_definition" || body === null) {
8955
9677
  return [];
8956
9678
  }
8957
- return body.namedChildren.flatMap((child) => {
9679
+ return children(body).flatMap((child) => {
8958
9680
  if (child === null) {
8959
9681
  return [];
8960
9682
  }
@@ -9027,6 +9749,10 @@ var PackPositions = class {
9027
9749
  const [packAt, patternAt] = id.split("/").map(Number);
9028
9750
  return this.packs[packAt ?? -1]?.discovery[patternAt ?? -1];
9029
9751
  }
9752
+ packOf(id) {
9753
+ const [packAt] = id.split("/").map(Number);
9754
+ return this.packs[packAt ?? -1];
9755
+ }
9030
9756
  formAt(at) {
9031
9757
  const pack = this.packs[at[0]];
9032
9758
  const pattern = pack?.discovery[at[1]];
@@ -9077,6 +9803,11 @@ function watchRouterIndex(index, db, positions, fileOfModule) {
9077
9803
  found
9078
9804
  );
9079
9805
  return found;
9806
+ },
9807
+ // The wrapper index notes what it reads through the mounts, in terms
9808
+ // it can look up again.
9809
+ mountsAbove(pattern, constructionKey) {
9810
+ return index.mountsAbove(pattern, constructionKey);
9080
9811
  }
9081
9812
  };
9082
9813
  }
@@ -9181,9 +9912,33 @@ function buildRouterIndex(files, packs, resolverOptions) {
9181
9912
  };
9182
9913
  }
9183
9914
  return NOT_ROUTER;
9915
+ },
9916
+ mountsAbove(pattern, constructionKey) {
9917
+ const index = byPattern.get(pattern);
9918
+ return index === void 0 ? null : hopsAbove(index, constructionKey);
9184
9919
  }
9185
9920
  };
9186
9921
  }
9922
+ function hopsAbove(index, key) {
9923
+ const hops = [];
9924
+ const seen = /* @__PURE__ */ new Set();
9925
+ let current = key;
9926
+ while (current !== null) {
9927
+ const construction = index.byValueKey.get(current);
9928
+ if (construction === void 0 || seen.has(current)) {
9929
+ return null;
9930
+ }
9931
+ seen.add(current);
9932
+ const states = index.mounts.get(construction) ?? [];
9933
+ const only = states.length === 1 ? states[0] : void 0;
9934
+ if (only?.kind !== "mounted" || only.written === void 0) {
9935
+ return null;
9936
+ }
9937
+ hops.push({ ...only.written, parentKey: only.parentValueKey });
9938
+ current = only.parentValueKey;
9939
+ }
9940
+ return hops;
9941
+ }
9187
9942
  function composedPrefixOf(index, construction, routerPath) {
9188
9943
  const ownPrefix = composedOwnPrefix(construction.prefix, index.composition);
9189
9944
  if (ownPrefix.kind === "abstain") {
@@ -9466,8 +10221,8 @@ function constructionStatement(stmt, module, importModule) {
9466
10221
  if (stmt.type !== "expression_statement") {
9467
10222
  return null;
9468
10223
  }
9469
- const assignment2 = stmt.namedChildren.find(
9470
- (child) => child !== null && child.type === "assignment"
10224
+ const assignment2 = children(stmt).find(
10225
+ (child) => child.type === "assignment"
9471
10226
  );
9472
10227
  if (assignment2 === void 0) {
9473
10228
  return null;
@@ -9541,7 +10296,7 @@ function collectReturnedConstructions(bound, importModule, composition, index) {
9541
10296
  if (inner.type !== "return_statement") {
9542
10297
  continue;
9543
10298
  }
9544
- const returned = inner.namedChildren[0];
10299
+ const returned = inner.namedChild(0);
9545
10300
  if (returned?.type !== "call") {
9546
10301
  continue;
9547
10302
  }
@@ -9721,7 +10476,7 @@ function sequenceElementNames(node) {
9721
10476
  return null;
9722
10477
  }
9723
10478
  const names = [];
9724
- for (const child of node.namedChildren) {
10479
+ for (const child of children(node)) {
9725
10480
  if (child?.type !== "identifier") {
9726
10481
  return null;
9727
10482
  }
@@ -9934,8 +10689,9 @@ function recordMountStatement(stmt, position, scan) {
9934
10689
  scan.index.facts
9935
10690
  );
9936
10691
  const mounted = args[0] ?? (scan.composition.routerKeyword === void 0 ? void 0 : keywordArgs[scan.composition.routerKeyword]);
10692
+ const recorded = state.kind === "mounted" ? { ...state, written: { call: mountCall.call, file: scan.bound.file } } : state;
9937
10693
  for (const target of mountedConstructions(mounted, position, scan)) {
9938
- recordMount(scan.index, target, state);
10694
+ recordMount(scan.index, target, recorded);
9939
10695
  }
9940
10696
  }
9941
10697
  function mountStateOf(includerConstructorName, includer, keywordArgs, objectPrefix, composition, site, facts) {
@@ -11047,6 +11803,8 @@ import { absentReading as absentReading2, walkDescendants as walkDescendants7 }
11047
11803
  import { noteKeyRead as noteKeyRead6, noteLookup as noteLookup3 } from "@suss/resolution";
11048
11804
  var EVERY_ROUTE = "wrapEvery ";
11049
11805
  var OWN_ROUTES = "wrapOwn ";
11806
+ var MOUNTED = "wrapMounted ";
11807
+ var MOUNTED_SEPARATOR = "\0";
11050
11808
  function describeRegistered(found) {
11051
11809
  return (found ?? []).map(
11052
11810
  ({ reference }) => `${reference.file}:${reference.name}:${reference.onThrow === true}`
@@ -11084,8 +11842,15 @@ var PythonWrapperIndex = class {
11084
11842
  /** By construction key: what a router or a blueprint registered. */
11085
11843
  ownRoutes = /* @__PURE__ */ new Map();
11086
11844
  unitsByKey = /* @__PURE__ */ new Map();
11845
+ /** By pack name: the app constructions its routes run under. */
11846
+ appCalls = /* @__PURE__ */ new Map();
11847
+ /** By wrapper reference: the parameters a dependency function declares, and its file. */
11848
+ dependencyParameterTable = /* @__PURE__ */ new Map();
11087
11849
  unitsByFile = /* @__PURE__ */ new Map();
11088
- /** Told about every registration, so a cache can repeat the ones a file's discovery made. */
11850
+ /**
11851
+ * Told about every registration, a pack's form or a project decorator's
11852
+ * closure, so a cache can repeat the ones a file's discovery made.
11853
+ */
11089
11854
  onRegistered = null;
11090
11855
  /**
11091
11856
  * The same lookup `wrappersFor` noted under `id`, made again over this
@@ -11102,6 +11867,12 @@ var PythonWrapperIndex = class {
11102
11867
  this.ownRoutes.get(id.slice(OWN_ROUTES.length))
11103
11868
  );
11104
11869
  }
11870
+ if (id.startsWith(MOUNTED)) {
11871
+ const [packName, position, key] = id.slice(MOUNTED.length).split(MOUNTED_SEPARATOR);
11872
+ const pack = this.options.packs.find((one) => one.name === packName);
11873
+ const pattern = pack?.discovery[Number(position)];
11874
+ return pack === void 0 || pattern === void 0 || key === void 0 ? describeRegistered([]) : describeRegistered(this.registeredAbove(pack, pattern, key));
11875
+ }
11105
11876
  return null;
11106
11877
  }
11107
11878
  noted(kind, key, found) {
@@ -11143,14 +11914,65 @@ var PythonWrapperIndex = class {
11143
11914
  inFile.push(unit);
11144
11915
  this.unitsByFile.set(target.file.file, inFile);
11145
11916
  }
11146
- return {
11147
- reference: {
11148
- file: target.file.displayPath,
11149
- name: target.name,
11150
- ...isThrowForm(declared.form) ? { onThrow: true } : {}
11151
- },
11152
- form: declared.form
11917
+ const reference = {
11918
+ file: target.file.displayPath,
11919
+ name: target.name,
11920
+ ...isThrowForm(declared.form) ? { onThrow: true } : {}
11153
11921
  };
11922
+ if (declared.form.type === "dependency") {
11923
+ this.dependencyParameterTable.set(referenceKey(reference), {
11924
+ file: target.file.file,
11925
+ names: parameterNamesOf(target.node),
11926
+ declaresSource: declaresSourcedParameter(
11927
+ target.node,
11928
+ Object.keys(declared.pattern.parameterSources ?? {})
11929
+ )
11930
+ });
11931
+ }
11932
+ return { reference, form: declared.form };
11933
+ }
11934
+ /**
11935
+ * The unit for the closure a project's decorator hands back, built once
11936
+ * however many routes the decorator is written on. Its name is what
11937
+ * calling the decorator writes, since the decorator may have a summary
11938
+ * of its own under its name.
11939
+ */
11940
+ registeredClosure(target, closure) {
11941
+ this.onRegistered?.(target, closure);
11942
+ const span = spanOf(target.node);
11943
+ const key = `${target.file.file}:${span.start}-${span.end}`;
11944
+ if (!this.unitsByKey.has(key)) {
11945
+ const unit = closureUnit(target, closure, this.options);
11946
+ this.unitsByKey.set(key, unit);
11947
+ const inFile = this.unitsByFile.get(target.file.file) ?? [];
11948
+ inFile.push(unit);
11949
+ this.unitsByFile.set(target.file.file, inFile);
11950
+ }
11951
+ return { file: target.file.displayPath, name: target.name };
11952
+ }
11953
+ /**
11954
+ * The parameters the dependencies among these wrappers declare, and
11955
+ * whether one is declared through a parameter source such as `Query()`.
11956
+ * The library fills those from the request and validates them, the same
11957
+ * as parameters the route declares.
11958
+ */
11959
+ dependencyParameters(references) {
11960
+ const names = /* @__PURE__ */ new Set();
11961
+ let declaresSource = false;
11962
+ for (const reference of references) {
11963
+ const found = this.dependencyParameterTable.get(referenceKey(reference));
11964
+ if (found === void 0) {
11965
+ continue;
11966
+ }
11967
+ if (this.options.facts !== void 0) {
11968
+ noteKeyRead6(this.options.facts, found.file);
11969
+ }
11970
+ for (const name of found.names) {
11971
+ names.add(name);
11972
+ }
11973
+ declaresSource ||= found.declaresSource;
11974
+ }
11975
+ return { names, declaresSource };
11154
11976
  }
11155
11977
  wrappersFor(query) {
11156
11978
  const file = this.filesByPath.get(query.file);
@@ -11159,16 +11981,22 @@ var PythonWrapperIndex = class {
11159
11981
  pattern: query.pattern,
11160
11982
  form
11161
11983
  }));
11162
- if (file === void 0 || forms.length === 0) {
11984
+ if (file === void 0) {
11163
11985
  return [];
11164
11986
  }
11987
+ const decorators = this.decoratorLayers(query, file);
11988
+ if (forms.length === 0) {
11989
+ return decorators;
11990
+ }
11991
+ const own = this.ownRoutesOf(query);
11165
11992
  const found = [
11166
11993
  ...this.noted(
11167
11994
  EVERY_ROUTE,
11168
11995
  query.pack.name,
11169
11996
  this.everyRoute.get(query.pack.name)
11170
11997
  ),
11171
- ...this.ownRoutesOf(query)
11998
+ ...own === null ? [] : this.mountedAbove(query, own.key),
11999
+ ...own?.registered ?? []
11172
12000
  ];
11173
12001
  for (const declared of forms) {
11174
12002
  if (declared.form.type !== "dependency") {
@@ -11197,13 +12025,128 @@ var PythonWrapperIndex = class {
11197
12025
  seen.add(key);
11198
12026
  ordered.push(wrapper.reference);
11199
12027
  }
11200
- return ordered;
12028
+ return [
12029
+ ...ordered.filter((one) => one.onThrow !== true),
12030
+ ...decorators,
12031
+ ...ordered.filter((one) => one.onThrow === true)
12032
+ ];
12033
+ }
12034
+ /**
12035
+ * The decorators written on the route's function below the one that
12036
+ * registers it, outermost first. Each wraps the function the library
12037
+ * calls. None has a summary: a library's has no body in the run, and a
12038
+ * project's own is not composed into the route yet, so a project
12039
+ * decorator goes by what calling it writes, `require_admin(fn)`, which
12040
+ * no summary is named. The decorators the packs read, such as the
12041
+ * route's own and the namespace's `@ns.doc`, are left out.
12042
+ */
12043
+ decoratorLayers(query, file) {
12044
+ const decorated = query.definitionNode.parent;
12045
+ if (decorated === null || decorated.type !== "decorated_definition") {
12046
+ return [];
12047
+ }
12048
+ const written = stripDecorators(decorated).decorators;
12049
+ const routeAt = written.findIndex(
12050
+ (decorator) => sameRange(rangeOf(decorator), query.classification.range)
12051
+ );
12052
+ return written.slice(routeAt + 1).flatMap((decorator) => this.decoratorLayer(decorator, query, file));
12053
+ }
12054
+ decoratorLayer(decorator, query, file) {
12055
+ const facts = this.options.facts;
12056
+ const direct = classifyDecorator(decorator, query.module, facts);
12057
+ const written = decoratorCallee(decorator);
12058
+ if (written === null || direct.module !== null && readByAPack(direct.module, this.options) || direct.objectName !== null && direct.objectName === query.classification.objectName || unwrapDecorator(decorator, facts) !== null) {
12059
+ return [];
12060
+ }
12061
+ const project = written.type === "identifier" ? this.functionCalled(file.file, written.text) : null;
12062
+ if (project !== null) {
12063
+ if (facts !== void 0) {
12064
+ noteKeyRead6(facts, project.file.file);
12065
+ }
12066
+ return [this.projectDecoratorLayer(project, query)];
12067
+ }
12068
+ return [{ file: direct.module ?? file.displayPath, name: written.text }];
12069
+ }
12070
+ /**
12071
+ * The layer a project's own decorator puts around the route. When it
12072
+ * hands back a closure written inside it that returns what calling the
12073
+ * function it was given returns, the closure is summarized and
12074
+ * composed into the route. Any other decorator, including one whose
12075
+ * closure keeps going after that call, goes by the same name with no
12076
+ * summary behind it.
12077
+ */
12078
+ projectDecoratorLayer(decorator, query) {
12079
+ const continuation = parameterNamesOf(decorator.node)[0] ?? "";
12080
+ const name = `${decorator.name}(${continuation})`;
12081
+ const closure = closureReturnedBy(decorator.node);
12082
+ const handsOn = closure === null || continuation === "" ? [] : callsThrough(closure, continuation);
12083
+ if (closure === null || handsOn.length === 0 || !handsOn.every((statement) => returnsCallTo(statement, continuation))) {
12084
+ return { file: decorator.file.displayPath, name };
12085
+ }
12086
+ return this.registeredClosure(
12087
+ { file: decorator.file, node: closure, name, exportPath: [] },
12088
+ { pack: query.pack, pattern: query.pattern, continuation }
12089
+ );
12090
+ }
12091
+ /**
12092
+ * What the mounts above the route's router register on the way down:
12093
+ * the dependencies an `include_router` call lists, and those of each
12094
+ * router it is mounted onto, outermost first, the order they run in.
12095
+ */
12096
+ mountedAbove(query, key) {
12097
+ const position = query.pack.discovery.indexOf(query.pattern);
12098
+ return [
12099
+ ...this.noted(
12100
+ MOUNTED,
12101
+ [query.pack.name, position, key].join(MOUNTED_SEPARATOR),
12102
+ this.registeredAbove(query.pack, query.pattern, key)
12103
+ )
12104
+ ];
12105
+ }
12106
+ registeredAbove(pack, pattern, key) {
12107
+ const hops = this.options.routers?.mountsAbove(pattern, key) ?? null;
12108
+ if (hops === null) {
12109
+ return [];
12110
+ }
12111
+ const forms = (pattern.wrappers ?? []).flatMap(
12112
+ (form) => form.type === "dependency" ? [{ pack, pattern, form }] : []
12113
+ );
12114
+ const found = [];
12115
+ for (const hop of [...hops].reverse()) {
12116
+ found.push(
12117
+ ...hop.parentKey === null ? [] : this.ownRoutes.get(hop.parentKey) ?? []
12118
+ );
12119
+ const { keywordArgs } = readCallArguments(field(hop.call, "arguments"));
12120
+ for (const declared of forms) {
12121
+ const listed = keywordArgs[declared.form.keyword];
12122
+ const names = listed === void 0 ? [] : dependencyNamesIn(listed.node, declared.form);
12123
+ for (const name of names) {
12124
+ const target = this.functionCalled(hop.file, name);
12125
+ if (target !== null) {
12126
+ found.push(this.registered(target, declared));
12127
+ }
12128
+ }
12129
+ }
12130
+ }
12131
+ return found;
11201
12132
  }
11202
12133
  ownRoutesOf(query) {
12134
+ const match = this.ownRouterOf(query);
12135
+ return match === null ? null : {
12136
+ key: match.key,
12137
+ registered: this.noted(
12138
+ OWN_ROUTES,
12139
+ match.key,
12140
+ this.ownRoutes.get(match.key)
12141
+ )
12142
+ };
12143
+ }
12144
+ /** The construction call of the router a route is decorated on, when a registrar reads it. */
12145
+ ownRouterOf(query) {
11203
12146
  const site = this.objectSiteOf(query);
11204
12147
  const bound = site === null ? void 0 : this.filesByPath.get(site.file);
11205
12148
  if (site === null || bound === void 0) {
11206
- return [];
12149
+ return null;
11207
12150
  }
11208
12151
  if (this.options.facts !== void 0) {
11209
12152
  noteKeyRead6(this.options.facts, site.file);
@@ -11217,12 +12160,46 @@ var PythonWrapperIndex = class {
11217
12160
  { pack: query.pack, pattern: query.pattern, form }
11218
12161
  );
11219
12162
  if (match !== null && match.registrar.covers === "ownRoutes") {
11220
- return [
11221
- ...this.noted(OWN_ROUTES, match.key, this.ownRoutes.get(match.key))
11222
- ];
12163
+ return match;
11223
12164
  }
11224
12165
  }
11225
- return [];
12166
+ return null;
12167
+ }
12168
+ /** Records the app a pack's routes run under, for the keywords written on its construction. */
12169
+ recordApp(pack, call, file) {
12170
+ const calls = this.appCalls.get(pack) ?? [];
12171
+ if (!calls.some((one) => one.call === call)) {
12172
+ calls.push({ call, file });
12173
+ }
12174
+ this.appCalls.set(pack, calls);
12175
+ }
12176
+ /**
12177
+ * Every call that registers something for a route on the way to it:
12178
+ * the app's construction, the mount calls and the routers they mount
12179
+ * onto, and the route's own router. A caller reads a keyword off each.
12180
+ */
12181
+ callsAbove(query) {
12182
+ const own = this.ownRouterOf(query);
12183
+ const hops = own === null ? [] : this.options.routers?.mountsAbove(query.pattern, own.key) ?? [];
12184
+ const parents = hops.flatMap((hop) => {
12185
+ const node = hop.parentKey === null ? null : nodeOfResolutionKey(hop.parentKey, this.options.facts);
12186
+ return node === null ? [] : [node];
12187
+ });
12188
+ const files = [
12189
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.file),
12190
+ ...hops.map((hop) => hop.file)
12191
+ ];
12192
+ if (this.options.facts !== void 0) {
12193
+ for (const file of files) {
12194
+ noteKeyRead6(this.options.facts, file);
12195
+ }
12196
+ }
12197
+ return [
12198
+ ...(this.appCalls.get(query.pack.name) ?? []).map((one) => one.call),
12199
+ ...hops.map((hop) => hop.call),
12200
+ ...parents,
12201
+ ...own === null ? [] : [own.call]
12202
+ ];
11226
12203
  }
11227
12204
  /**
11228
12205
  * Where the decorator's object was built: the route's own file, the file
@@ -11267,6 +12244,38 @@ var PythonWrapperIndex = class {
11267
12244
  return this.unitsByFile.get(file) ?? [];
11268
12245
  }
11269
12246
  };
12247
+ function referenceKey(reference) {
12248
+ return `${reference.file}::${reference.name}`;
12249
+ }
12250
+ function readByAPack(module, options) {
12251
+ return options.packs.some(
12252
+ (pack) => pack.discovery.some((pattern) => pattern.importModule.includes(module))
12253
+ );
12254
+ }
12255
+ function decoratorCallee(decorator) {
12256
+ const written = decorator.namedChild(0);
12257
+ if (written === null) {
12258
+ return null;
12259
+ }
12260
+ return written.type === "call" ? field(written, "function") : written;
12261
+ }
12262
+ function sameRange(a, b) {
12263
+ return a.start === b.start && a.end === b.end;
12264
+ }
12265
+ function parameterNamesOf(definition) {
12266
+ return children(field(definition, "parameters")).flatMap((parameter) => {
12267
+ const named = parameterNameAndType(parameter);
12268
+ return named === null ? [] : [named.name];
12269
+ });
12270
+ }
12271
+ function declaresSourcedParameter(definition, sources) {
12272
+ return children(field(definition, "parameters")).some((parameter) => {
12273
+ const value = field(parameter, "value");
12274
+ const callee = value?.type === "call" ? field(value, "function") : null;
12275
+ const name = callee?.type === "attribute" ? field(callee, "attribute")?.text : callee?.text;
12276
+ return name !== void 0 && sources.includes(name);
12277
+ });
12278
+ }
11270
12279
  function isThrowForm(form) {
11271
12280
  return form.type === "decoratedWrapper" && form.throwParam !== void 0;
11272
12281
  }
@@ -11313,8 +12322,8 @@ function registerFrom(file, declared, index) {
11313
12322
  }
11314
12323
  function registerConstructorDependencies(file, declared, form, index) {
11315
12324
  const scope = file.module.moduleScope;
11316
- for (const stmt of file.root.namedChildren) {
11317
- const assignment2 = stmt?.type === "expression_statement" ? stmt.namedChildren.find((child) => child?.type === "assignment") : void 0;
12325
+ for (const stmt of children(file.root)) {
12326
+ const assignment2 = stmt?.type === "expression_statement" ? children(stmt).find((child) => child?.type === "assignment") : void 0;
11318
12327
  const left = assignment2 === void 0 || assignment2 === null ? null : field(assignment2, "left");
11319
12328
  if (left?.type !== "identifier") {
11320
12329
  continue;
@@ -11329,6 +12338,9 @@ function registerConstructorDependencies(file, declared, form, index) {
11329
12338
  if (match === null) {
11330
12339
  continue;
11331
12340
  }
12341
+ if (match.registrar.covers === "everyRoute") {
12342
+ index.recordApp(declared.pack.name, match.call, file.file);
12343
+ }
11332
12344
  const { keywordArgs } = readCallArguments(field(match.call, "arguments"));
11333
12345
  const listed = keywordArgs[form.keyword];
11334
12346
  if (listed === void 0) {
@@ -11346,7 +12358,7 @@ function coverageOf(match, pack) {
11346
12358
  return match.registrar.covers === "everyRoute" ? { kind: "everyRoute", pack: pack.name } : { kind: "ownRoutes", key: match.key };
11347
12359
  }
11348
12360
  function registerDecorated(file, declared, form, index) {
11349
- walkDescendants7(file.root, file.module.moduleScope, {
12361
+ walkDescendants7(file.root, file.module.moduleScope, children, {
11350
12362
  at: (node, scope) => {
11351
12363
  if (node.type !== "decorated_definition") {
11352
12364
  return;
@@ -11382,7 +12394,7 @@ function registerDecorated(file, declared, form, index) {
11382
12394
  });
11383
12395
  }
11384
12396
  function dependencyNamesIn(node, form) {
11385
- const calls = node.type === "list" ? node.namedChildren : [node];
12397
+ const calls = node.type === "list" ? children(node) : [node];
11386
12398
  const names = [];
11387
12399
  for (const call of calls) {
11388
12400
  const name = call === null ? null : dependencyNameOf(call, form);
@@ -11401,7 +12413,7 @@ function dependencyNameOf(node, form) {
11401
12413
  if (calleeName3 === null || !form.callees.includes(calleeName3)) {
11402
12414
  return null;
11403
12415
  }
11404
- const first = field(node, "arguments")?.namedChildren[0];
12416
+ const first = field(node, "arguments")?.namedChild(0);
11405
12417
  return first?.type === "identifier" ? first.text : null;
11406
12418
  }
11407
12419
  function decoratorDependencies(classification, form) {
@@ -11419,7 +12431,7 @@ function parameterDependencies(definitionNode, form) {
11419
12431
  if (direct !== null) {
11420
12432
  return direct;
11421
12433
  }
11422
- for (const child of node.namedChildren) {
12434
+ for (const child of children(node)) {
11423
12435
  const found = findIn(child, depth + 1);
11424
12436
  if (found !== null) {
11425
12437
  return found;
@@ -11427,10 +12439,7 @@ function parameterDependencies(definitionNode, form) {
11427
12439
  }
11428
12440
  return null;
11429
12441
  };
11430
- for (const param of field(definitionNode, "parameters")?.namedChildren ?? []) {
11431
- if (param === null) {
11432
- continue;
11433
- }
12442
+ for (const param of children(field(definitionNode, "parameters"))) {
11434
12443
  const inDefault = field(param, "value");
11435
12444
  const found = inDefault === null ? findIn(field(param, "type"), 0) : dependencyNameOf(inDefault, form);
11436
12445
  if (found !== null) {
@@ -11440,15 +12449,42 @@ function parameterDependencies(definitionNode, form) {
11440
12449
  return names;
11441
12450
  }
11442
12451
  function wrapperUnit(target, declared, options) {
12452
+ return unitAround(
12453
+ target,
12454
+ declared.pattern,
12455
+ {
12456
+ continuations: continuationStatements(target.node, declared.form),
12457
+ continuationName: null,
12458
+ form: declared.form,
12459
+ nameKind: "binding"
12460
+ },
12461
+ options
12462
+ );
12463
+ }
12464
+ function closureUnit(target, closure, options) {
12465
+ return unitAround(
12466
+ target,
12467
+ closure.pattern,
12468
+ {
12469
+ continuations: callsThrough(target.node, closure.continuation),
12470
+ continuationName: closure.continuation,
12471
+ form: null,
12472
+ nameKind: "label"
12473
+ },
12474
+ options
12475
+ );
12476
+ }
12477
+ function unitAround(target, pattern, reading, options) {
11443
12478
  const { file, node, name, exportPath } = target;
11444
12479
  const body = field(node, "body");
11445
12480
  const range = rangeOf(node);
11446
12481
  const raised = raisedResponses(body, {
11447
- calls: declared.pattern.responseStatusCalls ?? [],
12482
+ calls: pattern.responseStatusCalls ?? [],
11448
12483
  module: file.module,
11449
- facts: options.facts
12484
+ facts: options.facts,
12485
+ constants: pattern.statusCodeConstants ?? {}
11450
12486
  });
11451
- const continuations = continuationStatements(node, declared.form);
12487
+ const continuations = reading.continuations;
11452
12488
  const terminals = bodyTerminals(body, raised, continuations);
11453
12489
  const effects = invocationEffects(node, options.facts);
11454
12490
  const extra = recognizedBodyEffects(
@@ -11457,7 +12493,7 @@ function wrapperUnit(target, declared, options) {
11457
12493
  options.storageFor(file),
11458
12494
  options.facts
11459
12495
  );
11460
- const delegate = (at) => ({
12496
+ const delegate = (at, to = null) => ({
11461
12497
  terminal: {
11462
12498
  kind: "delegate",
11463
12499
  statusCode: null,
@@ -11466,18 +12502,18 @@ function wrapperUnit(target, declared, options) {
11466
12502
  message: null,
11467
12503
  component: null,
11468
12504
  renderTree: null,
11469
- delegateTarget: null,
12505
+ delegateTarget: to,
11470
12506
  emitEvent: null,
11471
12507
  location: rangeOf(at)
11472
12508
  },
11473
12509
  location: rangeOf(at)
11474
12510
  });
11475
12511
  const respond = (statement) => returnBranch(statement, {
11476
- pattern: declared.pattern,
12512
+ pattern,
11477
12513
  responseShape: { reading: absentReading2 },
11478
12514
  declaredStatus: {
11479
12515
  reading: absentReading2,
11480
- ...declared.pattern.defaultStatusCode !== void 0 ? { libraryDefault: declared.pattern.defaultStatusCode } : {}
12516
+ ...pattern.defaultStatusCode !== void 0 ? { libraryDefault: pattern.defaultStatusCode } : {}
11481
12517
  },
11482
12518
  module: file.module,
11483
12519
  facts: options.facts
@@ -11489,7 +12525,10 @@ function wrapperUnit(target, declared, options) {
11489
12525
  location: rangeOf(terminal.statement)
11490
12526
  };
11491
12527
  }
11492
- if (terminal.type === "continuation" || returnHandsOn(terminal.statement, declared.form)) {
12528
+ if (terminal.type === "continuation") {
12529
+ return delegate(terminal.statement, reading.continuationName);
12530
+ }
12531
+ if (reading.form !== null && returnHandsOn(terminal.statement, reading.form)) {
11493
12532
  return delegate(terminal.statement);
11494
12533
  }
11495
12534
  return respond(terminal.statement);
@@ -11506,7 +12545,7 @@ function wrapperUnit(target, declared, options) {
11506
12545
  return {
11507
12546
  identity: {
11508
12547
  name,
11509
- nameKind: "binding",
12548
+ nameKind: reading.nameKind,
11510
12549
  kind: "middleware",
11511
12550
  file: file.displayPath,
11512
12551
  range,
@@ -11530,7 +12569,7 @@ function returnHandsOn(statement, form) {
11530
12569
  return false;
11531
12570
  }
11532
12571
  if (form.returnedValueResponds === true) {
11533
- const returned = statement.namedChildren[0];
12572
+ const returned = statement.namedChild(0);
11534
12573
  return returned === void 0 || returned === null || returned.type === "none";
11535
12574
  }
11536
12575
  return true;
@@ -11539,28 +12578,48 @@ function continuationStatements(node, form) {
11539
12578
  if (form.type !== "decoratedWrapper" || form.continuationParam === void 0) {
11540
12579
  return [];
11541
12580
  }
11542
- const params = (field(node, "parameters")?.namedChildren ?? []).filter(
11543
- (param2) => param2 !== null
11544
- );
12581
+ const params = children(field(node, "parameters"));
11545
12582
  const param = params[form.continuationParam];
11546
12583
  const continuation = param === void 0 ? null : field(param, "name")?.text ?? (param.type === "identifier" ? param.text : null);
11547
- if (continuation === null) {
11548
- return [];
11549
- }
12584
+ return continuation === null ? [] : callsThrough(node, continuation);
12585
+ }
12586
+ function callsThrough(node, callee) {
11550
12587
  const body = field(node, "body");
11551
12588
  if (body === null) {
11552
12589
  return [];
11553
12590
  }
11554
12591
  const found = [];
11555
12592
  for (const call of bodyCalls(body)) {
11556
- const callee = field(call, "function");
11557
- const statement = callee?.type === "identifier" && callee.text === continuation ? enclosingStatement(call, body) : null;
12593
+ const called = field(call, "function");
12594
+ const statement = called?.type === "identifier" && called.text === callee ? enclosingStatement(call, body) : null;
11558
12595
  if (statement !== null) {
11559
12596
  found.push(statement);
11560
12597
  }
11561
12598
  }
11562
12599
  return found;
11563
12600
  }
12601
+ function returnsCallTo(statement, callee) {
12602
+ if (statement.type !== "return_statement") {
12603
+ return false;
12604
+ }
12605
+ const value = statement.namedChild(0);
12606
+ const call = value?.type === "await" ? value.namedChild(0) : value;
12607
+ return call?.type === "call" && field(call, "function")?.text === callee;
12608
+ }
12609
+ function closureReturnedBy(decorator) {
12610
+ const statements = children(field(decorator, "body"));
12611
+ const returned = statements.find((statement) => statement.type === "return_statement")?.namedChild(0);
12612
+ if (returned?.type !== "identifier") {
12613
+ return null;
12614
+ }
12615
+ for (const statement of statements) {
12616
+ const { definition } = stripDecorators(statement);
12617
+ if (definition.type === "function_definition" && field(definition, "name")?.text === returned.text) {
12618
+ return definition;
12619
+ }
12620
+ }
12621
+ return null;
12622
+ }
11564
12623
 
11565
12624
  // src/project.ts
11566
12625
  function keptPythonParses() {
@@ -11655,7 +12714,7 @@ async function extractPythonProject(options) {
11655
12714
  const { roots, unreadManifests } = rootsOfRun(options);
11656
12715
  const timer = options.onTiming !== void 0 ? createTimer() : noopTimer();
11657
12716
  const cacheDir = adapterStamp.declineWhenRunFromSource(
11658
- options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? path5.join(options.projectRoot, ".suss", "cache") : null)
12717
+ options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? extractionCacheDirFor(options.projectRoot) : null)
11659
12718
  );
11660
12719
  const layer = createCacheLayer(cacheDir);
11661
12720
  const packsDigest = `${adapterStamp.packsDigest(
@@ -11678,11 +12737,14 @@ async function extractPythonProject(options) {
11678
12737
  "cache.lookup",
11679
12738
  () => layer.lookup(input)
11680
12739
  );
12740
+ const composed = (stored) => composeWrappers(stored, {
12741
+ gapHandling: options.gapHandling ?? "permissive"
12742
+ });
11681
12743
  if (lookup.kind === "hit") {
11682
12744
  options.onCacheDiagnostic?.(lookup.diagnostic);
11683
12745
  options.onTiming?.(timer.report());
11684
12746
  return {
11685
- summaries: lookup.summaries,
12747
+ summaries: composed(lookup.summaries),
11686
12748
  facts: new Database2(),
11687
12749
  roots,
11688
12750
  unreadManifests
@@ -11697,7 +12759,7 @@ async function extractPythonProject(options) {
11697
12759
  options.onCacheDiagnostic?.({ kind: "hit" });
11698
12760
  options.onTiming?.(timer.report());
11699
12761
  return {
11700
- summaries: plan.allSummaries(),
12762
+ summaries: composed(plan.allSummaries()),
11701
12763
  facts: new Database2(),
11702
12764
  roots,
11703
12765
  unreadManifests
@@ -11842,7 +12904,8 @@ async function runPython(options, roots, cache, previous) {
11842
12904
  roots,
11843
12905
  facts: needsValues ? db : void 0,
11844
12906
  definitions,
11845
- storageFor
12907
+ storageFor,
12908
+ routers: plainRouterIndex
11846
12909
  })
11847
12910
  );
11848
12911
  const importedDefinition2 = importedDefinitionLookup(db, bound);
@@ -11878,15 +12941,10 @@ async function runPython(options, roots, cache, previous) {
11878
12941
  });
11879
12942
  let registering = null;
11880
12943
  if (ledger !== null) {
11881
- wrapperIndex.onRegistered = (target, declared) => {
11882
- const form = positions.positionOf(declared);
11883
- if (registering !== null && form !== void 0) {
11884
- registering.push({
11885
- key: nodeId(target.file.file, target.node),
11886
- name: target.name,
11887
- exportPath: target.exportPath,
11888
- form
11889
- });
12944
+ wrapperIndex.onRegistered = (target, how) => {
12945
+ const stored = storedRegistration(target, how, positions);
12946
+ if (registering !== null && stored !== null) {
12947
+ registering.push(stored);
11890
12948
  }
11891
12949
  };
11892
12950
  }
@@ -11899,12 +12957,11 @@ async function runPython(options, roots, cache, previous) {
11899
12957
  if (record !== void 0) {
11900
12958
  discoveries.set(file, record);
11901
12959
  for (const registration of record.registrations) {
11902
- const declared = positions.formAt(registration.form);
11903
- const target = storedFunction(registration, definitions, filesByPath);
11904
- if (declared === void 0 || target === null) {
11905
- throw new ReplayFailed(registration.key);
11906
- }
11907
- wrapperIndex.registered(target, declared);
12960
+ registerAgain(registration, {
12961
+ positions,
12962
+ wrapperIndex,
12963
+ target: storedFunction(registration, definitions, filesByPath)
12964
+ });
11908
12965
  }
11909
12966
  continue;
11910
12967
  }
@@ -12073,6 +13130,7 @@ async function runPython(options, roots, cache, previous) {
12073
13130
  }
12074
13131
  placeCalls2(summary, reached.targetsByKey.get(key));
12075
13132
  placeArgTargets2(summary, reached.argTargetsByKey.get(key));
13133
+ placePossibleCalls2(summary, reached.possibleTargetsByKey.get(key));
12076
13134
  placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
12077
13135
  };
12078
13136
  for (const [key, owners] of summariesBySeed) {
@@ -12162,16 +13220,20 @@ async function runPython(options, roots, cache, previous) {
12162
13220
  markExitCodeFunctions(summaries, exits, db, options.workspaceRoot);
12163
13221
  return composeWrappers(summaries, { gapHandling });
12164
13222
  });
13223
+ const walkedFiles = new Map(
13224
+ bound.map(({ file, displayPath }) => [displayPath, file])
13225
+ );
12165
13226
  await timer.timeAsync("cache.write", async () => {
12166
- if (cacheDir === null || composed.length === 0) {
13227
+ if (cacheDir === null || summaries.length === 0) {
12167
13228
  return;
12168
13229
  }
12169
13230
  try {
12170
13231
  await cache.layer.write(
12171
13232
  cache.input,
12172
- composed,
13233
+ summaries,
12173
13234
  log === null || ledger === null || overBudget ? void 0 : pythonAttribution({
12174
- composed,
13235
+ summaries,
13236
+ walkedFileOf: (locationFile) => walkedFiles.get(locationFile),
12175
13237
  discoveries,
12176
13238
  charges,
12177
13239
  unitDrafts,
@@ -12226,6 +13288,39 @@ async function runPython(options, roots, cache, previous) {
12226
13288
  options.onTiming?.(timer.report());
12227
13289
  return { summaries: composed, facts: db };
12228
13290
  }
13291
+ function storedRegistration(target, how, positions) {
13292
+ const at = {
13293
+ key: nodeId(target.file.file, target.node),
13294
+ name: target.name,
13295
+ exportPath: target.exportPath
13296
+ };
13297
+ if ("continuation" in how) {
13298
+ const pattern = positions.idOf(how.pattern);
13299
+ return pattern === void 0 ? null : { ...at, pattern, continuation: how.continuation };
13300
+ }
13301
+ const form = positions.positionOf(how);
13302
+ return form === void 0 ? null : { ...at, form };
13303
+ }
13304
+ function registerAgain(registration, context) {
13305
+ const { positions, wrapperIndex, target } = context;
13306
+ const how = howRegistered(registration, positions);
13307
+ if (target === null || how === null) {
13308
+ throw new ReplayFailed(registration.key);
13309
+ }
13310
+ if ("continuation" in how) {
13311
+ wrapperIndex.registeredClosure(target, how);
13312
+ return;
13313
+ }
13314
+ wrapperIndex.registered(target, how);
13315
+ }
13316
+ function howRegistered(registration, positions) {
13317
+ if (!("continuation" in registration)) {
13318
+ return positions.formAt(registration.form) ?? null;
13319
+ }
13320
+ const pack = positions.packOf(registration.pattern);
13321
+ const pattern = positions.patternOf(registration.pattern);
13322
+ return pack === void 0 || pattern === void 0 ? null : { pack, pattern, continuation: registration.continuation };
13323
+ }
12229
13324
  function pythonAttribution(args) {
12230
13325
  const walkedByFile = walkRecordsByFile({
12231
13326
  scans: args.reached.scans,
@@ -12254,7 +13349,10 @@ function pythonAttribution(args) {
12254
13349
  }
12255
13350
  };
12256
13351
  });
12257
- return { roots, owners: args.composed.map(() => []) };
13352
+ return {
13353
+ roots,
13354
+ owners: ownersWhereWritten(args.summaries, args.walkedFileOf)
13355
+ };
12258
13356
  }
12259
13357
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([
12260
13358
  "__pycache__",
@@ -12285,7 +13383,7 @@ function indexDefinitions(into, file, node) {
12285
13383
  if (isFunction(node)) {
12286
13384
  into.set(nodeId(file, node), node);
12287
13385
  }
12288
- for (const child of node.namedChildren) {
13386
+ for (const child of children(node)) {
12289
13387
  if (child !== null) {
12290
13388
  indexDefinitions(into, file, child);
12291
13389
  }
@@ -12551,7 +13649,7 @@ function importSitesIn(root) {
12551
13649
  found.push({ module: entry.module, name: entry.name, line });
12552
13650
  }
12553
13651
  }
12554
- for (const child of node.namedChildren) {
13652
+ for (const child of children(node)) {
12555
13653
  if (child !== null) {
12556
13654
  visit(child);
12557
13655
  }