@suss/adapter-ruby 0.30.0 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -44,6 +44,7 @@ __export(index_exports, {
44
44
  emitValueFacts: () => emitValueFacts,
45
45
  evaluatedValue: () => evaluatedValue,
46
46
  extractRubyProject: () => extractRubyProject,
47
+ factsForFile: () => factsForFile,
47
48
  field: () => field,
48
49
  findRubyFiles: () => findRubyFiles,
49
50
  graphqlTypeNameFromQualified: () => graphqlTypeNameFromQualified,
@@ -407,7 +408,7 @@ function autoloadDirectories(root) {
407
408
 
408
409
  // src/defineMethod.ts
409
410
  var import_resolution5 = require("@suss/resolution");
410
- var import_values4 = require("@suss/values");
411
+ var import_values5 = require("@suss/values");
411
412
 
412
413
  // src/values/evaluator.ts
413
414
  var import_resolution4 = require("@suss/resolution");
@@ -416,58 +417,45 @@ var import_values2 = require("@suss/values");
416
417
  // src/facts/resolve.ts
417
418
  var import_datalog = require("@suss/datalog");
418
419
  var import_resolution = require("@suss/resolution");
419
- var RUBY_RULES = [
420
- // `Loader.new` makes one of the class, which the shared rules already say
421
- // about calling a class. Ruby writes it as a method read off the constant
422
- // instead, so the read is what steps to the class here.
420
+ var RUBY_RULES = (0, import_resolution.alsoSteps)([
421
+ // Making one of a class is a call of the class, which the shared rules
422
+ // already say. Ruby writes the callee as `new` read off the constant, so
423
+ // what this adds is that the callee is the class.
423
424
  (0, import_datalog.rule)(
424
- "stepsTo",
425
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("cls"), import_resolution.VALUE_STEP],
425
+ "hop",
426
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("cls"), import_resolution.INSTANCE_STEP],
426
427
  [
427
428
  (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.constant)("new")),
428
429
  (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("cls")),
429
430
  (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("cls"))
430
431
  ]
431
432
  ),
432
- // The shared `declared finder` step, for the same spelling: `Account.first`
433
- // and `Account.where(x).first` write no arguments, so Ruby reads them as
434
- // property reads and neither gets a `call` fact to hang that step on.
433
+ // The same spelling again, for the caller asking where a class was
434
+ // made. `callsNamed` reaches a callee written as the name itself, and
435
+ // Ruby writes `new` off the constant instead.
435
436
  (0, import_datalog.rule)(
436
- "stepsTo",
437
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("cls"), import_resolution.VALUE_STEP],
437
+ "constructsNamed",
438
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("cls")],
438
439
  [
439
- (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.variable)("m")),
440
- (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("cls")),
441
- (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("cls")),
442
- (0, import_datalog.lit)("libraryBase", (0, import_datalog.variable)("cls"), (0, import_datalog.variable)("n")),
443
- (0, import_datalog.lit)("givesBackOne", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("m"))
444
- ],
445
- "declared finder"
446
- ),
447
- // Ruby cannot refer to a method without running it, so
448
- // `Settings.filters` is worth what the method returns. The shared
449
- // rules hang that step on a `call` fact this spelling never gets.
450
- (0, import_datalog.rule)(
451
- "invokes",
452
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("f")],
453
- [
454
- (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.variable)("m")),
455
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("f")),
456
- (0, import_datalog.lit)("func", (0, import_datalog.variable)("f"))
457
- ],
458
- "property read runs a method"
440
+ (0, import_datalog.lit)("binds", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("cls")),
441
+ (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("o"), (0, import_datalog.constant)("new")),
442
+ (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c"))
443
+ ]
459
444
  ),
460
445
  // `%i[a b].freeze` is worth the list it was written as. The evaluator
461
446
  // says the same in its row table, for a value it reads in one file.
462
447
  ...["freeze", "dup"].map(
463
448
  (method) => (0, import_datalog.rule)(
464
- "stepsTo",
465
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), import_resolution.VALUE_STEP],
466
- [(0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.constant)(method))],
449
+ "hop",
450
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("o"), import_resolution.VALUE_STEP],
451
+ [
452
+ (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
453
+ (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("o"), (0, import_datalog.constant)(method))
454
+ ],
467
455
  "hands back the receiver"
468
456
  )
469
457
  )
470
- ];
458
+ ]);
471
459
  var RUBY_PROGRAM = (0, import_resolution.resolutionProgram)(RUBY_RULES);
