@suss/adapter-python 0.35.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1050,6 +1050,8 @@ var CONVERTING_BUILTINS = /* @__PURE__ */ new Set([
1050
1050
  ]);
1051
1051
  var ATTRIBUTE_GETTER = "getattr";
1052
1052
  var POSITION_COUNTER = "enumerate";
1053
+ var CLASS_METHOD_DECORATOR = "classmethod";
1054
+ var STATIC_METHOD_DECORATOR = "staticmethod";
1053
1055
 
1054
1056
  // src/facts/values.ts
1055
1057
  function nodeId(filePath, node) {
@@ -1729,6 +1731,26 @@ function yieldedValue(node) {
1729
1731
  }
1730
1732
  return children(node).find((child) => child?.type !== "comment") ?? null;
1731
1733
  }
1734
+ function emitReceiver(emitter, fn, paramKey, classKey) {
1735
+ const form = receiverFormOf(fn);
1736
+ if (form === import_resolution3.SELF_AS_CLASS_NAME) {
1737
+ add(emitter, "binds", paramKey, classKey);
1738
+ } else {
1739
+ add(emitter, "instanceOf", paramKey, classKey);
1740
+ }
1741
+ if (form !== null) {
1742
+ add(emitter, "selfReceiver", nodeId(emitter.filePath, fn), paramKey, form);
1743
+ }
1744
+ }
1745
+ function receiverFormOf(fn) {
1746
+ const decorators = fn.parent?.type === "decorated_definition" ? stripDecorators(fn.parent).decorators.map(
1747
+ (decorator) => decorator.text.slice(1).trim()
1748
+ ) : [];
1749
+ if (decorators.includes(STATIC_METHOD_DECORATOR)) {
1750
+ return null;
1751
+ }
1752
+ return decorators.includes(CLASS_METHOD_DECORATOR) ? import_resolution3.SELF_AS_CLASS_NAME : import_resolution3.SELF_AS_INSTANCE_NAME;
1753
+ }
1732
1754
  function emitFunctionFacts(emitter, fn, classKey) {
1733
1755
  const funcKey = nodeId(emitter.filePath, fn);
1734
1756
  add(emitter, "func", funcKey);
@@ -1752,7 +1774,7 @@ function emitFunctionFacts(emitter, fn, classKey) {
1752
1774
  }
1753
1775
  if (classKey !== void 0 && position === -1) {
1754
1776
  receiver = { classKey, name: paramName.text };
1755
- add(emitter, "instanceOf", paramKey, classKey);
1777
+ emitReceiver(emitter, fn, paramKey, classKey);
1756
1778
  }
1757
1779
  add(emitter, "paramNamed", funcKey, paramName.text, paramKey);
1758
1780
  const fallback2 = field(param, "value");
@@ -4075,6 +4097,7 @@ var Lowerer = class {
4075
4097
  ) ?? null;
4076
4098
  return {
4077
4099
  kind: "try",
4100
+ lines: rangeOf(node),
4078
4101
  tryBody: this.lowerBlock(field(node, "body")),
4079
4102
  catchBody: excepts.length > 0 ? excepts.flatMap((clause) => this.lowerBlock(childBlock(clause))) : null,
4080
4103
  finallyBody: finallyClause === null ? null : this.lowerBlock(childBlock(finallyClause)),
@@ -4115,12 +4138,19 @@ var Lowerer = class {
4115
4138
  }
4116
4139
  if (node.type === "while_statement" || node.type === "for_statement") {
4117
4140
  const header = node.type === "while_statement" ? field(node, "condition") : field(node, "right");
4141
+ const body = field(node, "body");
4118
4142
  return this.attach(
4119
4143
  {
4120
4144
  kind: "loop",
4121
4145
  condition: handleOf(header),
4122
- body: this.lowerBlock(field(node, "body")),
4123
- exitKind: exitKindOf(node, this.thrown)
4146
+ body: this.lowerBlock(body),
4147
+ exitKind: exitKindOf(node, this.thrown),
4148
+ // An `else` clause runs once the loop is done, so it is no part
4149
+ // of what repeats.
4150
+ lines: {
4151
+ start: rangeOf(node).start,
4152
+ end: rangeOf(body ?? node).end
4153
+ }
4124
4154
  },
4125
4155
  [header]
4126
4156
  );
@@ -4152,6 +4182,9 @@ function lowerPythonBody(body, terminals, thrown = new NodeSet()) {
4152
4182
  // src/paths/effects.ts
4153
4183
  var NESTED_DEFINITION_TYPES = /* @__PURE__ */ new Set(["function_definition"]);
4154
4184
  function literalArgOf(node, facts) {
4185
+ if (node.type === "none") {
4186
+ return { kind: "null" };
4187
+ }
4155
4188
  const value = evaluatedValue(node, facts);
4156
4189
  const text2 = (0, import_values7.literalOf)(value);
4157
4190
  if (text2 !== null) {
@@ -4229,9 +4262,6 @@ function argsOf(call, facts) {
4229
4262
  function calleeText(call) {
4230
4263
  return field(call, "function")?.text ?? call.text;
4231
4264
  }
4232
- function invocationEffects(definitionNode, facts) {
4233
- return invocationSites(definitionNode, facts).map((site) => site.effect);
4234
- }
4235
4265
  function invocationSites(definitionNode, facts) {
4236
4266
  const body = field(definitionNode, "body");
4237
4267
  if (body === null) {
@@ -4287,33 +4317,26 @@ function gatingInputs(body, gatingOf, facts) {
4287
4317
  );
4288
4318
  return operands.length === 0 ? void 0 : guardInputs(operands, { facts, unit });
4289
4319
  }
4320
+ function sitesIn(body, nodes) {
4321
+ return (0, import_extractor2.statementSites)(nodes, {
4322
+ statementOf: (node) => enclosingStatement(node, body),
4323
+ lower: (statements) => lowerPythonBody(body, statements),
4324
+ firstLine: (node) => rangeOf(node).start
4325
+ });
4326
+ }
4327
+ function effectSitesIn(body, nodes) {
4328
+ if (body === null || nodes.length === 0) {
4329
+ return (node) => ({ line: rangeOf(node).start });
4330
+ }
4331
+ return sitesIn(body, nodes).plainSiteOf;
4332
+ }
4290
4333
  function invocationEffectsIn(body, written, facts) {
4291
4334
  const calls = [...written].sort((a, b) => a.endIndex - b.endIndex);
4292
4335
  if (calls.length === 0) {
4293
4336
  return [];
4294
4337
  }
4295
- const statementOf3 = /* @__PURE__ */ new Map();
4296
- const statements = [];
4297
- for (const call of calls) {
4298
- const statement = enclosingStatement(call, body);
4299
- if (statement === null) {
4300
- continue;
4301
- }
4302
- statementOf3.set(call.id, statement);
4303
- statements.push(statement);
4304
- }
4305
- const lowered = lowerPythonBody(body, statements);
4306
- const enumerated = (0, import_extractor2.enumerateOrDegrade)(
4307
- {
4308
- statements: lowered.statements,
4309
- terminalsByStmt: lowered.terminalsByStmt
4310
- },
4311
- statements
4312
- );
4313
- const gatingOf = calls.map((call) => {
4314
- const statement = statementOf3.get(call.id);
4315
- return statement === void 0 ? void 0 : enumerated.byTerminal.get(statement);
4316
- });
4338
+ const sites = sitesIn(body, calls);
4339
+ const gatingOf = calls.map((call) => sites.pathsTo(call));
4317
4340
  askWrittenValues(
4318
4341
  calls.flatMap((call, index) => effectValueNodes(call, gatingOf[index])),
4319
4342
  facts
@@ -4331,8 +4354,10 @@ function invocationEffectsIn(body, written, facts) {
4331
4354
  callee: calleeText(call),
4332
4355
  args: argsOf(call, facts),
4333
4356
  async: false,
4334
- ...conditions.length > 0 ? { preconditions: conditions } : {}
4335
- }
4357
+ ...conditions.length > 0 ? { preconditions: conditions } : {},
4358
+ location: rangeOf(call)
4359
+ },
4360
+ site: sites.siteOf(call, conditions)
4336
4361
  };
4337
4362
  });
4338
4363
  }
@@ -4437,7 +4462,7 @@ function rawSqlEffects(calls, options) {
4437
4462
  for (const match of matchesIn(calls, options)) {
4438
4463
  for (const access of match.accesses) {
4439
4464
  const effect = effectFor(match, access);
4440
- found.effects.push(effect);
4465
+ found.effects.push({ effect, at: match.call });
4441
4466
  for (const slot of boundSlots(access, match.binds)) {
4442
4467
  found.slots.push({ ...slot, effect });
4443
4468
  }
@@ -4544,7 +4569,8 @@ function effectFor(match, access) {
4544
4569
  fields: access.fields,
4545
4570
  ...access.selector.length > 0 ? { selector: access.selector } : {},
4546
4571
  operation
4547
- }
4572
+ },
4573
+ location: rangeOf(match.call)
4548
4574
  };
4549
4575
  }
4550
4576
  var NO_GROUP = "default";
@@ -5058,7 +5084,8 @@ function effectFor2(pattern, chain, operation, options) {
5058
5084
  fields: fieldsOf(chain, valueMethods),
5059
5085
  operation,
5060
5086
  ...picked.length > 0 ? { selector: picked } : {}
5061
- }
5087
+ },
5088
+ location: rangeOf(chain.last)
5062
5089
  };
5063
5090
  if (options.statesSlots !== void 0) {
5064
5091
  const slots = slotsOf(chain, valueMethods);
@@ -5155,7 +5182,12 @@ function storageCallIds(calls, options) {
5155
5182
  }
5156
5183
  function storageEffects(calls, options) {
5157
5184
  return matchedChains(calls, options).flatMap(
5158
- ({ chain, pattern, operation }) => operation === null ? [] : [effectFor2(pattern, chain, operation, options)]
5185
+ ({ chain, pattern, operation }) => operation === null ? [] : [
5186
+ {
5187
+ effect: effectFor2(pattern, chain, operation, options),
5188
+ at: chain.last
5189
+ }
5190
+ ]
5159
5191
  );
5160
5192
  }
5161
5193
  var storageByScope = /* @__PURE__ */ new WeakMap();
@@ -5353,11 +5385,6 @@ function bodyTerminals(body, raised, continuations = []) {
5353
5385
  ];
5354
5386
  return found.sort((a, b) => a.statement.startIndex - b.statement.startIndex);
5355
5387
  }
5356
- function effectsReaching(effects, conditions) {
5357
- return effects.filter(
5358
- (effect) => (0, import_extractor4.guardsHoldOn)(effect.preconditions, conditions)
5359
- );
5360
- }
5361
5388
  function conditionInputs(pathSets, options) {
5362
5389
  const unit = options.body?.parent ?? null;
5363
5390
  if (options.facts === void 0 || unit === null) {
@@ -5392,7 +5419,8 @@ function enumerateBodyBranches(options) {
5392
5419
  options
5393
5420
  );
5394
5421
  const branches = [];
5395
- const push = (branch, paths) => {
5422
+ const push = (branch, paths, placement) => {
5423
+ const loopAround = placement?.loopAround;
5396
5424
  for (const path8 of paths) {
5397
5425
  const conditions = path8.map((condition) => ({
5398
5426
  sourceText: condition.sourceText,
@@ -5400,10 +5428,14 @@ function enumerateBodyBranches(options) {
5400
5428
  polarity: condition.polarity,
5401
5429
  source: condition.source
5402
5430
  }));
5403
- branches.push({
5431
+ const ending = {
5404
5432
  ...branch,
5405
5433
  conditions,
5406
- effects: effectsReaching(options.effects, conditions),
5434
+ ...loopAround === void 0 ? {} : { loopAround }
5435
+ };
5436
+ branches.push({
5437
+ ...ending,
5438
+ effects: (0, import_extractor4.effectsFiringOn)(options.effects, ending),
5407
5439
  isDefault: path8.length === 0
5408
5440
  });
5409
5441
  }
@@ -5411,11 +5443,15 @@ function enumerateBodyBranches(options) {
5411
5443
  for (const found of options.terminals) {
5412
5444
  const paths = enumerated.byTerminal.get(found.statement);
5413
5445
  if (paths !== void 0) {
5414
- push(options.branchOf(found), paths);
5446
+ push(
5447
+ options.branchOf(found),
5448
+ paths,
5449
+ enumerated.placement.get(found.statement)
5450
+ );
5415
5451
  }
5416
5452
  }
5417
5453
  if (options.fallthrough !== void 0) {
5418
- push(options.fallthrough, enumerated.fallthrough);
5454
+ push(options.fallthrough, enumerated.fallthrough, void 0);
5419
5455
  }
5420
5456
  return branches;
5421
5457
  }
@@ -5590,15 +5626,10 @@ function writtenMessageOf(call) {
5590
5626
  var import_extractor5 = require("@suss/extractor");
5591
5627
  function throughResponseHelpers(branches, sites, options) {
5592
5628
  const handOffs = [];
5593
- for (const { call, effect } of sites) {
5629
+ for (const { call, site } of sites) {
5594
5630
  const helper = helperBranchesAt(call, options);
5595
5631
  if (helper !== null) {
5596
- handOffs.push({
5597
- helper,
5598
- preconditions: effect.preconditions,
5599
- line: rangeOf(call).start,
5600
- at: rangeOf(call)
5601
- });
5632
+ handOffs.push({ ...site, helper, at: rangeOf(call) });
5602
5633
  }
5603
5634
  }
5604
5635
  return handOffs.length === 0 ? [...branches] : branches.flatMap((branch) => (0, import_extractor5.splitAtHandOffs)(branch, handOffs));
@@ -5845,7 +5876,7 @@ function callerBranches(definition, reading) {
5845
5876
  body,
5846
5877
  terminals: bodyTerminals(body, raised),
5847
5878
  raised,
5848
- effects,
5879
+ effects: sites,
5849
5880
  branchOf: (found2) => found2.type === "raise" ? { terminal: found2.terminal, location: rangeOf(found2.statement) } : handsBackAt(rangeOf(found2.statement)),
5850
5881
  fallthrough: handsBackAt(range),
5851
5882
  facts: reading.facts,
@@ -6425,9 +6456,7 @@ function envReadEffects(root, module2, facts) {
6425
6456
  const syntax = envReadSyntaxAt(node, scope);
6426
6457
  if (syntax !== null) {
6427
6458
  const name = stringLiteralValue(syntax.name);
6428
- if (name !== null) {
6429
- slots.push({ read: { name, defaulted: syntax.defaulted } });
6430
- }
6459
+ slots.push({ read: { name, defaulted: syntax.defaulted }, at: node });
6431
6460
  return;
6432
6461
  }
6433
6462
  if (readsHelpers && node.type === "call") {
@@ -6447,21 +6476,25 @@ function envReadEffects(root, module2, facts) {
6447
6476
  );
6448
6477
  return slots.flatMap((slot) => {
6449
6478
  if ("read" in slot) {
6450
- return [configReadEffect(slot.read)];
6479
+ return [{ effect: configReadEffect(slot.read, slot.at), at: slot.at }];
6451
6480
  }
6452
- return throughHelpers.get(slot.call.id) ?? [];
6481
+ return (throughHelpers.get(slot.call.id) ?? []).map((effect) => ({
6482
+ effect,
6483
+ at: slot.call
6484
+ }));
6453
6485
  });
6454
6486
  }
6455
- function configReadEffect(read) {
6487
+ function configReadEffect(read, at) {
6456
6488
  return {
6457
6489
  type: "interaction",
6458
6490
  binding: (0, import_behavioral_ir4.runtimeConfigBinding)({ recognition: PYTHON_ENV_RECOGNITION }),
6459
- callee: `os.environ["${read.name}"]`,
6491
+ callee: read.name === null ? at.text : `os.environ["${read.name}"]`,
6460
6492
  interaction: {
6461
6493
  class: "config-read",
6462
6494
  name: read.name,
6463
6495
  defaulted: read.defaulted
6464
- }
6496
+ },
6497
+ location: rangeOf(at)
6465
6498
  };
6466
6499
  }
6467
6500
  function envReadSyntaxAt(node, scope) {
@@ -6667,6 +6700,15 @@ function argumentsByParameter(db, call, calleeKey2) {
6667
6700
  }
6668
6701
  return argumentAt2;
6669
6702
  }
6703
+ function handsOnParameter(argument) {
6704
+ const caller = enclosingFunction(argument);
6705
+ if (argument.type !== "identifier" || caller === null) {
6706
+ return false;
6707
+ }
6708
+ return children(field(caller, "parameters")).some(
6709
+ (param) => parameterIdentifier(param)?.text === argument.text
6710
+ );
6711
+ }
6670
6712
  function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6671
6713
  const reads = /* @__PURE__ */ new Map();
6672
6714
  for (const [parameter, argument] of argumentAt2) {
@@ -6676,7 +6718,7 @@ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6676
6718
  `${NAMED_PARAMETER}${parameter}`,
6677
6719
  describeSites(sites, siteById)
6678
6720
  );
6679
- if (sites === void 0) {
6721
+ if (sites === void 0 || handsOnParameter(argument)) {
6680
6722
  continue;
6681
6723
  }
6682
6724
  const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
@@ -6693,7 +6735,7 @@ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
6693
6735
  }
6694
6736
  const defaultedAtCall = isDefaultedAt(call);
6695
6737
  return [...reads].map(
6696
- ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall })
6738
+ ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall }, call)
6697
6739
  );
6698
6740
  }
6699
6741
 
@@ -6800,6 +6842,11 @@ var PYTHON_STREAM_RECOGNITION = "python-stdlib";
6800
6842
  var STREAMS = /* @__PURE__ */ new Set(["stdout", "stderr"]);
6801
6843
  var DEFERRED_BODY_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
6802
6844
  function streamWriteEffects(root, module2) {
6845
+ return (0, import_extractor8.numberRepeatedCallSites)(
6846
+ writesUnder(root, module2).map((write) => [write])
6847
+ ).flat();
6848
+ }
6849
+ function writesUnder(root, module2) {
6803
6850
  const found = [];
6804
6851
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
6805
6852
  (0, import_extractor8.walkDescendants)(root, startScope, children, {
@@ -6809,7 +6856,7 @@ function streamWriteEffects(root, module2) {
6809
6856
  }
6810
6857
  const write = streamWriteAt(node, scope);
6811
6858
  if (write !== null) {
6812
- found.push(write);
6859
+ found.push({ effect: write, at: node });
6813
6860
  }
6814
6861
  },
6815
6862
  into: (node, scope) => {
@@ -6848,16 +6895,17 @@ function standardStream(node, scope) {
6848
6895
  }
6849
6896
  function writeEffect(site) {
6850
6897
  const { call, callee, target } = site;
6851
- return {
6898
+ const effect = {
6852
6899
  type: "interaction",
6853
6900
  binding: (0, import_behavioral_ir5.ioBinding)({ recognition: PYTHON_STREAM_RECOGNITION, target }),
6854
6901
  callee,
6855
- groupId: `${call.startPosition.row + 1}:${call.startPosition.column + 1}`,
6856
6902
  interaction: {
6857
6903
  class: "stream-write",
6858
6904
  ...payloadOf(site.positional, site.scope)
6859
- }
6905
+ },
6906
+ location: rangeOf(call)
6860
6907
  };
6908
+ return { ...effect, groupId: (0, import_extractor8.callSiteKey)(effect) };
6861
6909
  }
6862
6910
  function payloadOf(args, scope) {
6863
6911
  const only = args[0];
@@ -7718,7 +7766,8 @@ function buildRouteUnit(options) {
7718
7766
  options.responseShape,
7719
7767
  () => readReturnAnnotation(definitionNode, enclosingScope, ctx)
7720
7768
  );
7721
- const effects = invocationEffects(definitionNode, options.facts);
7769
+ const calls = invocationSites(definitionNode, options.facts);
7770
+ const effects = calls.map((call) => call.effect);
7722
7771
  const extra = recognizedBodyEffects(
7723
7772
  definitionNode,
7724
7773
  module2,
@@ -7727,7 +7776,7 @@ function buildRouteUnit(options) {
7727
7776
  );
7728
7777
  const perTerminal = branchesPerTerminal({
7729
7778
  definitionNode,
7730
- effects,
7779
+ effects: calls,
7731
7780
  responseStatusCalls: options.responseStatusCalls,
7732
7781
  ctx: {
7733
7782
  pattern,
@@ -7870,12 +7919,17 @@ function unreadRoleReadings(parameters, range) {
7870
7919
  }
7871
7920
  function recognizedBodyEffects(definitionNode, module2, storageLookup, facts) {
7872
7921
  const storage = storageLookup === void 0 ? { effects: [], slots: [] } : bodyStorage(definitionNode, storageLookup);
7922
+ const found = [
7923
+ ...envReadEffects(definitionNode, module2, facts),
7924
+ ...storage.effects,
7925
+ ...streamWriteEffects(definitionNode, module2)
7926
+ ];
7927
+ const siteOf2 = effectSitesIn(
7928
+ field(definitionNode, "body"),
7929
+ found.map((one) => one.at)
7930
+ );
7873
7931
  return {
7874
- effects: [
7875
- ...envReadEffects(definitionNode, module2, facts),
7876
- ...storage.effects,
7877
- ...streamWriteEffects(definitionNode, module2)
7878
- ],
7932
+ effects: found.map(({ effect, at }) => ({ effect, site: siteOf2(at) })),
7879
7933
  provenance: storage.slots.length === 0 || storageLookup === void 0 ? [] : slotProvenance(storage.slots, {
7880
7934
  facts: storageLookup.facts,
7881
7935
  unit: definitionNode
@@ -7883,12 +7937,13 @@ function recognizedBodyEffects(definitionNode, module2, storageLookup, facts) {
7883
7937
  };
7884
7938
  }
7885
7939
  function withBodyEffects(branch, extra) {
7886
- if (extra.effects.length === 0) {
7940
+ const effects = (0, import_extractor9.effectsFiringOn)(extra.effects, branch);
7941
+ if (effects.length === 0) {
7887
7942
  return branch;
7888
7943
  }
7889
7944
  return {
7890
7945
  ...branch,
7891
- extraEffects: extra.effects,
7946
+ extraEffects: effects,
7892
7947
  ...extra.provenance.length === 0 ? {} : { provenance: extra.provenance }
7893
7948
  };
7894
7949
  }
@@ -8260,7 +8315,7 @@ var import_node_fs4 = __toESM(require("fs"), 1);
8260
8315
  var import_node_path5 = __toESM(require("path"), 1);
8261
8316
  var import_behavioral_ir10 = require("@suss/behavioral-ir");
8262
8317
  var import_datalog2 = require("@suss/datalog");
8263
- var import_extractor18 = require("@suss/extractor");
8318
+ var import_extractor19 = require("@suss/extractor");
8264
8319
  var import_resolution18 = require("@suss/resolution");
8265
8320
 
8266
8321
  // src/diagnostics.ts
@@ -8278,6 +8333,12 @@ function buildPythonExtractionReport(args) {
8278
8333
  });
8279
8334
  }
8280
8335
 
8336
+ // src/reach/closure.ts
8337
+ var import_behavioral_ir7 = require("@suss/behavioral-ir");
8338
+ var import_datalog = require("@suss/datalog");
8339
+ var import_extractor13 = require("@suss/extractor");
8340
+ var import_resolution11 = require("@suss/resolution");
8341
+
8281
8342
  // src/exits.ts
8282
8343
  var import_extractor11 = require("@suss/extractor");
8283
8344
  var import_resolution9 = require("@suss/resolution");
@@ -8410,75 +8471,6 @@ function returnTerminal(shape, location) {
8410
8471
  };
8411
8472
  }
8412
8473
 
8413
- // src/importedDefinitions.ts
8414
- var import_resolution10 = require("@suss/resolution");
8415
- function importedDefinitionLookup(db, files) {
8416
- const fileOfModuleScope = /* @__PURE__ */ new Map();
8417
- const byFile = /* @__PURE__ */ new Map();
8418
- for (const file of files) {
8419
- fileOfModuleScope.set(file.module.moduleScope.node.id, file.file);
8420
- byFile.set(file.file, file);
8421
- }
8422
- const found = /* @__PURE__ */ new Map();
8423
- return (scope, name) => {
8424
- const fromFile = fileOfModuleScope.get(moduleScopeOf(scope).node.id);
8425
- if (fromFile === void 0) {
8426
- return null;
8427
- }
8428
- const key = `${fromFile}#${name}`;
8429
- let definition = found.get(key);
8430
- if (definition === void 0) {
8431
- definition = definitionAmong(originsOf(db, key), byFile);
8432
- found.set(key, definition);
8433
- } else if ((0, import_resolution10.isObserved)(db)) {
8434
- originsOf(db, key);
8435
- }
8436
- if (definition !== null) {
8437
- (0, import_resolution10.noteKeyRead)(db, definition.file);
8438
- }
8439
- return definition;
8440
- };
8441
- }
8442
- function definitionAmong(origins, byFile) {
8443
- for (const origin of origins) {
8444
- const file = byFile.get(origin.module);
8445
- const binding = file?.module.moduleScope.bindings.get(origin.name);
8446
- if (file === void 0 || binding === void 0) {
8447
- continue;
8448
- }
8449
- const node = definedNodeOf(binding);
8450
- if (node !== null) {
8451
- return {
8452
- node,
8453
- file: file.file,
8454
- moduleScope: file.module.moduleScope,
8455
- scopeFor: file.module.scopeFor
8456
- };
8457
- }
8458
- }
8459
- return null;
8460
- }
8461
- function definedNodeOf(binding) {
8462
- if (binding.kind === "classDef") {
8463
- return binding.node;
8464
- }
8465
- if (binding.kind === "assignment") {
8466
- return binding.value;
8467
- }
8468
- return null;
8469
- }
8470
-
8471
- // src/moduleSurface.ts
8472
- var import_behavioral_ir8 = require("@suss/behavioral-ir");
8473
- var import_extractor14 = require("@suss/extractor");
8474
- var import_resolution13 = require("@suss/resolution");
8475
-
8476
- // src/reach/closure.ts
8477
- var import_behavioral_ir7 = require("@suss/behavioral-ir");
8478
- var import_datalog = require("@suss/datalog");
8479
- var import_extractor13 = require("@suss/extractor");
8480
- var import_resolution12 = require("@suss/resolution");
8481
-
8482
8474
  // src/flags.ts
8483
8475
  var import_extractor12 = require("@suss/extractor");
8484
8476
  var PARSE_METHODS = /* @__PURE__ */ new Set(["parse_args", "parse_known_args"]);
@@ -8560,7 +8552,7 @@ function methodCalledOn(call) {
8560
8552
  }
8561
8553
 
8562
8554
  // src/reach/resolveCallee.ts
8563
- var import_resolution11 = require("@suss/resolution");
8555
+ var import_resolution10 = require("@suss/resolution");
8564
8556
  var STOP_FOR = {
8565
8557
  severalSources: "multipleSources",
8566
8558
  outsideRun: "outsideRun",
@@ -8574,25 +8566,122 @@ var NO_DECLARATION = {
8574
8566
  function calleeSpellings(calls, ctx) {
8575
8567
  const spellingOf = /* @__PURE__ */ new Map();
8576
8568
  const keys = /* @__PURE__ */ new Set();
8569
+ const receivers = /* @__PURE__ */ new Set();
8577
8570
  for (const { call, site } of calls) {
8578
8571
  const spelling = spellingFor2(call, site, ctx);
8579
8572
  spellingOf.set(call.id, spelling);
8580
8573
  if (spelling.kind === "key") {
8581
8574
  keys.add(spelling.key);
8582
8575
  }
8576
+ const receiver = receiverOf(call, site);
8577
+ if (receiver !== null) {
8578
+ receivers.add(receiver.key);
8579
+ }
8583
8580
  }
8584
- return { spellingOf, outcomes: (0, import_resolution11.calleeOutcomes)(ctx.facts, [...keys]) };
8581
+ const outcomes = (0, import_resolution10.calleeOutcomes)(ctx.facts, [...keys]);
8582
+ resolveCalls(ctx.facts, [...receivers]);
8583
+ return { spellingOf, outcomes };
8585
8584
  }
8586
8585
  function resolveCallee2(call, site, ctx, read) {
8587
8586
  const spelling = read?.spellingOf.get(call.id) ?? spellingFor2(call, site, ctx);
8588
8587
  if (spelling.kind === "stopped") {
8589
8588
  return spelling;
8590
8589
  }
8591
- const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution11.calleeOutcomeOf)(ctx.facts, spelling.key);
8592
- if (selfCallInMixin(call, outcome, site, ctx)) {
8593
- return { kind: "stopped", reason: "selfCallInMixin" };
8590
+ const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution10.calleeOutcomeOf)(ctx.facts, spelling.key);
8591
+ const settled = selfCallInMixin(call, outcome, site, ctx) ? { kind: "stopped", reason: "selfCallInMixin" } : asCallee(outcome, site.owner, ctx);
8592
+ return onReceiverClass(call, site, ctx, settled) ?? settled;
8593
+ }
8594
+ function receiverOf(call, site) {
8595
+ const callee = readThrough(field(call, "function"));
8596
+ const object = callee?.type === "attribute" ? field(callee, "object") : null;
8597
+ const name = callee === null ? void 0 : field(callee, "attribute")?.text;
8598
+ if (object === null || name === void 0) {
8599
+ return null;
8594
8600
  }
8595
- return asCallee(outcome, site.owner, ctx);
8601
+ return {
8602
+ key: readKey(site.file.file, object, enclosingFunction(object)),
8603
+ name
8604
+ };
8605
+ }
8606
+ function onReceiverClass(call, site, ctx, settled) {
8607
+ const own = ownReceiverOf(site, ctx);
8608
+ const callee = readThrough(field(call, "function"));
8609
+ if (callee?.type === "identifier") {
8610
+ const calleeKey2 = readKey(
8611
+ site.file.file,
8612
+ callee,
8613
+ enclosingFunction(callee)
8614
+ );
8615
+ return own !== null && (0, import_resolution10.selfFormOf)(ctx.facts, calleeKey2, own.key) === import_resolution10.SELF_AS_CLASS_NAME ? methodOn(own.cls, CONSTRUCTOR_METHOD, ctx) : null;
8616
+ }
8617
+ const read = receiverOf(call, site);
8618
+ if (read === null) {
8619
+ return null;
8620
+ }
8621
+ if (own !== null && (0, import_resolution10.selfFormOf)(ctx.facts, read.key, own.key) !== null) {
8622
+ return methodOn(own.cls, read.name, ctx);
8623
+ }
8624
+ const sentTo = classReturnedTo(read.key, own, ctx);
8625
+ if (sentTo !== null) {
8626
+ return methodOn(sentTo, read.name, ctx);
8627
+ }
8628
+ const receiverClass = settled.kind === "followed" ? onlyClassOf(read.key, ctx) : null;
8629
+ return receiverClass === null || settled.kind !== "followed" ? null : { kind: "followed", target: runningOn(settled.target, receiverClass) };
8630
+ }
8631
+ function ownReceiverOf(site, ctx) {
8632
+ const [row] = ctx.facts.lookup("selfReceiver", 0, site.owner);
8633
+ const cls = site.receiverClass ?? classOfFunction(site.owner, ctx);
8634
+ return row === void 0 || cls === null ? null : { key: String(row[1]), cls };
8635
+ }
8636
+ function classReturnedTo(key, own, ctx) {
8637
+ const returned = (0, import_resolution10.selfResultOf)(ctx.facts, key);
8638
+ if (returned === null) {
8639
+ return null;
8640
+ }
8641
+ if (own !== null && returned.receiver === own.key) {
8642
+ return own.cls;
8643
+ }
8644
+ resolveCalls(ctx.facts, [returned.receiver]);
8645
+ return onlyClassOf(returned.receiver, ctx);
8646
+ }
8647
+ var CONSTRUCTOR_METHOD = "__init__";
8648
+ function methodOn(cls, name, ctx) {
8649
+ const key = (0, import_resolution10.memberOf)(ctx.facts, cls.key, name, (0, import_resolution10.resolutionProgram)());
8650
+ const found = key === null ? null : functionCallee(key, ctx);
8651
+ if (found === null || found.kind !== "followed") {
8652
+ return null;
8653
+ }
8654
+ return { kind: "followed", target: runningOn(found.target, cls) };
8655
+ }
8656
+ function runningOn(target, cls) {
8657
+ const owner = ownerClassOf(target.node);
8658
+ if (owner === null || nodeId(target.file.file, owner) === cls.key) {
8659
+ return target;
8660
+ }
8661
+ return {
8662
+ ...target,
8663
+ exportPath: [cls.name, target.name],
8664
+ receiverClass: cls
8665
+ };
8666
+ }
8667
+ function classOfFunction(functionKey2, ctx) {
8668
+ const node = ctx.definitions.get(functionKey2);
8669
+ const owner = node === void 0 ? null : ownerClassOf(node);
8670
+ return owner === null ? null : subclassAt(owner, (0, import_resolution10.fileOfKey)(functionKey2));
8671
+ }
8672
+ function onlyClassOf(key, ctx) {
8673
+ const objects = (0, import_resolution10.answersFor)(ctx.facts, "wantedObjectOf", key);
8674
+ const [only] = objects;
8675
+ if (objects.length !== 1 || only === void 0) {
8676
+ return null;
8677
+ }
8678
+ const file = ctx.filesByPath.get((0, import_resolution10.fileOfKey)(only));
8679
+ const node = file === void 0 ? null : (0, import_resolution10.nodeOfKey)(/* @__PURE__ */ new Map([[file.file, file.root]]), only);
8680
+ return node?.type === "class_definition" ? subclassAt(node, file?.file ?? (0, import_resolution10.fileOfKey)(only)) : null;
8681
+ }
8682
+ function subclassAt(cls, file) {
8683
+ const name = field(cls, "name")?.text;
8684
+ return name === void 0 ? null : { key: nodeId(file, cls), name, file };
8596
8685
  }
8597
8686
  function selfCallInMixin(call, outcome, site, ctx) {
8598
8687
  if (outcome.kind !== "callerSupplied" || !outcome.key.startsWith(`${site.owner}#`)) {
@@ -8619,19 +8708,19 @@ function instancesHaveAttribute(facts, classKey, name) {
8619
8708
  return true;
8620
8709
  }
8621
8710
  return facts.lookup("storesProperty", 0, String(member[2])).some(
8622
- (store) => String(store[1]) === name && store[3] === import_resolution11.RECEIVER_STORE_NAME
8711
+ (store) => String(store[1]) === name && store[3] === import_resolution10.RECEIVER_STORE_NAME
8623
8712
  );
8624
8713
  });
8625
8714
  }
8626
8715
  function namedOutcomes(keys, ctx) {
8627
- return (0, import_resolution11.calleeOutcomes)(ctx.facts, [...new Set(keys)]);
8716
+ return (0, import_resolution10.calleeOutcomes)(ctx.facts, [...new Set(keys)]);
8628
8717
  }
8629
8718
  function dispatchedCalls(calls, ctx) {
8630
8719
  const byKey = new Map(
8631
8720
  calls.map(({ call, site }) => [nodeId(site.file.file, call), call])
8632
8721
  );
8633
8722
  const found = /* @__PURE__ */ new Map();
8634
- for (const [key, outcome] of (0, import_resolution11.dispatchOutcomes)(ctx.facts, [...byKey.keys()])) {
8723
+ for (const [key, outcome] of (0, import_resolution10.dispatchOutcomes)(ctx.facts, [...byKey.keys()])) {
8635
8724
  const call = byKey.get(key);
8636
8725
  if (call === void 0) {
8637
8726
  continue;
@@ -8664,11 +8753,11 @@ function functionNamed(nameKey2, ctx, read) {
8664
8753
  if (assignedElsewhere(ctx.facts, nameKey2)) {
8665
8754
  return null;
8666
8755
  }
8667
- const outcome = read?.get(nameKey2) ?? (0, import_resolution11.calleeOutcomeOf)(ctx.facts, nameKey2);
8756
+ const outcome = read?.get(nameKey2) ?? (0, import_resolution10.calleeOutcomeOf)(ctx.facts, nameKey2);
8668
8757
  return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
8669
8758
  }
8670
8759
  function assignedElsewhere(facts, nameKey2) {
8671
- return (0, import_resolution11.writtenSourcesOf)(facts, nameKey2).some(
8760
+ return (0, import_resolution10.writtenSourcesOf)(facts, nameKey2).some(
8672
8761
  (source) => !facts.has("func", [source])
8673
8762
  );
8674
8763
  }
@@ -8851,7 +8940,7 @@ function moduleNamed(from, module2, relativeLevel, ctx) {
8851
8940
  if (file === void 0) {
8852
8941
  return null;
8853
8942
  }
8854
- (0, import_resolution11.noteKeyRead)(ctx.facts, file.file);
8943
+ (0, import_resolution10.noteKeyRead)(ctx.facts, file.file);
8855
8944
  return { kind: "module", file };
8856
8945
  }
8857
8946
 
@@ -8949,7 +9038,10 @@ function reachedFunctions(seeds, options) {
8949
9038
  passedPositions.add(position);
8950
9039
  }
8951
9040
  const called = [];
8952
- for (const target of scan.followed) {
9041
+ const calledHere = scan.followed.flatMap(
9042
+ (target) => target.receiverClass === void 0 ? [target] : [plainOf(target), target]
9043
+ );
9044
+ for (const target of calledHere) {
8953
9045
  const calleeKey2 = keyOf2(target);
8954
9046
  if (!functionByKey.has(calleeKey2)) {
8955
9047
  functionByKey.set(calleeKey2, target);
@@ -8966,11 +9058,30 @@ function reachedFunctions(seeds, options) {
8966
9058
  if (ledger === void 0) {
8967
9059
  settleBodyValues(reached, functionByKey, options);
8968
9060
  }
9061
+ const placementsOf = (key) => ({
9062
+ targets: targetsByKey.get(key),
9063
+ argTargets: argTargetsByKey.get(key),
9064
+ possibleTargets: possibleTargetsByKey.get(key)
9065
+ });
9066
+ const onSubclass = (0, import_resolution11.keptVariants)(
9067
+ reached.flatMap(
9068
+ (key) => functionByKey.get(key)?.receiverClass === void 0 ? [] : [
9069
+ {
9070
+ key,
9071
+ differs: !(0, import_behavioral_ir7.sameDeclarations)(
9072
+ placementsOf(key),
9073
+ placementsOf((0, import_resolution11.plainKeyOf)(key))
9074
+ ),
9075
+ calls: callsByKey.get(key) ?? []
9076
+ }
9077
+ ]
9078
+ )
9079
+ );
8969
9080
  const summaries = [];
8970
9081
  const summariesByKey = /* @__PURE__ */ new Map();
8971
9082
  for (const key of reached) {
8972
9083
  const target = functionByKey.get(key);
8973
- if (target === void 0) {
9084
+ if (target === void 0 || target.receiverClass !== void 0 && !onSubclass.has(key)) {
8974
9085
  continue;
8975
9086
  }
8976
9087
  const kept = replay?.summaryOf(key);
@@ -8985,6 +9096,9 @@ function reachedFunctions(seeds, options) {
8985
9096
  const summary2 = (0, import_extractor13.assembleSummary)(libraryUnit(target, options), {
8986
9097
  gapHandling: options.gapHandling
8987
9098
  });
9099
+ if (target.receiverClass !== void 0) {
9100
+ summary2.receiverClass = displayedClass(target.receiverClass, ctx);
9101
+ }
8988
9102
  if (options.gapHandling !== "silent") {
8989
9103
  summary2.gaps.push(
8990
9104
  ...(stopsByKey.get(key) ?? []).map(import_behavioral_ir7.unfollowedCallGap)
@@ -9035,7 +9149,7 @@ var ReplayFailed = class extends Error {
9035
9149
  function chargeOf(charges, key) {
9036
9150
  let charge = charges.get(key);
9037
9151
  if (charge === void 0) {
9038
- charge = (0, import_resolution12.noDependencies)();
9152
+ charge = (0, import_resolution11.noDependencies)();
9039
9153
  charges.set(key, charge);
9040
9154
  }
9041
9155
  return charge;
@@ -9122,7 +9236,8 @@ function storedScan(scan) {
9122
9236
  followed: scan.followed.map((target) => ({
9123
9237
  key: keyOf2(target),
9124
9238
  name: target.name,
9125
- exportPath: target.exportPath
9239
+ exportPath: target.exportPath,
9240
+ ...target.receiverClass === void 0 ? {} : { receiverClass: target.receiverClass }
9126
9241
  })),
9127
9242
  stops: scan.stops,
9128
9243
  targets: [...scan.targets],
@@ -9155,14 +9270,25 @@ function scanFromStored(stored, functionFor) {
9155
9270
  };
9156
9271
  }
9157
9272
  function keyOf2(target) {
9158
- return nodeId(target.file.file, target.node);
9273
+ const key = nodeId(target.file.file, target.node);
9274
+ return target.receiverClass === void 0 ? key : (0, import_resolution11.variantKey)(key, target.receiverClass);
9275
+ }
9276
+ function plainOf(target) {
9277
+ return reachedFunctionOf(target.file, target.node);
9278
+ }
9279
+ function placeOf(target, ctx) {
9280
+ const place = { file: target.file.displayPath, span: spanOf(target.node) };
9281
+ return target.receiverClass === void 0 ? place : { ...place, receiverClass: displayedClass(target.receiverClass, ctx) };
9282
+ }
9283
+ function displayedClass(cls, ctx) {
9284
+ return {
9285
+ name: cls.name,
9286
+ file: ctx.filesByPath.get(cls.file)?.displayPath ?? cls.file
9287
+ };
9159
9288
  }
9160
- function placeCallee(placements, callee, outcome, call, file) {
9289
+ function placeCallee(placements, callee, outcome, call, file, ctx) {
9161
9290
  if (outcome.kind === "followed") {
9162
- placements.place(callee, {
9163
- file: outcome.target.file.displayPath,
9164
- span: spanOf(outcome.target.node)
9165
- });
9291
+ placements.place(callee, placeOf(outcome.target, ctx));
9166
9292
  return;
9167
9293
  }
9168
9294
  if (outcome.reason === "noDeclaration" && !isMethodCall(call)) {
@@ -9190,9 +9316,13 @@ function bodyOf(source) {
9190
9316
  }
9191
9317
  return {
9192
9318
  body,
9193
- written: callsWritten(file, body, scopeAt2(file, node), keyOf2(source), {
9194
- descendsInto: isModule(node) ? runsAtModuleLoad : outsideNestedDef
9195
- })
9319
+ written: callsWritten(
9320
+ file,
9321
+ body,
9322
+ scopeAt2(file, node),
9323
+ { owner: nodeId(file.file, node), receiverClass: source.receiverClass },
9324
+ { descendsInto: isModule(node) ? runsAtModuleLoad : outsideNestedDef }
9325
+ )
9196
9326
  };
9197
9327
  }
9198
9328
  function outsideNestedDef(node) {
@@ -9262,16 +9392,20 @@ function scanBody(source, ctx, recognized, read, implied) {
9262
9392
  followed.push(target);
9263
9393
  }
9264
9394
  };
9265
- const recordPassedArgs = (call, callee, calleeKey2) => {
9395
+ const recordPassedArgs = (call, callee, target) => {
9266
9396
  const args = field(call, "arguments");
9267
9397
  if (args === null) {
9268
9398
  return;
9269
9399
  }
9400
+ const calleeKeys = target === null ? [] : [keyOf2(target), keyOf2(plainOf(target))];
9401
+ const passes = (position) => {
9402
+ for (const calleeKey2 of calleeKeys) {
9403
+ passedPositions.add(`${calleeKey2}#${position}`);
9404
+ }
9405
+ };
9270
9406
  children(args).forEach((arg, position) => {
9271
9407
  if (arg.type === "lambda") {
9272
- if (calleeKey2 !== null) {
9273
- passedPositions.add(`${calleeKey2}#${position}`);
9274
- }
9408
+ passes(position);
9275
9409
  return;
9276
9410
  }
9277
9411
  const nameKey2 = passedNameKeyOf(file, arg);
@@ -9283,23 +9417,15 @@ function scanBody(source, ctx, recognized, read, implied) {
9283
9417
  return;
9284
9418
  }
9285
9419
  follow(resolved);
9286
- placements.placeArg(callee, position, {
9287
- file: resolved.file.displayPath,
9288
- span: spanOf(resolved.node)
9289
- });
9290
- if (calleeKey2 !== null) {
9291
- passedPositions.add(`${calleeKey2}#${position}`);
9292
- }
9420
+ placements.placeArg(callee, position, placeOf(resolved, ctx));
9421
+ passes(position);
9293
9422
  });
9294
9423
  };
9295
9424
  const recordPossible = (call, callee, dispatched) => {
9296
9425
  dispatched.targets.forEach(follow);
9297
9426
  placements.placePossible(
9298
9427
  callee,
9299
- dispatched.targets.map((target) => ({
9300
- file: target.file.displayPath,
9301
- span: spanOf(target.node)
9302
- }))
9428
+ dispatched.targets.map((target) => placeOf(target, ctx))
9303
9429
  );
9304
9430
  recordPassedArgs(call, callee, null);
9305
9431
  if (dispatched.unresolved > 0 && !recognized.has(call.id)) {
@@ -9316,11 +9442,11 @@ function scanBody(source, ctx, recognized, read, implied) {
9316
9442
  return;
9317
9443
  }
9318
9444
  const outcome = only === void 0 ? settled : { kind: "followed", target: only };
9319
- placeCallee(placements, callee, outcome, call, file);
9445
+ placeCallee(placements, callee, outcome, call, file, ctx);
9320
9446
  recordPassedArgs(
9321
9447
  call,
9322
9448
  callee,
9323
- outcome.kind === "followed" ? keyOf2(outcome.target) : null
9449
+ outcome.kind === "followed" ? outcome.target : null
9324
9450
  );
9325
9451
  if (outcome.kind === "stopped") {
9326
9452
  if (!recognized.has(call.id) && (0, import_behavioral_ir7.worthRecording)(outcome.reason)) {
@@ -9361,12 +9487,12 @@ function scanBody(source, ctx, recognized, read, implied) {
9361
9487
  passedPositions
9362
9488
  };
9363
9489
  }
9364
- function callsWritten(file, body, outer, owner, limit) {
9490
+ function callsWritten(file, body, outer, scanned, limit) {
9365
9491
  const found = [];
9366
9492
  (0, import_extractor13.walkDescendants)(body, outer, children, {
9367
9493
  at: (child, scope) => {
9368
9494
  if (child.type === "call") {
9369
- found.push({ call: child, site: { file, scope, owner } });
9495
+ found.push({ call: child, site: { file, scope, ...scanned } });
9370
9496
  }
9371
9497
  },
9372
9498
  into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : import_extractor13.SKIP_CHILDREN
@@ -9415,13 +9541,8 @@ function libraryUnit(target, options, binding = (0, import_behavioral_ir7.functi
9415
9541
  file.module,
9416
9542
  parameters.map((parameter) => parameter.name)
9417
9543
  );
9418
- const effects = invocationEffects(node, options.facts);
9419
- const branches = exitingBranches(
9420
- node,
9421
- file.module,
9422
- effects,
9423
- options.facts
9424
- ) ?? [
9544
+ const calls = invocationSites(node, options.facts);
9545
+ const branches = exitingBranches(node, file.module, calls, options.facts) ?? [
9425
9546
  {
9426
9547
  conditions: [],
9427
9548
  terminal: {
@@ -9436,7 +9557,7 @@ function libraryUnit(target, options, binding = (0, import_behavioral_ir7.functi
9436
9557
  emitEvent: null,
9437
9558
  location: range
9438
9559
  },
9439
- effects,
9560
+ effects: calls.map((call) => call.effect),
9440
9561
  location: range,
9441
9562
  isDefault: true
9442
9563
  }
@@ -9533,12 +9654,132 @@ function parameterName2(param) {
9533
9654
  return identifiersUnder(param)[0] ?? null;
9534
9655
  }
9535
9656
 
9657
+ // src/entryPoints.ts
9658
+ function declaredEntryUnits(file, packs, options) {
9659
+ const units = [];
9660
+ for (const pack of packs) {
9661
+ for (const entry of pack.entryPoints?.(file.file) ?? []) {
9662
+ const binding = file.module.moduleScope.bindings.get(entry.name);
9663
+ if (binding?.kind !== "functionDef") {
9664
+ continue;
9665
+ }
9666
+ for (const declared of entry.units) {
9667
+ const raw = libraryUnit(
9668
+ {
9669
+ file,
9670
+ node: binding.node,
9671
+ name: declared.name,
9672
+ exportPath: [entry.name]
9673
+ },
9674
+ options,
9675
+ declared.binding
9676
+ );
9677
+ units.push({
9678
+ ...raw,
9679
+ identity: { ...raw.identity, kind: declared.kind },
9680
+ deployableUnit: declared.deployableUnit,
9681
+ ...declared.metadata !== void 0 ? { packMetadata: declared.metadata } : {}
9682
+ });
9683
+ }
9684
+ }
9685
+ }
9686
+ return units;
9687
+ }
9688
+
9689
+ // src/importedDefinitions.ts
9690
+ var import_resolution12 = require("@suss/resolution");
9691
+ function importedDefinitionLookup(db, files) {
9692
+ const fileOfModuleScope = /* @__PURE__ */ new Map();
9693
+ const byFile = /* @__PURE__ */ new Map();
9694
+ for (const file of files) {
9695
+ fileOfModuleScope.set(file.module.moduleScope.node.id, file.file);
9696
+ byFile.set(file.file, file);
9697
+ }
9698
+ const found = /* @__PURE__ */ new Map();
9699
+ return (scope, name) => {
9700
+ const fromFile = fileOfModuleScope.get(moduleScopeOf(scope).node.id);
9701
+ if (fromFile === void 0) {
9702
+ return null;
9703
+ }
9704
+ const key = `${fromFile}#${name}`;
9705
+ let definition = found.get(key);
9706
+ if (definition === void 0) {
9707
+ definition = definitionAmong(originsOf(db, key), byFile);
9708
+ found.set(key, definition);
9709
+ } else if ((0, import_resolution12.isObserved)(db)) {
9710
+ originsOf(db, key);
9711
+ }
9712
+ if (definition !== null) {
9713
+ (0, import_resolution12.noteKeyRead)(db, definition.file);
9714
+ }
9715
+ return definition;
9716
+ };
9717
+ }
9718
+ function definitionAmong(origins, byFile) {
9719
+ for (const origin of origins) {
9720
+ const file = byFile.get(origin.module);
9721
+ const binding = file?.module.moduleScope.bindings.get(origin.name);
9722
+ if (file === void 0 || binding === void 0) {
9723
+ continue;
9724
+ }
9725
+ const node = definedNodeOf(binding);
9726
+ if (node !== null) {
9727
+ return {
9728
+ node,
9729
+ file: file.file,
9730
+ moduleScope: file.module.moduleScope,
9731
+ scopeFor: file.module.scopeFor
9732
+ };
9733
+ }
9734
+ }
9735
+ return null;
9736
+ }
9737
+ function definedNodeOf(binding) {
9738
+ if (binding.kind === "classDef") {
9739
+ return binding.node;
9740
+ }
9741
+ if (binding.kind === "assignment") {
9742
+ return binding.value;
9743
+ }
9744
+ return null;
9745
+ }
9746
+
9747
+ // src/libraryEnvReads.ts
9748
+ var import_extractor14 = require("@suss/extractor");
9749
+ function libraryEnvReadMarkers(files, packs) {
9750
+ const markers = [];
9751
+ for (const pack of packs) {
9752
+ for (const declared of pack.libraryEnvVars ?? []) {
9753
+ const importer = files.find(
9754
+ (file) => importsModule(file, declared.module)
9755
+ );
9756
+ if (importer !== void 0) {
9757
+ markers.push(
9758
+ (0, import_extractor14.libraryEnvReadMarker)(pack.name, declared, importer.displayPath)
9759
+ );
9760
+ }
9761
+ }
9762
+ }
9763
+ return markers;
9764
+ }
9765
+ function importsModule(file, prefix) {
9766
+ for (const binding of file.module.moduleScope.bindings.values()) {
9767
+ if ((binding.kind === "import" || binding.kind === "importFrom") && binding.relativeLevel === 0 && binding.module.startsWith(prefix)) {
9768
+ return true;
9769
+ }
9770
+ }
9771
+ return false;
9772
+ }
9773
+
9536
9774
  // src/moduleSurface.ts
9775
+ var import_behavioral_ir8 = require("@suss/behavioral-ir");
9776
+ var import_extractor15 = require("@suss/extractor");
9777
+ var import_resolution13 = require("@suss/resolution");
9537
9778
  function settlePythonModules(modules) {
9538
- return (0, import_extractor14.settleModules)(modules, ["__init__.py"]);
9779
+ return (0, import_extractor15.settleModules)(modules, ["__init__.py"]);
9539
9780
  }
9540
9781
  function moduleExportUnits(file, options) {
9541
- const module2 = (0, import_extractor14.moduleOfFile)(options.modules, file.file);
9782
+ const module2 = (0, import_extractor15.moduleOfFile)(options.modules, file.file);
9542
9783
  if (module2 === void 0) {
9543
9784
  return [];
9544
9785
  }
@@ -9561,7 +9802,7 @@ function moduleExportUnits(file, options) {
9561
9802
  },
9562
9803
  options,
9563
9804
  (0, import_behavioral_ir8.moduleExportBinding)({
9564
- recognition: import_extractor14.MODULE_SURFACE_RECOGNITION,
9805
+ recognition: import_extractor15.MODULE_SURFACE_RECOGNITION,
9565
9806
  module: module2.name,
9566
9807
  exportName
9567
9808
  })
@@ -9634,9 +9875,20 @@ function pythonEntryReuse(records, changedFiles, changes) {
9634
9875
  return new import_resolution14.EntryReuse(records, changedFiles, changes, seedsOfRecord);
9635
9876
  }
9636
9877
  function storedFunction(target, definitions, filesByPath) {
9637
- const node = definitions.get(target.key);
9878
+ const plainKey = (0, import_resolution14.plainKeyOf)(target.key);
9879
+ const node = definitions.get(plainKey);
9638
9880
  const file = filesByPath.get((0, import_resolution14.fileOfKey)(target.key));
9639
- return node === void 0 || file === void 0 ? null : { file, node, name: target.name, exportPath: target.exportPath };
9881
+ const key = target.receiverClass === void 0 ? plainKey : (0, import_resolution14.variantKey)(plainKey, target.receiverClass);
9882
+ if (node === void 0 || file === void 0 || key !== target.key) {
9883
+ return null;
9884
+ }
9885
+ const found = {
9886
+ file,
9887
+ node,
9888
+ name: target.name,
9889
+ exportPath: target.exportPath
9890
+ };
9891
+ return target.receiverClass === void 0 ? found : { ...found, receiverClass: target.receiverClass };
9640
9892
  }
9641
9893
  var WalkReplay = class {
9642
9894
  constructor(reuse, definitions, filesByPath) {
@@ -11064,7 +11316,7 @@ function poetryPackages(pyproject) {
11064
11316
  // src/testCases.ts
11065
11317
  var import_node_path4 = __toESM(require("path"), 1);
11066
11318
  var import_behavioral_ir9 = require("@suss/behavioral-ir");
11067
- var import_extractor15 = require("@suss/extractor");
11319
+ var import_extractor16 = require("@suss/extractor");
11068
11320
  var import_resolution16 = require("@suss/resolution");
11069
11321
  var import_values27 = require("@suss/values");
11070
11322
  function pythonTestIndex(packs, context) {
@@ -11083,7 +11335,7 @@ var PythonTestIndex = class {
11083
11335
  implied = /* @__PURE__ */ new Map();
11084
11336
  /** The tests in one file, when the file is collected and listed. */
11085
11337
  unitsIn(file, options) {
11086
- return this.testsIn(file).filter((test) => (0, import_extractor15.isListedTestFile)(file.file, test.pattern.files)).map((test) => this.testUnit(file, test, options));
11338
+ return this.testsIn(file).filter((test) => (0, import_extractor16.isListedTestFile)(file.file, test.pattern.files)).map((test) => this.testUnit(file, test, options));
11087
11339
  }
11088
11340
  /** What pytest calls before this function runs, when it is a test or a fixture. */
11089
11341
  impliedCalls(source) {
@@ -11169,7 +11421,7 @@ var PythonTestIndex = class {
11169
11421
  }
11170
11422
  patternFor(file) {
11171
11423
  return this.patterns.find(
11172
- (pattern) => (0, import_extractor15.matchesTestFileName)(file.file, pattern.filePatterns)
11424
+ (pattern) => (0, import_extractor16.matchesTestFileName)(file.file, pattern.filePatterns)
11173
11425
  ) ?? null;
11174
11426
  }
11175
11427
  testsIn(file) {
@@ -11722,15 +11974,15 @@ function oneLine(text2) {
11722
11974
  }
11723
11975
 
11724
11976
  // src/version.ts
11725
- var import_extractor16 = require("@suss/extractor");
11977
+ var import_extractor17 = require("@suss/extractor");
11726
11978
  var ADAPTER_VERSION = "0.1.0";
11727
- var adapterStamp = (0, import_extractor16.createAdapterStamp)({
11979
+ var adapterStamp = (0, import_extractor17.createAdapterStamp)({
11728
11980
  moduleUrl: importMetaUrl,
11729
11981
  version: ADAPTER_VERSION
11730
11982
  });
11731
11983
 
11732
11984
  // src/wrappers.ts
11733
- var import_extractor17 = require("@suss/extractor");
11985
+ var import_extractor18 = require("@suss/extractor");
11734
11986
  var import_resolution17 = require("@suss/resolution");
11735
11987
  var EVERY_ROUTE = "wrapEvery ";
11736
11988
  var OWN_ROUTES = "wrapOwn ";
@@ -12289,7 +12541,7 @@ function coverageOf(match, pack) {
12289
12541
  return match.registrar.covers === "everyRoute" ? { kind: "everyRoute", pack: pack.name } : { kind: "ownRoutes", key: match.key };
12290
12542
  }
12291
12543
  function registerDecorated(file, declared, form, index) {
12292
- (0, import_extractor17.walkDescendants)(file.root, file.module.moduleScope, children, {
12544
+ (0, import_extractor18.walkDescendants)(file.root, file.module.moduleScope, children, {
12293
12545
  at: (node, scope) => {
12294
12546
  if (node.type !== "decorated_definition") {
12295
12547
  return;
@@ -12417,7 +12669,7 @@ function unitAround(target, pattern, reading, options) {
12417
12669
  });
12418
12670
  const continuations = reading.continuations;
12419
12671
  const terminals = bodyTerminals(body, raised, continuations);
12420
- const effects = invocationEffects(node, options.facts);
12672
+ const effects = invocationSites(node, options.facts);
12421
12673
  const extra = recognizedBodyEffects(
12422
12674
  node,
12423
12675
  file.module,
@@ -12441,9 +12693,9 @@ function unitAround(target, pattern, reading, options) {
12441
12693
  });
12442
12694
  const respond = (statement) => returnBranch(statement, {
12443
12695
  pattern,
12444
- responseShape: { reading: import_extractor17.absentReading },
12696
+ responseShape: { reading: import_extractor18.absentReading },
12445
12697
  declaredStatus: {
12446
- reading: import_extractor17.absentReading,
12698
+ reading: import_extractor18.absentReading,
12447
12699
  ...pattern.defaultStatusCode !== void 0 ? { libraryDefault: pattern.defaultStatusCode } : {}
12448
12700
  },
12449
12701
  module: file.module,
@@ -12554,7 +12806,7 @@ function closureReturnedBy(decorator) {
12554
12806
 
12555
12807
  // src/project.ts
12556
12808
  function keptPythonParses() {
12557
- return new import_extractor18.KeptParses(({ root }) => root.tree.delete());
12809
+ return new import_extractor19.KeptParses(({ root }) => root.tree.delete());
12558
12810
  }
12559
12811
  async function parsePythonAhead(files, kept) {
12560
12812
  for (const file of files) {
@@ -12643,16 +12895,16 @@ function factsForFile(options) {
12643
12895
  }
12644
12896
  async function extractPythonProject(options) {
12645
12897
  const { roots, unreadManifests } = rootsOfRun(options);
12646
- const timer = options.onTiming !== void 0 ? (0, import_extractor18.createTimer)() : (0, import_extractor18.noopTimer)();
12898
+ const timer = options.onTiming !== void 0 ? (0, import_extractor19.createTimer)() : (0, import_extractor19.noopTimer)();
12647
12899
  const cacheDir = adapterStamp.declineWhenRunFromSource(
12648
- options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? (0, import_extractor18.extractionCacheDirFor)(options.projectRoot) : null)
12900
+ options.cacheDir === null ? null : options.cacheDir ?? (options.projectRoot !== void 0 ? (0, import_extractor19.extractionCacheDirFor)(options.projectRoot) : null)
12649
12901
  );
12650
- const layer = (0, import_extractor18.createCacheLayer)(cacheDir);
12902
+ const layer = (0, import_extractor19.createCacheLayer)(cacheDir);
12651
12903
  const packsDigest = `${adapterStamp.packsDigest(
12652
12904
  options.packs.map(
12653
12905
  (pack) => pack.version !== void 0 ? { name: pack.name, version: pack.version } : { name: pack.name }
12654
12906
  )
12655
- )}|${(0, import_extractor18.extractionConfigStamp)({
12907
+ )}|${(0, import_extractor19.extractionConfigStamp)({
12656
12908
  gapHandling: options.gapHandling,
12657
12909
  workspaceRoot: options.workspaceRoot,
12658
12910
  projectRoot: options.projectRoot,
@@ -12662,13 +12914,13 @@ async function extractPythonProject(options) {
12662
12914
  })}`;
12663
12915
  const input = {
12664
12916
  files: cacheDir === null ? [] : options.files,
12665
- adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor18.runDigest)(packsDigest, options.packs, options.files)
12917
+ adapterPacksDigest: cacheDir === null ? packsDigest : (0, import_extractor19.runDigest)(packsDigest, options.packs, options.files)
12666
12918
  };
12667
12919
  const lookup = await timer.timeAsync(
12668
12920
  "cache.lookup",
12669
12921
  () => layer.lookup(input)
12670
12922
  );
12671
- const composed = (stored) => (0, import_extractor18.composeWrappers)(stored, {
12923
+ const composed = (stored) => (0, import_extractor19.composeWrappers)(stored, {
12672
12924
  gapHandling: options.gapHandling ?? "permissive"
12673
12925
  });
12674
12926
  if (lookup.kind === "hit") {
@@ -12752,7 +13004,9 @@ async function runPython(options, roots, cache, previous) {
12752
13004
  const sqlClients = options.packs.flatMap((pack) => pack.sqlClients ?? []);
12753
13005
  const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
12754
13006
  const declaredModules = settlePythonModules(options.modules);
12755
- const discovers = declaredModules.length > 0 || options.packs.some((pack) => pack.discovery.length > 0);
13007
+ const discovers = declaredModules.length > 0 || options.packs.some(
13008
+ (pack) => pack.discovery.length > 0 || pack.entryPoints !== void 0
13009
+ );
12756
13010
  const buildsReceivers = options.packs.some(
12757
13011
  (pack) => (pack.clients ?? []).some(
12758
13012
  (client) => (client.receiverConstructors ?? []).length > 0
@@ -12915,6 +13169,10 @@ async function runPython(options, roots, cache, previous) {
12915
13169
  facts: db,
12916
13170
  storageFor
12917
13171
  }),
13172
+ ...declaredEntryUnits(boundFile, options.packs, {
13173
+ facts: db,
13174
+ storageFor
13175
+ }),
12918
13176
  ...tests === null ? [] : tests.unitsIn(boundFile, { storageFor, facts: db })
12919
13177
  ];
12920
13178
  const charge = (0, import_resolution18.noDependencies)();
@@ -12977,7 +13235,7 @@ async function runPython(options, roots, cache, previous) {
12977
13235
  const assemble = (raw, boundFile) => {
12978
13236
  const summary = timer.time(
12979
13237
  "summarize",
12980
- () => (0, import_extractor18.assembleSummary)(raw, { gapHandling })
13238
+ () => (0, import_extractor19.assembleSummary)(raw, { gapHandling })
12981
13239
  );
12982
13240
  summaries.push(summary);
12983
13241
  assembledHere.add(summary);
@@ -13024,7 +13282,10 @@ async function runPython(options, roots, cache, previous) {
13024
13282
  }
13025
13283
  const loadTimeReads = record !== void 0 && discoveries.get(file) === record ? null : timer.time(
13026
13284
  "discover",
13027
- () => charged(file, () => envReadEffects(root, moduleBinding, db))
13285
+ () => charged(
13286
+ file,
13287
+ () => envReadEffects(root, moduleBinding, db).map((one) => one.effect)
13288
+ )
13028
13289
  );
13029
13290
  const moduleKey = nodeId(file, root);
13030
13291
  if (!summariesBySeed.has(moduleKey)) {
@@ -13103,8 +13364,8 @@ async function runPython(options, roots, cache, previous) {
13103
13364
  }
13104
13365
  const summary = timer.time(
13105
13366
  "summarize",
13106
- () => (0, import_extractor18.assembleSummary)(
13107
- (0, import_extractor18.moduleInitStructure)({
13367
+ () => (0, import_extractor19.assembleSummary)(
13368
+ (0, import_extractor19.moduleInitStructure)({
13108
13369
  name: import_node_path5.default.basename(displayPath),
13109
13370
  file: displayPath,
13110
13371
  range: rangeOf(boundFile.root),
@@ -13113,7 +13374,7 @@ async function runPython(options, roots, cache, previous) {
13113
13374
  ...charged(
13114
13375
  boundFile.file,
13115
13376
  () => moduleLoadInvocationEffects(boundFile.root, db)
13116
- ).map(import_extractor18.effectToIR)
13377
+ ).map(import_extractor19.effectToIR)
13117
13378
  ]
13118
13379
  }),
13119
13380
  { gapHandling }
@@ -13127,14 +13388,15 @@ async function runPython(options, roots, cache, previous) {
13127
13388
  });
13128
13389
  }
13129
13390
  summaries.push(...reached.summaries);
13391
+ summaries.push(...libraryEnvReadMarkers(bound, options.packs));
13130
13392
  const spend = (0, import_resolution18.underQuestionSpend)(db);
13131
13393
  const overBudget = spend.abandoned + spend.skipped > 0;
13132
13394
  if (overBudget && reuse !== null) {
13133
13395
  throw new ReplayFailed("");
13134
13396
  }
13135
13397
  const resolvedImports = (0, import_resolution18.importedFilesByFile)(db, displayPathOf);
13136
- (0, import_extractor18.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);
13137
- (0, import_extractor18.stampModules)(summaries, declaredModules, options.workspaceRoot);
13398
+ (0, import_extractor19.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);
13399
+ (0, import_extractor19.stampModules)(summaries, declaredModules, options.workspaceRoot);
13138
13400
  const idRoot = options.projectRoot ?? options.workspaceRoot;
13139
13401
  for (const summary of summaries) {
13140
13402
  const absoluteFile = options.workspaceRoot === void 0 ? summary.location.file : import_node_path5.default.resolve(options.workspaceRoot, summary.location.file);
@@ -13149,7 +13411,7 @@ async function runPython(options, roots, cache, previous) {
13149
13411
  (0, import_behavioral_ir10.linkCallsToSummaries)(summaries);
13150
13412
  const composed = timer.time("summarize", () => {
13151
13413
  markExitCodeFunctions(summaries, exits, db, options.workspaceRoot);
13152
- return (0, import_extractor18.composeWrappers)(summaries, { gapHandling });
13414
+ return (0, import_extractor19.composeWrappers)(summaries, { gapHandling });
13153
13415
  });
13154
13416
  const walkedFiles = new Map(
13155
13417
  bound.map(({ file, displayPath }) => [displayPath, file])
@@ -13282,7 +13544,7 @@ function pythonAttribution(args) {
13282
13544
  });
13283
13545
  return {
13284
13546
  roots,
13285
- owners: (0, import_extractor18.ownersWhereWritten)(args.summaries, args.walkedFileOf)
13547
+ owners: (0, import_extractor19.ownersWhereWritten)(args.summaries, args.walkedFileOf)
13286
13548
  };
13287
13549
  }
13288
13550
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([