472
460
  function resolveValues(db, keys) {
473
461
  (0, import_resolution.askResolution)(db, keys, "wanted", RUBY_PROGRAM);
@@ -479,10 +467,125 @@ function writtenValueOf(db, key) {
479
467
  resolveValues(db, [key]);
480
468
  return (0, import_resolution.writtenValueOf)(db, key, (keys) => resolveValues(db, keys));
481
469
  }
470
+ function writtenValueUnder(db, key, site) {
471
+ const outcome = (0, import_resolution.askResolutionUnder)(
472
+ db,
473
+ [[key, site]],
474
+ (0, import_resolution.resolutionUnderProgram)(RUBY_RULES)
475
+ );
476
+ if (outcome === "abandoned") {
477
+ return null;
478
+ }
479
+ return (0, import_resolution.writtenValueUnder)(db, key, site);
480
+ }
481
+ function constructionSites(db, classKey) {
482
+ (0, import_resolution.askResolution)(db, [classKey], "wantedSites", RUBY_PROGRAM);
483
+ return (0, import_resolution.allocationSitesOf)(db, classKey);
484
+ }
482
485
 
483
486
  // src/facts/values.ts
484
487
  var import_resolution3 = require("@suss/resolution");
485
488
 
489
+ // src/paths/bareCalls.ts
490
+ var PARAMETER_LIST_TYPES = [
491
+ "method_parameters",
492
+ "block_parameters",
493
+ "lambda_parameters",
494
+ "parameters"
495
+ ];
496
+ function isFieldOf(parent, node, name) {
497
+ return field(parent, name)?.id === node.id;
498
+ }
499
+ function namedAt(parent, node) {
500
+ return isFieldOf(parent, node, "name");
501
+ }
502
+ function always() {
503
+ return true;
504
+ }
505
+ var SPELLS_A_NAME = {
506
+ call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
507
+ method: namedAt,
508
+ singleton_method: namedAt,
509
+ assignment: (parent, node) => isFieldOf(parent, node, "left"),
510
+ operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
511
+ for: (parent, node) => isFieldOf(parent, node, "pattern"),
512
+ optional_parameter: namedAt,
513
+ splat_parameter: namedAt,
514
+ hash_splat_parameter: namedAt,
515
+ keyword_parameter: namedAt,
516
+ block_parameter: namedAt,
517
+ destructured_parameter: always,
518
+ left_assignment_list: always,
519
+ rest_assignment: always,
520
+ destructured_left_assignment: always,
521
+ exception_variable: always,
522
+ undef: always,
523
+ alias: always,
524
+ ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
525
+ };
526
+ function addNamesUnder(node, names) {
527
+ if (node === null) {
528
+ return;
529
+ }
530
+ if (node.type === "identifier") {
531
+ names.add(node.text);
532
+ return;
533
+ }
534
+ for (const child of bodyStatements(node)) {
535
+ addNamesUnder(child, names);
536
+ }
537
+ }
538
+ function parameterNames(node, names) {
539
+ for (const parameter of bodyStatements(node)) {
540
+ if (parameter.type === "identifier") {
541
+ names.add(parameter.text);
542
+ continue;
543
+ }
544
+ const declared = field(parameter, "name");
545
+ if (declared !== null && declared.type === "identifier") {
546
+ names.add(declared.text);
547
+ continue;
548
+ }
549
+ addNamesUnder(parameter, names);
550
+ }
551
+ }
552
+ function assignedNames(node, names) {
553
+ addNamesUnder(field(node, "left"), names);
554
+ }
555
+ var BINDS_A_NAME = {
556
+ assignment: assignedNames,
557
+ operator_assignment: assignedNames,
558
+ for: (node, names) => addNamesUnder(field(node, "pattern"), names),
559
+ exception_variable: (node, names) => addNamesUnder(node, names),
560
+ ...Object.fromEntries(
561
+ PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
562
+ )
563
+ };
564
+ function localNamesIn(definitionNode) {
565
+ const names = /* @__PURE__ */ new Set();
566
+ const visit = (node) => {
567
+ BINDS_A_NAME[node.type]?.(node, names);
568
+ for (const child of bodyStatements(node)) {
569
+ visit(child);
570
+ }
571
+ };
572
+ visit(definitionNode);
573
+ return names;
574
+ }
575
+ function spellsAName(node) {
576
+ const parent = node.parent;
577
+ if (parent === null) {
578
+ return true;
579
+ }
580
+ return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
581
+ }
582
+ function isBareMethodCall(node, locals) {
583
+ if (node.type !== "identifier" || locals.has(node.text)) {
584
+ return false;
585
+ }
586
+ return !spellsAName(node);
587
+ }
588
+
486
589
  // src/facts/locals.ts
487
590
  var import_resolution2 = require("@suss/resolution");
488
591
  var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
@@ -507,7 +610,7 @@ function parametersOf(owner) {
507
610
  const params = field(owner, "parameters");
508
611
  return params === null ? [] : bodyStatements(params);
509
612
  }
510
- function parameterNames(owner) {
613
+ function parameterNames2(owner) {
511
614
  const names = /* @__PURE__ */ new Set();
512
615
  for (const param of parametersOf(owner)) {
513
616
  const name = paramNameOf(param);
@@ -555,6 +658,13 @@ function readForVariable(node) {
555
658
  }
556
659
  return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
557
660
  }
661
+ function readRescueVariable(node) {
662
+ const variable = field(node, "variable");
663
+ if (variable === null) {
664
+ return [];
665
+ }
666
+ return bodyStatements(variable).flatMap(targetsOf).map((target) => writeOf(target, null, node, false));
667
+ }
558
668
  function readBlockParameters(node) {
559
669
  const found = [];
560
670
  for (const param of parametersOf(node)) {
@@ -569,6 +679,7 @@ var WRITE_READERS = {
569
679
  assignment: readAssignment,
570
680
  operator_assignment: readOperatorAssignment,
571
681
  for: readForVariable,
682
+ rescue: readRescueVariable,
572
683
  block: readBlockParameters,
573
684
  do_block: readBlockParameters
574
685
  };
@@ -614,7 +725,7 @@ function namesOfMethod(method) {
614
725
  if (remembered !== void 0) {
615
726
  return remembered;
616
727
  }
617
- const names = scopeNames(parameterNames(method), field(method, "body"));
728
+ const names = scopeNames(parameterNames2(method), field(method, "body"));
618
729
  cache.byMethod.set(method, names);
619
730
  return names;
620
731
  }
@@ -636,10 +747,17 @@ function ownerOfName(node, name, enclosing) {
636
747
  }
637
748
  return enclosing !== null && names.locals.has(name) ? enclosing : null;
638
749
  }
750
+ function isLocalName(node, name, enclosing) {
751
+ const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
752
+ if (names.blockParams.has(name) && blockDeclaring(node, name) !== null) {
753
+ return true;
754
+ }
755
+ return names.locals.has(name);
756
+ }
639
757
  function blockDeclaring(node, name) {
640
758
  let current = node.parent;
641
759
  while (current !== null) {
642
- if (BLOCK_TYPES2.has(current.type) && parameterNames(current).has(name)) {
760
+ if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
643
761
  return current;
644
762
  }
645
763
  if (OWN_BODY_TYPES.has(current.type)) {
@@ -695,6 +813,17 @@ function collectWrites(method, body) {
695
813
  }
696
814
  return [...groups.values()];
697
815
  }
816
+ function instanceWritesRunInOrder(body, name, targets) {
817
+ return (0, import_resolution2.writesRunInOrder)(
818
+ body,
819
+ name,
820
+ targets.map((target) => ({
821
+ at: target,
822
+ direct: target.parent?.parent?.id === body.id
823
+ })),
824
+ nameReads(new Set(targets.map((target) => target.id)))
825
+ );
826
+ }
698
827
  function orderedWrite(write, scope) {
699
828
  return {
700
829
  at: write.at,
@@ -740,12 +869,28 @@ function readKey(filePath, written, enclosing) {
740
869
  if (node.type !== "identifier" && node.type !== "constant") {
741
870
  return nodeId(filePath, node);
742
871
  }
743
- const parent = node.parent;
744
- if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
745
- return isPropertyRead(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
872
+ if (isMethodOfReceiver(node) || isBareCall(node, enclosing)) {
873
+ return nodeId(filePath, node);
746
874
  }
747
875
  return nameKey(filePath, node, enclosing);
748
876
  }
877
+ function isMethodOfReceiver(node) {
878
+ const parent = node.parent;
879
+ return parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null;
880
+ }
881
+ function isReceiverOfCall(node) {
882
+ const parent = node.parent;
883
+ return parent !== null && parent.type === "call" && field(parent, "receiver")?.id === node.id;
884
+ }
885
+ function isBareCall(node, enclosing) {
886
+ if (node.type !== "identifier") {
887
+ return false;
888
+ }
889
+ if (spellsAName(node) && !isReceiverOfCall(node)) {
890
+ return false;
891
+ }
892
+ return !isLocalName(node, node.text, enclosing);
893
+ }
749
894
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
750
895
  "string",
751
896
  "integer",
@@ -774,11 +919,7 @@ function add(emitter, relation, ...tuple) {
774
919
  emitter.db.add(relation, tuple);
775
920
  }
776
921
  function valueKey(emitter, written) {
777
- const value = readThrough(written);
778
- if (value.type !== "identifier" && value.type !== "constant") {
779
- return nodeId(emitter.filePath, value);
780
- }
781
- return nameKey(emitter.filePath, value, emitter.enclosing);
922
+ return readKey(emitter.filePath, written, emitter.enclosing);
782
923
  }
783
924
  function pairKeyText(key) {
784
925
  if (key.type === "hash_key_symbol") {
@@ -792,35 +933,29 @@ function pairKeyText(key) {
792
933
  }
793
934
  return null;
794
935
  }
795
- function isPropertyRead(node) {
796
- return node.type === "call" && field(node, "receiver") !== null && field(node, "arguments") === null && children(node).every(
797
- (child) => child.type !== "do_block" && child.type !== "block"
798
- );
799
- }
800
- function emitPropertyRead(emitter, node) {
801
- const receiver = field(node, "receiver");
802
- const method = field(node, "method");
803
- if (receiver === null || method === null) {
804
- return;
936
+ function calleeKeyOf(filePath, node, enclosing) {
937
+ if (isBareCall(node, enclosing)) {
938
+ return nameKey(filePath, node, enclosing);
805
939
  }
806
- add(
807
- emitter,
808
- "readsProperty",
809
- nodeId(emitter.filePath, node),
810
- valueKey(emitter, receiver),
811
- method.text
812
- );
940
+ const method = node.type === "call" ? field(node, "method") : null;
941
+ if (method === null) {
942
+ return null;
943
+ }
944
+ return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
813
945
  }
814
946
  function emitCall(emitter, call) {
815
947
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
816
- const method = field(call, "method");
817
- if (method === null) {
948
+ const method = call.type === "identifier" ? call : field(call, "method");
949
+ const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
950
+ if (method === null || calleeKey === null) {
818
951
  return;
819
952
  }
820
953
  const receiver = field(call, "receiver");
821
954
  const callKey = nodeId(emitter.filePath, call);
822
- const calleeKey = receiver === null ? valueKey(emitter, method) : nodeId(emitter.filePath, method);
823
955
  add(emitter, "call", callKey, calleeKey);
956
+ if (!emitter.insideMethod) {
957
+ add(emitter, "callOutsideMethod", callKey);
958
+ }
824
959
  if (receiver !== null) {
825
960
  add(
826
961
  emitter,
@@ -829,6 +964,8 @@ function emitCall(emitter, call) {
829
964
  valueKey(emitter, receiver),
830
965
  method.text
831
966
  );
967
+ } else if (emitter.selfKey !== null) {
968
+ add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
832
969
  }
833
970
  const args = field(call, "arguments");
834
971
  let position = 0;
@@ -953,9 +1090,7 @@ function emitExpressionFacts(emitter, node) {
953
1090
  emitDynamicDefinition(emitter, node, []);
954
1091
  walkExpressions(node, emitter, (child, turns) => {
955
1092
  emitDynamicDefinition(emitter, child, turns);
956
- if (isPropertyRead(child)) {
957
- emitPropertyRead(emitter, child);
958
- } else if (child.type === "call") {
1093
+ if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
959
1094
  emitCall(emitter, child);
960
1095
  }
961
1096
  if (ARRAY_TYPES.has(child.type)) {
@@ -1011,28 +1146,52 @@ function collectInstanceWrite(emitter, node) {
1011
1146
  const operator = field(node, "operator")?.text;
1012
1147
  const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
1013
1148
  const written = collector.get(left.text) ?? [];
1014
- written.push(
1015
- describeWrite(emitter, {
1149
+ written.push({
1150
+ write: describeWrite(emitter, {
1016
1151
  name: left.text,
1017
1152
  target: left,
1018
1153
  value,
1019
1154
  at: node,
1020
1155
  fromParameter: false
1021
- })
1022
- );
1156
+ }),
1157
+ target: left
1158
+ });
1023
1159
  collector.set(left.text, written);
1024
1160
  }
1161
+ function settledWrites(writes, ordered) {
1162
+ const described = writes.map((written) => written.write);
1163
+ const settled = (0, import_resolution3.valueLeftByWrites)(described, ordered);
1164
+ if (settled !== null) {
1165
+ return [settled];
1166
+ }
1167
+ const left = [];
1168
+ for (const write of described) {
1169
+ if (write.value !== null && !write.narrowsName) {
1170
+ left.push(write.value);
1171
+ }
1172
+ }
1173
+ return left;
1174
+ }
1025
1175
  function emitInstanceWrites(emitter, classKey, collected) {
1026
1176
  for (const [name, writes] of collected) {
1027
- const settled = (0, import_resolution3.valueLeftByWrites)(writes, false);
1028
- if (settled !== null) {
1029
- add(emitter, "holdsProperty", classKey, name, settled);
1177
+ for (const value of settledWrites(writes, false)) {
1178
+ add(emitter, "holdsProperty", classKey, name, value);
1179
+ }
1180
+ }
1181
+ }
1182
+ function emitInstanceStores(emitter, funcKey, body, collected) {
1183
+ for (const [name, writes] of collected) {
1184
+ const deciding = writes.filter((written) => !written.write.narrowsName);
1185
+ if (deciding.length === 0) {
1030
1186
  continue;
1031
1187
  }
1032
- for (const write of writes) {
1033
- if (write.value !== null && !write.narrowsName) {
1034
- add(emitter, "holdsProperty", classKey, name, write.value);
1035
- }
1188
+ const ordered = instanceWritesRunInOrder(
1189
+ body,
1190
+ name,
1191
+ deciding.map((written) => written.target)
1192
+ );
1193
+ for (const value of settledWrites(deciding, ordered)) {
1194
+ add(emitter, "storesProperty", funcKey, name, value);
1036
1195
  }
1037
1196
  }
1038
1197
  }
@@ -1092,7 +1251,7 @@ function emitMethodFacts(emitter, method) {
1092
1251
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1093
1252
  }
1094
1253
  }
1095
- if (child.type === "call") {
1254
+ if (child.type === "call" || isBareCall(child, method)) {
1096
1255
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
1097
1256
  }
1098
1257
  });
@@ -1160,20 +1319,32 @@ function emitScopeWrites(emitter, method, body) {
1160
1319
  }
1161
1320
  }
1162
1321
  function emitCandidates(emitter, key, group) {
1322
+ let unstated = false;
1323
+ let stated = 0;
1163
1324
  for (const write of group.writes) {
1164
1325
  if (write.value === null) {
1165
1326
  add(emitter, "writesUnstated", key);
1327
+ unstated = true;
1166
1328
  continue;
1167
1329
  }
1168
1330
  if (write.fromParameter) {
1331
+ unstated = true;
1169
1332
  continue;
1170
1333
  }
1171
1334
  if (!describeWrite(emitter, write).narrowsName) {
1172
1335
  add(emitter, "mayHold", key, valueKey(emitter, write.value));
1336
+ stated += 1;
1173
1337
  }
1174
1338
  }
1339
+ if (!unstated && stated > 0) {
1340
+ add(emitter, "writesAllStated", key);
1341
+ }
1175
1342
  }
1176
1343
  var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1344
+ var INITIALIZE_METHOD = "initialize";
1345
+ function receiverKeyOf(filePath, method, classKey) {
1346
+ return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
1347
+ }
1177
1348
  var CONSTANT_REF_TYPES = /* @__PURE__ */ new Set(["constant", "scope_resolution"]);
1178
1349
  function mixedInConstants(body, callName) {
1179
1350
  return bareCallArgumentGroups(body, callName).flatMap((group) => [...group].reverse()).reverse().map(readThrough).filter((argument) => CONSTANT_REF_TYPES.has(argument.type));
@@ -1201,6 +1372,7 @@ function emitClassFacts(emitter, cls) {
1201
1372
  const within = {
1202
1373
  ...emitter,
1203
1374
  selfKey: classKey,
1375
+ insideMethod: false,
1204
1376
  instanceWrites: collected
1205
1377
  };
1206
1378
  const statements = body === null ? [] : bodyStatementsRun(body, cls.type === "module", emitter.bodyBlocks);
@@ -1228,10 +1400,30 @@ function emitClassFacts(emitter, cls) {
1228
1400
  emitExpressionFacts(within, statement);
1229
1401
  continue;
1230
1402
  }
1231
- const funcKey = emitMethodFacts(within, statement);
1403
+ const stored = /* @__PURE__ */ new Map();
1404
+ const receiverKey = receiverKeyOf(emitter.filePath, statement, classKey);
1405
+ if (receiverKey !== classKey) {
1406
+ add(emitter, "instanceOf", receiverKey, classKey);
1407
+ }
1408
+ const funcKey = emitMethodFacts(
1409
+ {
1410
+ ...within,
1411
+ selfKey: receiverKey,
1412
+ insideMethod: receiverKey !== classKey,
1413
+ instanceWrites: stored
1414
+ },
1415
+ statement
1416
+ );
1232
1417
  const name = field(statement, "name");
1233
1418
  if (name !== null) {
1234
1419
  add(emitter, "holdsProperty", classKey, name.text, funcKey);
1420
+ if (name.text === INITIALIZE_METHOD) {
1421
+ add(emitter, "initializes", classKey, funcKey);
1422
+ }
1423
+ }
1424
+ const methodBody = field(statement, "body");
1425
+ if (methodBody !== null) {
1426
+ emitInstanceStores(within, funcKey, methodBody, stored);
1235
1427
  }
1236
1428
  }
1237
1429
  emitInstanceWrites(within, classKey, collected);
@@ -1243,6 +1435,7 @@ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
1243
1435
  filePath,
1244
1436
  enclosing: null,
1245
1437
  selfKey: null,
1438
+ insideMethod: false,
1246
1439
  instanceWrites: null,
1247
1440
  bodyBlocks
1248
1441
  };
@@ -1323,7 +1516,7 @@ function rubyLowering(options) {
1323
1516
  statement: statementOf,
1324
1517
  siteOf,
1325
1518
  functionOf,
1326
- writtenTo: (node) => context === null ? null : context.writtenTo(node),
1519
+ writtenTo: (node, site) => context === null ? null : context.writtenTo(node, site),
1327
1520
  callable: (node) => {
1328
1521
  if (!isInlinableCall(node)) {
1329
1522
  return null;
@@ -1840,17 +2033,32 @@ function siteOf(node) {
1840
2033
  current = parent;
1841
2034
  }
1842
2035
  }
2036
+ var BARE_SPLAT_TYPES = /* @__PURE__ */ new Set(["splat_parameter", "hash_splat_parameter"]);
1843
2037
  function parametersOf2(parameters) {
1844
2038
  if (parameters === null) {
1845
2039
  return [];
1846
2040
  }
1847
- return named(parameters).flatMap((parameter) => {
2041
+ const declared = [];
2042
+ let position = 0;
2043
+ for (const parameter of named(parameters)) {
2044
+ if (BARE_SPLAT_TYPES.has(parameter.type) && field(parameter, "name") === null) {
2045
+ continue;
2046
+ }
1848
2047
  if (parameter.type === "identifier") {
1849
- return [{ name: parameter.text, default: null }];
2048
+ declared.push({ name: parameter.text, default: null, position });
2049
+ } else {
2050
+ const name = field(parameter, "name");
2051
+ if (name !== null) {
2052
+ declared.push({
2053
+ name: name.text,
2054
+ default: field(parameter, "value"),
2055
+ position
2056
+ });
2057
+ }
1850
2058
  }
1851
- const name = field(parameter, "name");
1852
- return name === null ? [] : [{ name: name.text, default: field(parameter, "value") }];
1853
- });
2059
+ position += 1;
2060
+ }
2061
+ return declared;
1854
2062
  }
1855
2063
  function functionOf(node) {
1856
2064
  if (node.type === "program") {
@@ -2240,25 +2448,56 @@ var rubyRows = [
2240
2448
 
2241
2449
  // src/values/evaluator.ts
2242
2450
  var evaluators = /* @__PURE__ */ new WeakMap();
2451
+ var contexts = /* @__PURE__ */ new WeakMap();
2243
2452
  var withoutFacts = /* @__PURE__ */ new WeakMap();
2453
+ var filesByTree = /* @__PURE__ */ new WeakMap();
2244
2454
  var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
2245
2455
  function bindEvaluator(db, nodes) {
2246
- evaluators.set(
2247
- db,
2248
- new import_values2.Evaluator(
2249
- rubyLowering({ context: contextOver(db, nodes), rows: rubyRows })
2250
- )
2251
- );
2456
+ for (const entry of nodes.files) {
2457
+ filesByTree.set(entry.root.tree, entry.file);
2458
+ }
2459
+ const context = contextOver(db, nodes);
2460
+ contexts.set(db, context);
2461
+ evaluators.set(db, new import_values2.Evaluator(rubyLowering({ context, rows: rubyRows })));
2252
2462
  }
2253
- function evaluatedValue(node, db, bindings) {
2463
+ function writtenNodeOf(node, db, site) {
2464
+ const context = db === void 0 ? void 0 : contexts.get(db);
2465
+ return context === void 0 ? null : context.writtenTo(node, site);
2466
+ }
2467
+ function evaluatedValue(node, db, bindings, site) {
2254
2468
  const evaluator = evaluatorFor(node, db);
2255
2469
  if (bindings === void 0) {
2256
- return (0, import_values2.force)(evaluator.evaluate(node));
2470
+ return (0, import_values2.force)(
2471
+ site === void 0 ? evaluator.evaluate(node) : evaluator.evaluate(node, { site })
2472
+ );
2257
2473
  }
2258
2474
  const supplied = new Map(
2259
2475
  [...bindings].map(([name, value]) => [name, (0, import_values2.text)(value)])
2260
2476
  );
2261
- return (0, import_values2.force)(evaluator.evaluate(node, { bindings: supplied }));
2477
+ return (0, import_values2.force)(
2478
+ evaluator.evaluate(node, {
2479
+ bindings: supplied,
2480
+ ...site === void 0 ? {} : { site }
2481
+ })
2482
+ );
2483
+ }
2484
+ function constructionSitesOf(node, db) {
2485
+ const cls = enclosingClass(node);
2486
+ const file = cls === null ? void 0 : filesByTree.get(cls.tree);
2487
+ if (db === void 0 || cls === null || file === void 0) {
2488
+ return [];
2489
+ }
2490
+ return constructionSites(db, nodeId(file, cls));
2491
+ }
2492
+ function enclosingClass(node) {
2493
+ let current = node.parent;
2494
+ while (current !== null) {
2495
+ if (current.type === "class") {
2496
+ return current;
2497
+ }
2498
+ current = current.parent;
2499
+ }
2500
+ return null;
2262
2501
  }
2263
2502
  function stringValueOf(node, db, bindings) {
2264
2503
  return (0, import_values2.literalOf)(evaluatedValue(node, db, bindings));
@@ -2310,15 +2549,13 @@ function contextOver(db, nodes) {
2310
2549
  }
2311
2550
  const fileOf = (node) => filesByRoot.get(node.tree.rootNode.id)?.file ?? null;
2312
2551
  return {
2313
- writtenTo: (node) => {
2552
+ writtenTo: (node, site) => {
2314
2553
  const file = fileOf(node);
2315
2554
  if (file === null) {
2316
2555
  return null;
2317
2556
  }
2318
- const answer = writtenValueOf(
2319
- db,
2320
- readKey(file, node, enclosingMethod(node))
2321
- );
2557
+ const key = readKey(file, node, enclosingMethod(node));
2558
+ const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
2322
2559
  return answer === null ? null : (0, import_resolution4.nodeOfKey)(rootsByFile, answer);
2323
2560
  },
2324
2561
  callable: (call) => {
@@ -2336,6 +2573,31 @@ function contextOver(db, nodes) {
2336
2573
  };
2337
2574
  }
2338
2575
 
2576
+ // src/values/literals.ts
2577
+ var import_values4 = require("@suss/values");
2578
+ function literalElementsIn(value) {
2579
+ if (value.kind !== "sequence") {
2580
+ return null;
2581
+ }
2582
+ const elements = [];
2583
+ for (const item of value.items) {
2584
+ const literal = (0, import_values4.literalOf)((0, import_values4.force)(item.value));
2585
+ if (literal === null) {
2586
+ return null;
2587
+ }
2588
+ elements.push(literal);
2589
+ }
2590
+ return elements;
2591
+ }
2592
+ function literalElementsOf(node, db) {
2593
+ return literalElementsIn(evaluatedValue(node, db));
2594
+ }
2595
+ function namesOf(node, db) {
2596
+ const value = evaluatedValue(node, db);
2597
+ const single = (0, import_values4.literalOf)(value);
2598
+ return single === null ? literalElementsIn(value) : [single];
2599
+ }
2600
+
2339
2601
  // src/defineMethod.ts
2340
2602
  var NOTHING = {
2341
2603
  names: /* @__PURE__ */ new Set(),
@@ -2382,8 +2644,8 @@ function readOne(db, rootsByFile, nameKey2) {
2382
2644
  const names = [];
2383
2645
  const patterns = [];
2384
2646
  for (const bindings of loopTurns(db, rootsByFile, nameKey2)) {
2385
- const value = node === null ? (0, import_values4.hole)(nameKey2) : evaluatedValue(node, db, bindings.size === 0 ? void 0 : bindings);
2386
- const name = (0, import_values4.literalOf)(value);
2647
+ const value = node === null ? (0, import_values5.hole)(nameKey2) : evaluatedValue(node, db, bindings.size === 0 ? void 0 : bindings);
2648
+ const name = (0, import_values5.literalOf)(value);
2387
2649
  if (name !== null) {
2388
2650
  names.push(name);
2389
2651
  continue;
@@ -2397,7 +2659,7 @@ function readOne(db, rootsByFile, nameKey2) {
2397
2659
  return { names, patterns };
2398
2660
  }
2399
2661
  function patternOf(value) {
2400
- const pieces = (0, import_values4.piecesOf)(value);
2662
+ const pieces = (0, import_values5.piecesOf)(value);
2401
2663
  let source = "^";
2402
2664
  let readSomething = false;
2403
2665
  for (const piece of pieces) {
@@ -2439,29 +2701,14 @@ function listsBehind(db, rootsByFile, nameKey2) {
2439
2701
  0,
2440
2702
  nameKey2
2441
2703
  )) {
2442
- const elements = literalElementsOf(rootsByFile, String(overKey), db);
2704
+ const node = (0, import_resolution5.nodeOfKey)(rootsByFile, String(overKey));
2705
+ const elements = node === null ? null : literalElementsOf(node, db);
2443
2706
  if (elements !== null) {
2444
2707
  found.push({ element: String(element), index: String(index), elements });
2445
2708
  }
2446
2709
  }
2447
2710
  return found;
2448
2711
  }
2449
- function literalElementsOf(rootsByFile, key, db) {
2450
- const node = (0, import_resolution5.nodeOfKey)(rootsByFile, key);
2451
- const value = node === null ? null : evaluatedValue(node, db);
2452
- if (value === null || value.kind !== "sequence") {
2453
- return null;
2454
- }
2455
- const elements = [];
2456
- for (const item of value.items) {
2457
- const literal = (0, import_values4.literalOf)((0, import_values4.force)(item.value));
2458
- if (literal === null) {
2459
- return null;
2460
- }
2461
- elements.push(literal);
2462
- }
2463
- return elements;
2464
- }
2465
2712
 
2466
2713
  // src/scope.ts
2467
2714
  function qualifyConstantRef(node, nesting) {
@@ -2790,107 +3037,11 @@ var import_extractor5 = require("@suss/extractor");
2790
3037
 
2791
3038
  // src/clientCalls.ts
2792
3039
  var import_behavioral_ir = require("@suss/behavioral-ir");
2793
- var import_values7 = require("@suss/values");
3040
+ var import_values9 = require("@suss/values");
2794
3041
 
2795
3042
  // src/paths/effects.ts
2796
3043
  var import_extractor2 = require("@suss/extractor");
2797
-
2798
- // src/paths/bareCalls.ts
2799
- var PARAMETER_LIST_TYPES = [
2800
- "method_parameters",
2801
- "block_parameters",
2802
- "lambda_parameters",
2803
- "parameters"
2804
- ];
2805
- function isFieldOf(parent, node, name) {
2806
- return field(parent, name)?.id === node.id;
2807
- }
2808
- function namedAt(parent, node) {
2809
- return isFieldOf(parent, node, "name");
2810
- }
2811
- function always() {
2812
- return true;
2813
- }
2814
- var SPELLS_A_NAME = {
2815
- call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
2816
- method: namedAt,
2817
- singleton_method: namedAt,
2818
- assignment: (parent, node) => isFieldOf(parent, node, "left"),
2819
- operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
2820
- for: (parent, node) => isFieldOf(parent, node, "pattern"),
2821
- optional_parameter: namedAt,
2822
- splat_parameter: namedAt,
2823
- hash_splat_parameter: namedAt,
2824
- keyword_parameter: namedAt,
2825
- block_parameter: namedAt,
2826
- destructured_parameter: always,
2827
- left_assignment_list: always,
2828
- rest_assignment: always,
2829
- destructured_left_assignment: always,
2830
- exception_variable: always,
2831
- undef: always,
2832
- alias: always,
2833
- ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
2834
- };
2835
- function addNamesUnder(node, names) {
2836
- if (node === null) {
2837
- return;
2838
- }
2839
- if (node.type === "identifier") {
2840
- names.add(node.text);
2841
- return;
2842
- }
2843
- for (const child of bodyStatements(node)) {
2844
- addNamesUnder(child, names);
2845
- }
2846
- }
2847
- function parameterNames2(node, names) {
2848
- for (const parameter of bodyStatements(node)) {
2849
- if (parameter.type === "identifier") {
2850
- names.add(parameter.text);
2851
- continue;
2852
- }
2853
- const declared = field(parameter, "name");
2854
- if (declared !== null && declared.type === "identifier") {
2855
- names.add(declared.text);
2856
- continue;
2857
- }
2858
- addNamesUnder(parameter, names);
2859
- }
2860
- }
2861
- function assignedNames(node, names) {
2862
- addNamesUnder(field(node, "left"), names);
2863
- }
2864
- var BINDS_A_NAME = {
2865
- assignment: assignedNames,
2866
- operator_assignment: assignedNames,
2867
- for: (node, names) => addNamesUnder(field(node, "pattern"), names),
2868
- exception_variable: (node, names) => addNamesUnder(node, names),
2869
- ...Object.fromEntries(
2870
- PARAMETER_LIST_TYPES.map((type) => [type, parameterNames2])
2871
- )
2872
- };
2873
- function localNamesIn(definitionNode) {
2874
- const names = /* @__PURE__ */ new Set();
2875
- const visit = (node) => {
2876
- BINDS_A_NAME[node.type]?.(node, names);
2877
- for (const child of bodyStatements(node)) {
2878
- visit(child);
2879
- }
2880
- };
2881
- visit(definitionNode);
2882
- return names;
2883
- }
2884
- function isBareMethodCall(node, locals) {
2885
- if (node.type !== "identifier" || locals.has(node.text)) {
2886
- return false;
2887
- }
2888
- const parent = node.parent;
2889
- if (parent === null) {
2890
- return false;
2891
- }
2892
- return SPELLS_A_NAME[parent.type]?.(parent, node) !== true;
2893
- }
3044
+ var import_values7 = require("@suss/values");
2894
3045
 
2895
3046
  // src/paths/lowering.ts
2896
3047
  var RAISE_NAMES = /* @__PURE__ */ new Set(["raise", "fail"]);
@@ -3178,23 +3329,21 @@ var BLOCK_TYPES5 = /* @__PURE__ */ new Set([
3178
3329
  "do_block",
3179
3330
  "block"
3180
3331
  ]);
3181
- var LITERAL_ARGS = {
3182
- string: (node) => ({
3183
- kind: "string",
3184
- value: node.text.replace(/^["']|["']$/g, "")
3185
- }),
3186
- integer: (node) => {
3187
- const value = Number.parseInt(node.text, 10);
3188
- return Number.isNaN(value) ? null : { kind: "number", value };
3189
- },
3190
- float: (node) => {
3191
- const value = Number.parseFloat(node.text);
3192
- return Number.isNaN(value) ? null : { kind: "number", value };
3193
- },
3194
- true: () => ({ kind: "boolean", value: true }),
3195
- false: () => ({ kind: "boolean", value: false }),
3196
- simple_symbol: (node) => ({ kind: "string", value: node.text.slice(1) })
3197
- };
3332
+ function literalArgOf(node, facts) {
3333
+ const value = evaluatedValue(node, facts);
3334
+ const text2 = (0, import_values7.literalOf)(value);
3335
+ if (text2 !== null) {
3336
+ return { kind: "string", value: text2 };
3337
+ }
3338
+ const constant2 = (0, import_values7.constantOf)(value);
3339
+ if (typeof constant2 === "number") {
3340
+ return { kind: "number", value: constant2 };
3341
+ }
3342
+ if (typeof constant2 === "boolean") {
3343
+ return { kind: "boolean", value: constant2 };
3344
+ }
3345
+ return null;
3346
+ }
3198
3347
  function children2(node) {
3199
3348
  return node.namedChildren.filter((child) => child !== null);
3200
3349
  }
@@ -3278,23 +3427,27 @@ function calleeText(call) {
3278
3427
  }
3279
3428
  return receiver === null ? method : `${receiver.text}.${method}`;
3280
3429
  }
3281
- function argOf(node) {
3282
- const literal = LITERAL_ARGS[node.type]?.(node);
3283
- if (literal !== null && literal !== void 0) {
3430
+ function argOf(node, facts) {
3431
+ const literal = literalArgOf(node, facts);
3432
+ if (literal !== null) {
3284
3433
  return literal;
3285
3434
  }
3286
3435
  if (node.type === "call" && !isArglessReceiverCall(node)) {
3287
- return { kind: "call", callee: calleeText(node), args: argsOf(node) };
3436
+ return {
3437
+ kind: "call",
3438
+ callee: calleeText(node),
3439
+ args: argsOf(node, facts)
3440
+ };
3288
3441
  }
3289
3442
  return { kind: "identifier", name: node.text };
3290
3443
  }
3291
- function argsOf(call) {
3444
+ function argsOf(call, facts) {
3292
3445
  const args = field(call, "arguments");
3293
3446
  if (args === null) {
3294
3447
  return [];
3295
3448
  }
3296
3449
  return children2(args).map(
3297
- (child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
3450
+ (child) => child.type === "pair" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3298
3451
  );
3299
3452
  }
3300
3453
  function enclosingStatement(call, body) {
@@ -3307,7 +3460,7 @@ function enclosingStatement(call, body) {
3307
3460
  }
3308
3461
  return current;
3309
3462
  }
3310
- function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS) {
3463
+ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
3311
3464
  const body = field(definitionNode, "body");
3312
3465
  if (body === null) {
3313
3466
  return [];
@@ -3347,7 +3500,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
3347
3500
  return {
3348
3501
  type: "invocation",
3349
3502
  callee: calleeText(call),
3350
- args: argsOf(call),
3503
+ args: argsOf(call, facts),
3351
3504
  async: false,
3352
3505
  ...conditions.length > 0 ? { preconditions: conditions } : {}
3353
3506
  };
@@ -3356,7 +3509,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
3356
3509
 
3357
3510
  // src/responseStatus.ts
3358
3511
  var import_extractor3 = require("@suss/extractor");
3359
- var import_values6 = require("@suss/values");
3512
+ var import_values8 = require("@suss/values");
3360
3513
 
3361
3514
  // src/paths/predicates.ts
3362
3515
  var OPERATORS = {
@@ -3425,7 +3578,7 @@ function binaryOf(node) {
3425
3578
  return opaqueOf(node);
3426
3579
  }
3427
3580
  function valueRefOf(node) {
3428
- const literal = literalOf3(node);
3581
+ const literal = literalOf5(node);
3429
3582
  if (literal !== null) {
3430
3583
  return literal;
3431
3584
  }
@@ -3438,7 +3591,7 @@ function valueRefOf(node) {
3438
3591
  }
3439
3592
  return { type: "unresolved", sourceText: node.text };
3440
3593
  }
3441
- function literalOf3(node) {
3594
+ function literalOf5(node) {
3442
3595
  if (node.type === "integer") {
3443
3596
  const value = Number.parseInt(node.text, 10);
3444
3597
  return Number.isNaN(value) ? null : { type: "literal", value };
@@ -3481,13 +3634,13 @@ function statusArgumentOf(call, declaration) {
3481
3634
  }
3482
3635
  return args.positional[declaration.statusArgument] ?? null;
3483
3636
  }
3484
- function statusNumberOf(node, names) {
3485
- const value = evaluatedValue(node);
3486
- const constant2 = (0, import_values6.constantOf)(value);
3637
+ function statusNumberOf(node, names, facts) {
3638
+ const value = evaluatedValue(node, facts);
3639
+ const constant2 = (0, import_values8.constantOf)(value);
3487
3640
  if (typeof constant2 === "number") {
3488
3641
  return constant2;
3489
3642
  }
3490
- const name = (0, import_values6.literalOf)(value);
3643
+ const name = (0, import_values8.literalOf)(value);
3491
3644
  return name === null ? null : names[name] ?? null;
3492
3645
  }
3493
3646
  function declarationsByName(declarations) {
@@ -3531,12 +3684,12 @@ function collectReturns(node, found) {
3531
3684
  }
3532
3685
  return found;
3533
3686
  }
3534
- function readingOfCall(call, declaration, names) {
3687
+ function readingOfCall(call, declaration, names, facts) {
3535
3688
  const argument = statusArgumentOf(call, declaration);
3536
3689
  if (argument === null) {
3537
3690
  return declaration.defaultStatusCode === void 0 ? import_extractor3.absentReading : (0, import_extractor3.writtenReading)(declaration.defaultStatusCode, rangeOf(call));
3538
3691
  }
3539
- const status = statusNumberOf(argument, names);
3692
+ const status = statusNumberOf(argument, names, facts);
3540
3693
  if (status === null) {
3541
3694
  return (0, import_extractor3.unreadableReading)(
3542
3695
  "This response writes a status that does not settle on a number here, so this outcome claims none",
@@ -3680,7 +3833,7 @@ function responseBranches(method, pattern, effects, extraEffects, options = {})
3680
3833
  const outcomes = [];
3681
3834
  for (const terminal of terminals) {
3682
3835
  const declaration = declarationOf(terminal, byName, locals);
3683
- const reading = declaration === void 0 ? import_extractor3.absentReading : readingOfCall(terminal, declaration, statusNames);
3836
+ const reading = declaration === void 0 ? import_extractor3.absentReading : readingOfCall(terminal, declaration, statusNames, options.facts);
3684
3837
  for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
3685
3838
  outcomes.push({
3686
3839
  conditions: path7.map(conditionOf),
@@ -3714,11 +3867,9 @@ function clientCallUnits(root, pack, pattern, options) {
3714
3867
  continue;
3715
3868
  }
3716
3869
  for (const call of callsUnder(method)) {
3717
- const request = requestCall(call, method, root, pattern, options);
3718
- if (request === null) {
3719
- continue;
3870
+ for (const request of requestCalls(call, pattern, options)) {
3871
+ units.push(clientUnit(method, name, request, pattern, pack, options));
3720
3872
  }
3721
- units.push(clientUnit(method, name, request, pattern, pack, options));
3722
3873
  }
3723
3874
  }
3724
3875
  return units;
@@ -3747,36 +3898,60 @@ function callsUnder(node, found = []) {
3747
3898
  }
3748
3899
  return found;
3749
3900
  }
3750
- function requestCall(call, method, root, pattern, options) {
3901
+ function requestCalls(call, pattern, options) {
3751
3902
  const receiver = field(call, "receiver");
3752
3903
  const called = field(call, "method")?.text;
3753
- if (receiver === null || called === void 0) {
3754
- return null;
3904
+ if (receiver === null || called === void 0 || !isLibraryReceiver(receiver, pattern, options)) {
3905
+ return [];
3755
3906
  }
3756
- const prefix = receiverPrefix(receiver, method, root, pattern, options);
3757
- if (prefix === null) {
3758
- return null;
3907
+ const read = (site) => requestCall(
3908
+ call,
3909
+ called,
3910
+ receiverPrefix(receiver, pattern, options, site),
3911
+ pattern,
3912
+ options,
3913
+ site
3914
+ );
3915
+ const plain = read();
3916
+ if (plain !== null && !(0, import_behavioral_ir.hasNameHole)(plain.path)) {
3917
+ return [plain];
3759
3918
  }
3919
+ const byBoundary = /* @__PURE__ */ new Map();
3920
+ for (const site of constructionSitesOf(call, options.facts)) {
3921
+ const stated = read(site);
3922
+ if (stated !== null) {
3923
+ byBoundary.set(`${stated.method} ${stated.path}`, stated);
3924
+ }
3925
+ }
3926
+ if (byBoundary.size > 0) {
3927
+ return [...byBoundary.values()];
3928
+ }
3929
+ return plain === null ? [] : [plain];
3930
+ }
3931
+ function requestCall(call, called, prefix, pattern, options, site) {
3760
3932
  const args = readCallArgs(field(call, "arguments"));
3761
3933
  const verb = pattern.verbMethodNames[called];
3762
3934
  if (verb !== void 0) {
3763
- const path8 = urlIn(args, pattern, options);
3935
+ const path8 = urlIn(args, pattern, options, site);
3764
3936
  return path8 === null ? null : { method: verb, path: prefix + path8 };
3765
3937
  }
3766
3938
  const sent = pattern.requestObject;
3767
3939
  if (sent === void 0 || sent.attribute !== called) {
3768
3940
  return null;
3769
3941
  }
3770
- const built = requestBuilt(args.positional[0], method, sent);
3942
+ const built = requestBuilt(args.positional[0], sent, options, site);
3771
3943
  if (built === null) {
3772
3944
  return null;
3773
3945
  }
3774
- const path7 = pathAt(built.url, options);
3946
+ const path7 = pathAt(built.url, options, site);
3775
3947
  return path7 === null ? null : { method: built.method, path: prefix + path7 };
3776
3948
  }
3777
- function requestBuilt(argument, method, sent) {
3778
- const written = argument === void 0 ? null : assignedCall(argument, method) ?? argument;
3779
- if (written === null || written.type !== "call") {
3949
+ function requestBuilt(argument, sent, options, site) {
3950
+ if (argument === void 0) {
3951
+ return null;
3952
+ }
3953
+ const written = writtenNodeOf(argument, options.facts, site) ?? argument;
3954
+ if (written.type !== "call") {
3780
3955
  return null;
3781
3956
  }
3782
3957
  const requestClass = field(written, "receiver")?.text;
@@ -3787,28 +3962,18 @@ function requestBuilt(argument, method, sent) {
3787
3962
  const args = readCallArgs(field(written, "arguments"));
3788
3963
  return { method: verb, url: args.positional[sent.urlPosition] };
3789
3964
  }
3790
- function assignedCall(node, method) {
3791
- if (node.type !== "identifier") {
3792
- return null;
3793
- }
3794
- const value = assignedValue(node.text, method);
3795
- return value !== null && value.type === "call" ? value : null;
3965
+ function isLibraryReceiver(receiver, pattern, options) {
3966
+ return namesConstant(receiver, pattern.constantName) || builderCallBehind(receiver, pattern, options.facts) !== null;
3796
3967
  }
3797
- function receiverPrefix(receiver, method, root, pattern, options) {
3798
- if (namesConstant(receiver, pattern.constantName)) {
3799
- return "";
3800
- }
3801
- if (receiver.type !== "identifier") {
3802
- return null;
3803
- }
3804
- const built = builtBy(receiver.text, method, root, pattern);
3968
+ function receiverPrefix(receiver, pattern, options, site) {
3969
+ const built = builderCallBehind(receiver, pattern, options.facts);
3805
3970
  if (built === null) {
3806
- return null;
3971
+ return "";
3807
3972
  }
3808
3973
  const args = readCallArgs(field(built, "arguments"));
3809
3974
  const keyword = pattern.builderUrlKeyword;
3810
3975
  const base = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[0];
3811
- const path7 = base === void 0 ? null : (0, import_values7.pathOf)(evaluatedValue(base, options.facts));
3976
+ const path7 = base === void 0 ? null : (0, import_values9.pathOf)(evaluatedValue(base, options.facts, void 0, site));
3812
3977
  return path7 === void 0 || path7 === null ? "" : trimmed(path7);
3813
3978
  }
3814
3979
  function trimmed(path7) {
@@ -3820,61 +3985,28 @@ function namesConstant(receiver, constantName2) {
3820
3985
  }
3821
3986
  return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
3822
3987
  }
3823
- function builtBy(name, method, root, pattern) {
3988
+ function builderCallBehind(receiver, pattern, facts) {
3824
3989
  const builders = pattern.receiverBuilders ?? [];
3825
3990
  if (builders.length === 0) {
3826
3991
  return null;
3827
3992
  }
3828
- const value = assignedValue(name, method) ?? valueMethodNamedReturns(name, root);
3829
- if (value === null || value.type !== "call" || !builders.includes(field(value, "method")?.text ?? "")) {
3830
- return null;
3831
- }
3832
- const receiver = field(value, "receiver");
3833
- return receiver !== null && namesConstant(receiver, pattern.constantName) ? value : null;
3834
- }
3835
- function valueMethodNamedReturns(name, root) {
3836
- const defined = methodDefinitions(root).find(
3837
- (candidate) => field(candidate, "name")?.text === name
3838
- );
3839
- if (defined === void 0) {
3840
- return null;
3841
- }
3842
- const body = field(defined, "body");
3843
- const statements = body === null ? [] : body.namedChildren;
3844
- const last = statements[statements.length - 1];
3845
- if (last === void 0) {
3846
- return null;
3847
- }
3848
- if (last.type === "operator_assignment" || last.type === "assignment") {
3849
- return field(last, "right");
3850
- }
3851
- return last.type === "return" ? last.namedChildren[0] ?? null : last;
3852
- }
3853
- function assignedValue(name, method) {
3854
- const body = field(method, "body");
3855
- if (body === null) {
3993
+ const written = writtenNodeOf(receiver, facts);
3994
+ if (written === null || written.type !== "call" || !builders.includes(field(written, "method")?.text ?? "")) {
3856
3995
  return null;
3857
3996
  }
3858
- for (const statement of runStatements(body)) {
3859
- if (statement.type !== "assignment") {
3860
- continue;
3861
- }
3862
- if (field(statement, "left")?.text === name) {
3863
- return field(statement, "right");
3864
- }
3865
- }
3866
- return null;
3997
+ const constant2 = field(written, "receiver");
3998
+ return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
3867
3999
  }
3868
- function urlIn(args, pattern, options) {
4000
+ function urlIn(args, pattern, options, site) {
3869
4001
  const keyword = pattern.url.keyword;
3870
4002
  const written = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
3871
- return pathAt(written, options);
4003
+ return pathAt(written, options, site);
3872
4004
  }
3873
- function pathAt(written, options) {
4005
+ function pathAt(written, options, site) {
3874
4006
  if (written === void 0) {
3875
4007
  return null;
3876
4008
  }
3877
- const path7 = (0, import_values7.pathOf)(evaluatedValue(written, options.facts));
4009
+ const path7 = (0, import_values9.pathOf)(evaluatedValue(written, options.facts, void 0, site));
3878
4010
  return path7 === void 0 || (0, import_behavioral_ir.namesNothing)(path7) ? null : path7;
3879
4011
  }
3880
4012
  function clientUnit(method, name, request, pattern, pack, options) {
@@ -3897,15 +4029,15 @@ function clientUnit(method, name, request, pattern, pack, options) {
3897
4029
  recognition: pack.name
3898
4030
  }),
3899
4031
  parameters: [],
3900
- branches: callerBranches(method, range),
4032
+ branches: callerBranches(method, range, options.facts),
3901
4033
  ...responseAccessors(pattern),
3902
4034
  bodyContent: "statements",
3903
4035
  dependencyCalls: [],
3904
4036
  declaredContract: null
3905
4037
  };
3906
4038
  }
3907
- function callerBranches(method, range) {
3908
- const effects = invocationEffects(method);
4039
+ function callerBranches(method, range, facts) {
4040
+ const effects = invocationEffects(method, void 0, void 0, facts);
3909
4041
  return returnPathBranches(method, effects) ?? [returnBranch(range, effects)];
3910
4042
  }
3911
4043
  function responseAccessors(pattern) {
@@ -4063,7 +4195,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4063
4195
  }
4064
4196
  const form = byName.get(called);
4065
4197
  if (form !== void 0) {
4066
- for (const declaration of declarationsOf(statement, form)) {
4198
+ for (const declaration of declarationsOf(statement, form, read.facts)) {
4067
4199
  declared = [
4068
4200
  ...declared.filter((earlier) => !sameFilter(earlier, declaration)),
4069
4201
  declaration
@@ -4073,7 +4205,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4073
4205
  }
4074
4206
  const skipped = skipNames.get(called);
4075
4207
  if (skipped !== void 0) {
4076
- declared = applySkips(declared, skipsOf(statement, skipped));
4208
+ declared = applySkips(declared, skipsOf(statement, skipped, read.facts));
4077
4209
  }
4078
4210
  }
4079
4211
  const resolved = [];
@@ -4110,14 +4242,14 @@ function filterReference(filter, displayPath) {
4110
4242
  ...filter.filter.onThrow === true ? { onThrow: true } : {}
4111
4243
  };
4112
4244
  }
4113
- function filterUnit(filter, pattern, displayPath, body) {
4245
+ function filterUnit(filter, pattern, displayPath, body, facts) {
4114
4246
  const range = rangeOf(filter.method);
4115
4247
  const branches = responseBranches(
4116
4248
  filter.method,
4117
4249
  pattern,
4118
4250
  body.effects ?? [],
4119
4251
  body.extraEffects,
4120
- { fallthrough: "handOn" }
4252
+ { fallthrough: "handOn", facts }
4121
4253
  );
4122
4254
  return {
4123
4255
  identity: {
@@ -4165,30 +4297,30 @@ function calledName(statement) {
4165
4297
  }
4166
4298
  return field(statement, "method")?.text ?? null;
4167
4299
  }
4168
- function declarationsOf(statement, filter) {
4300
+ function declarationsOf(statement, filter, facts) {
4169
4301
  const args = readCallArgs(field(statement, "arguments"));
4170
4302
  const keywords = filter.actionKeywords;
4171
- const only = keywords === void 0 ? null : namesUnder(args.keyword[keywords.include]);
4172
- const except = keywords === void 0 ? null : namesUnder(args.keyword[keywords.exclude]);
4173
- return methodNamesOf(args, filter).map((methodName) => ({
4303
+ const only = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.include], facts);
4304
+ const except = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.exclude], facts);
4305
+ return methodNamesOf(args, filter, facts).map((methodName) => ({
4174
4306
  filter,
4175
4307
  methodName,
4176
4308
  only,
4177
4309
  except: except ?? /* @__PURE__ */ new Set()
4178
4310
  }));
4179
4311
  }
4180
- function methodNamesOf(args, filter) {
4312
+ function methodNamesOf(args, filter, facts) {
4181
4313
  if (filter.methodFrom === "withKeyword") {
4182
4314
  const named2 = args.keyword.with;
4183
- const value = named2 === void 0 ? null : symbolValue(named2);
4315
+ const value = named2 === void 0 ? null : stringValueOf(named2, facts);
4184
4316
  return value === null ? [] : [value];
4185
4317
  }
4186
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null);
4318
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
4187
4319
  }
4188
- function skipsOf(statement, filterName) {
4320
+ function skipsOf(statement, filterName, facts) {
4189
4321
  const args = readCallArgs(field(statement, "arguments"));
4190
- const actions = namesUnder(args.keyword.only);
4191
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
4322
+ const actions = actionsUnder(args.keyword.only, facts);
4323
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
4192
4324
  }
4193
4325
  function sameFilter(one, other) {
4194
4326
  return one.filter.name === other.filter.name && one.methodName === other.methodName;
@@ -4212,26 +4344,12 @@ function applySkips(declared, skips) {
4212
4344
  }
4213
4345
  return left;
4214
4346
  }
4215
- function namesUnder(node) {
4347
+ function actionsUnder(node, facts) {
4216
4348
  if (node === void 0) {
4217
4349
  return null;
4218
4350
  }
4219
- const single = symbolValue(node);
4220
- if (single !== null) {
4221
- return /* @__PURE__ */ new Set([single]);
4222
- }
4223
- if (node.type !== "array" && node.type !== "symbol_array") {
4224
- return null;
4225
- }
4226
- const names = /* @__PURE__ */ new Set();
4227
- for (const child of bodyStatements(node)) {
4228
- const bare = child.type === "bare_symbol" ? child.text : null;
4229
- const name = symbolValue(child) ?? bare;
4230
- if (name !== null) {
4231
- names.add(name);
4232
- }
4233
- }
4234
- return names;
4351
+ const found = namesOf(node, facts);
4352
+ return found === null ? null : new Set(found);
4235
4353
  }
4236
4354
 
4237
4355
  // src/storage.ts
@@ -4309,67 +4427,64 @@ function argumentsOf2(call) {
4309
4427
  const args = field(call, "arguments");
4310
4428
  return args === null ? [] : children3(args);
4311
4429
  }
4312
- function isKeywordArgument(argument) {
4313
- return argument.type === "pair" || argument.type === "hash";
4430
+ function recordFieldsOf(argument, facts) {
4431
+ const value = evaluatedValue(argument, facts);
4432
+ return value.kind === "record" ? [...value.fields.keys()] : [];
4314
4433
  }
4315
- function bareName(node) {
4316
- return node.text.replace(/^:/, "").replace(/:$/, "");
4317
- }
4318
- function pairsIn(argument) {
4319
- if (argument.type === "pair") {
4320
- return [argument];
4321
- }
4322
- if (argument.type === "hash") {
4323
- return children3(argument).filter((child) => child.type === "pair");
4324
- }
4325
- return [];
4326
- }
4327
- function keywordKeys(call) {
4434
+ function keywordKeys(call, facts) {
4328
4435
  const keys = [];
4329
4436
  for (const argument of argumentsOf2(call)) {
4330
- for (const pair of pairsIn(argument)) {
4331
- const key = field(pair, "key");
4332
- if (key !== null) {
4333
- keys.push(bareName(key));
4334
- }
4437
+ if (argument.type !== "pair") {
4438
+ keys.push(...recordFieldsOf(argument, facts));
4439
+ continue;
4440
+ }
4441
+ const key = field(argument, "key");
4442
+ const name = key === null ? null : keyNameOf(key);
4443
+ if (name !== null) {
4444
+ keys.push(name);
4335
4445
  }
4336
4446
  }
4337
4447
  return keys;
4338
4448
  }
4339
- function hasPositionalArgument(call) {
4340
- return argumentsOf2(call).some((argument) => !isKeywordArgument(argument));
4449
+ function keyNameOf(key) {
4450
+ return hashKeySymbolName(key) ?? symbolValue(key) ?? stringLiteralValue(key);
4341
4451
  }
4342
- function columnsAskedFor(call, pattern) {
4452
+ function hasPositionalArgument(call, facts) {
4453
+ return argumentsOf2(call).some(
4454
+ (argument) => argument.type !== "pair" && recordFieldsOf(argument, facts).length === 0
4455
+ );
4456
+ }
4457
+ function columnsAskedFor(call, pattern, facts) {
4343
4458
  if (!(pattern.columnArguments ?? []).includes(methodOf(call))) {
4344
4459
  return [];
4345
4460
  }
4346
- return argumentsOf2(call).filter((argument) => argument.type === "simple_symbol").map(bareName);
4461
+ return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
4347
4462
  }
4348
- function primaryKeySelector(call, pattern) {
4463
+ function primaryKeySelector(call, pattern, facts) {
4349
4464
  const byPrimaryKey = pattern.byPrimaryKey;
4350
4465
  if (byPrimaryKey === void 0) {
4351
4466
  return [];
4352
4467
  }
4353
- const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call);
4468
+ const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call, facts);
4354
4469
  return picks ? [byPrimaryKey.column] : [];
4355
4470
  }
4356
- function selectorOf(call, pattern) {
4471
+ function selectorOf(call, pattern, facts) {
4357
4472
  const picked = [];
4358
4473
  for (const link of chainLinks(call)) {
4359
- picked.push(...primaryKeySelector(link, pattern));
4474
+ picked.push(...primaryKeySelector(link, pattern, facts));
4360
4475
  if (pattern.reads.includes(methodOf(link))) {
4361
- picked.push(...keywordKeys(link));
4476
+ picked.push(...keywordKeys(link, facts));
4362
4477
  }
4363
4478
  }
4364
4479
  return [...new Set(picked)];
4365
4480
  }
4366
- function fieldsOf(call, pattern, kind) {
4481
+ function fieldsOf(call, pattern, kind, facts) {
4367
4482
  if (kind === "write") {
4368
- return [...new Set(keywordKeys(call))];
4483
+ return [...new Set(keywordKeys(call, facts))];
4369
4484
  }
4370
4485
  return [
4371
4486
  ...new Set(
4372
- chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern))
4487
+ chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern, facts))
4373
4488
  )
4374
4489
  ];
4375
4490
  }
@@ -4461,8 +4576,8 @@ function modelCallEffects(call, file, options, enclosing) {
4461
4576
  target.container,
4462
4577
  pattern,
4463
4578
  kind,
4464
- selectorOf(worked, pattern),
4465
- fieldsOf(worked, pattern, kind)
4579
+ selectorOf(worked, pattern, options.facts),
4580
+ fieldsOf(worked, pattern, kind, options.facts)
4466
4581
  )
4467
4582
  ];
4468
4583
  }
@@ -4782,7 +4897,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4782
4897
  filter,
4783
4898
  pattern,
4784
4899
  displayPath,
4785
- bodyOfMethod(filter.method, filter.file, options)
4900
+ bodyOfMethod(filter.method, filter.file, options),
4901
+ options.facts
4786
4902
  );
4787
4903
  units.push(raw);
4788
4904
  options.onReachSeed?.(raw, {
@@ -4873,7 +4989,8 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
4873
4989
  method,
4874
4990
  pattern,
4875
4991
  body.effects ?? [],
4876
- body.extraEffects
4992
+ body.extraEffects,
4993
+ { facts: bodyRead.facts }
4877
4994
  );
4878
4995
  return {
4879
4996
  identity: {
@@ -5003,7 +5120,8 @@ function bodyOfMethod(method, file, bodyRead = {}) {
5003
5120
  const effects = invocationEffects(
5004
5121
  method,
5005
5122
  bodyRead.inheritedMethods,
5006
- EVERY_ARGLESS_CALL
5123
+ EVERY_ARGLESS_CALL,
5124
+ bodyRead.facts
5007
5125
  );
5008
5126
  const storage = bodyRead.storage;
5009
5127
  const extra = [
@@ -5679,7 +5797,7 @@ function emitConstantBindings(db, perFile) {
5679
5797
  var import_node_fs4 = __toESM(require("fs"), 1);
5680
5798
  var import_node_path5 = __toESM(require("path"), 1);
5681
5799
  var import_datalog4 = require("@suss/datalog");
5682
- var import_resolution8 = require("@suss/resolution");
5800
+ var import_resolution9 = require("@suss/resolution");
5683
5801
 
5684
5802
  // src/parser.ts
5685
5803
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -5743,6 +5861,7 @@ var import_node_path4 = __toESM(require("path"), 1);
5743
5861
  var import_behavioral_ir5 = require("@suss/behavioral-ir");
5744
5862
  var import_datalog3 = require("@suss/datalog");
5745
5863
  var import_extractor10 = require("@suss/extractor");
5864
+ var import_resolution8 = require("@suss/resolution");
5746
5865
 
5747
5866
  // src/diagnostics.ts
5748
5867
  var import_extractor6 = require("@suss/extractor");
@@ -6506,20 +6625,41 @@ function associationCallsIn(packs) {
6506
6625
  )
6507
6626
  );
6508
6627
  }
6509
- function emitStorageFacts(db, packs) {
6510
- for (const pack of packs) {
6511
- for (const pattern of pack.storage ?? []) {
6512
- for (const base of pattern.baseClasses) {
6513
- for (const method of pattern.givesBack) {
6514
- db.add("givesBackOne", [base, method]);
6515
- }
6516
- }
6517
- }
6518
- }
6628
+ function packWordsOf(packs) {
6629
+ return {
6630
+ givesBackOne: packs.flatMap(
6631
+ (pack) => (pack.storage ?? []).flatMap(
6632
+ (pattern) => pattern.baseClasses.flatMap(
6633
+ (base) => pattern.givesBack.map((method) => ({ base, method }))
6634
+ )
6635
+ )
6636
+ )
6637
+ };
6638
+ }
6639
+ function factsForFile(options) {
6640
+ const db = new import_datalog3.Database();
6641
+ const bodyBlocks = bodyBlocksIn(options.packs);
6642
+ emitValueFacts(db, options.file, options.root, bodyBlocks);
6643
+ emitConstantBindings(db, [
6644
+ collectFileConstants(
6645
+ options.file,
6646
+ options.root,
6647
+ associationCallsIn(options.packs),
6648
+ inflectionsIn(options.packs)
6649
+ )
6650
+ ]);
6651
+ emitRequireFacts(db, options.file, options.root, /* @__PURE__ */ new Set([options.file]));
6652
+ bindEvaluator(db, {
6653
+ files: [{ file: options.file, root: options.root }],
6654
+ definitions: new Map(methodDefinitionsIn(options.file, options.root))
6655
+ });
6656
+ (0, import_resolution8.addPackWords)(db, packWordsOf(options.packs));
6657
+ return db;
6519
6658
  }
6520
6659
  function alreadyDiscovered(seen, raw) {
6521
6660
  const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
6522
- const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
6661
+ const boundary = raw.boundaryBinding === null ? "" : (0, import_behavioral_ir5.boundaryKey)(raw.boundaryBinding) ?? "";
6662
+ const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}::${boundary}`;
6523
6663
  if (seen.has(key)) {
6524
6664
  return true;
6525
6665
  }
@@ -6586,7 +6726,7 @@ async function extractRubyProject(options) {
6586
6726
  emitRequireFacts(db, file, root, known);
6587
6727
  }
6588
6728
  bindEvaluator(db, { files: parsed, definitions });
6589
- emitStorageFacts(db, options.packs);
6729
+ (0, import_resolution8.addPackWords)(db, packWordsOf(options.packs));
6590
6730
  return readDynamicNames(
6591
6731
  db,
6592
6732
  new Map(parsed.map(({ file, root }) => [file, root]))
@@ -6810,7 +6950,7 @@ function findRubyFiles(root) {
6810
6950
  }
6811
6951
 
6812
6952
  // src/facts/why.ts
6813
- var WITNESS_RULES = [...import_resolution8.RESOLUTION_RULES, ...RUBY_RULES];
6953
+ var WITNESS_RULES = [...import_resolution9.RESOLUTION_RULES, ...RUBY_RULES];
6814
6954
  var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6815
6955
  function namedChildrenOf(node) {
6816
6956
  return node.namedChildren.filter((child) => child !== null);
@@ -6859,6 +6999,10 @@ function displayNameOf(node) {
6859
6999
  function width(node) {
6860
7000
  return node.endIndex - node.startIndex;
6861
7001
  }
7002
+ function askedKey(value) {
7003
+ const enclosing = enclosingMethod2(value.node);
7004
+ return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
7005
+ }
6862
7006
  var RubyWhySession = class {
6863
7007
  root;
6864
7008
  db = new import_datalog4.Database();
@@ -6931,10 +7075,10 @@ var RubyWhySession = class {
6931
7075
  * which the caller says in its own words.
6932
7076
  */
6933
7077
  explain(value, options = {}) {
6934
- return (0, import_resolution8.explainResolvedKey)({
7078
+ return (0, import_resolution9.explainResolvedKey)({
6935
7079
  db: this.db,
6936
7080
  rules: WITNESS_RULES,
6937
- key: readKey(value.file, value.node, enclosingMethod2(value.node)),
7081
+ key: askedKey(value),
6938
7082
  locate: (key) => this.locate(key),
6939
7083
  displayPath: (key) => this.displayPath(key),
6940
7084
  ...options
@@ -7052,6 +7196,7 @@ async function rubyStubEvidence(options) {
7052
7196
  emitValueFacts,
7053
7197
  evaluatedValue,
7054
7198
  extractRubyProject,
7199
+ factsForFile,
7055
7200
  field,
7056
7201
  findRubyFiles,
7057
7202
  graphqlTypeNameFromQualified,