@suss/adapter-ruby 0.30.0 → 0.31.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
@@ -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,118 @@ 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
+ (0, import_resolution.askResolutionUnder)(db, [[key, site]], (0, import_resolution.resolutionUnderProgram)(RUBY_RULES));
472
+ return (0, import_resolution.writtenValueUnder)(db, key, site);
473
+ }
474
+ function constructionSites(db, classKey) {
475
+ (0, import_resolution.askResolution)(db, [classKey], "wantedSites", RUBY_PROGRAM);
476
+ return (0, import_resolution.allocationSitesOf)(db, classKey);
477
+ }
482
478
 
483
479
  // src/facts/values.ts
484
480
  var import_resolution3 = require("@suss/resolution");
485
481
 
482
+ // src/paths/bareCalls.ts
483
+ var PARAMETER_LIST_TYPES = [
484
+ "method_parameters",
485
+ "block_parameters",
486
+ "lambda_parameters",
487
+ "parameters"
488
+ ];
489
+ function isFieldOf(parent, node, name) {
490
+ return field(parent, name)?.id === node.id;
491
+ }
492
+ function namedAt(parent, node) {
493
+ return isFieldOf(parent, node, "name");
494
+ }
495
+ function always() {
496
+ return true;
497
+ }
498
+ var SPELLS_A_NAME = {
499
+ call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
500
+ method: namedAt,
501
+ singleton_method: namedAt,
502
+ assignment: (parent, node) => isFieldOf(parent, node, "left"),
503
+ operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
504
+ for: (parent, node) => isFieldOf(parent, node, "pattern"),
505
+ optional_parameter: namedAt,
506
+ splat_parameter: namedAt,
507
+ hash_splat_parameter: namedAt,
508
+ keyword_parameter: namedAt,
509
+ block_parameter: namedAt,
510
+ destructured_parameter: always,
511
+ left_assignment_list: always,
512
+ rest_assignment: always,
513
+ destructured_left_assignment: always,
514
+ exception_variable: always,
515
+ undef: always,
516
+ alias: always,
517
+ ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
518
+ };
519
+ function addNamesUnder(node, names) {
520
+ if (node === null) {
521
+ return;
522
+ }
523
+ if (node.type === "identifier") {
524
+ names.add(node.text);
525
+ return;
526
+ }
527
+ for (const child of bodyStatements(node)) {
528
+ addNamesUnder(child, names);
529
+ }
530
+ }
531
+ function parameterNames(node, names) {
532
+ for (const parameter of bodyStatements(node)) {
533
+ if (parameter.type === "identifier") {
534
+ names.add(parameter.text);
535
+ continue;
536
+ }
537
+ const declared = field(parameter, "name");
538
+ if (declared !== null && declared.type === "identifier") {
539
+ names.add(declared.text);
540
+ continue;
541
+ }
542
+ addNamesUnder(parameter, names);
543
+ }
544
+ }
545
+ function assignedNames(node, names) {
546
+ addNamesUnder(field(node, "left"), names);
547
+ }
548
+ var BINDS_A_NAME = {
549
+ assignment: assignedNames,
550
+ operator_assignment: assignedNames,
551
+ for: (node, names) => addNamesUnder(field(node, "pattern"), names),
552
+ exception_variable: (node, names) => addNamesUnder(node, names),
553
+ ...Object.fromEntries(
554
+ PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
555
+ )
556
+ };
557
+ function localNamesIn(definitionNode) {
558
+ const names = /* @__PURE__ */ new Set();
559
+ const visit = (node) => {
560
+ BINDS_A_NAME[node.type]?.(node, names);
561
+ for (const child of bodyStatements(node)) {
562
+ visit(child);
563
+ }
564
+ };
565
+ visit(definitionNode);
566
+ return names;
567
+ }
568
+ function spellsAName(node) {
569
+ const parent = node.parent;
570
+ if (parent === null) {
571
+ return true;
572
+ }
573
+ return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
574
+ }
575
+ function isBareMethodCall(node, locals) {
576
+ if (node.type !== "identifier" || locals.has(node.text)) {
577
+ return false;
578
+ }
579
+ return !spellsAName(node);
580
+ }
581
+
486
582
  // src/facts/locals.ts
487
583
  var import_resolution2 = require("@suss/resolution");
488
584
  var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
@@ -507,7 +603,7 @@ function parametersOf(owner) {
507
603
  const params = field(owner, "parameters");
508
604
  return params === null ? [] : bodyStatements(params);
509
605
  }
510
- function parameterNames(owner) {
606
+ function parameterNames2(owner) {
511
607
  const names = /* @__PURE__ */ new Set();
512
608
  for (const param of parametersOf(owner)) {
513
609
  const name = paramNameOf(param);
@@ -555,6 +651,13 @@ function readForVariable(node) {
555
651
  }
556
652
  return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
557
653
  }
654
+ function readRescueVariable(node) {
655
+ const variable = field(node, "variable");
656
+ if (variable === null) {
657
+ return [];
658
+ }
659
+ return bodyStatements(variable).flatMap(targetsOf).map((target) => writeOf(target, null, node, false));
660
+ }
558
661
  function readBlockParameters(node) {
559
662
  const found = [];
560
663
  for (const param of parametersOf(node)) {
@@ -569,6 +672,7 @@ var WRITE_READERS = {
569
672
  assignment: readAssignment,
570
673
  operator_assignment: readOperatorAssignment,
571
674
  for: readForVariable,
675
+ rescue: readRescueVariable,
572
676
  block: readBlockParameters,
573
677
  do_block: readBlockParameters
574
678
  };
@@ -614,7 +718,7 @@ function namesOfMethod(method) {
614
718
  if (remembered !== void 0) {
615
719
  return remembered;
616
720
  }
617
- const names = scopeNames(parameterNames(method), field(method, "body"));
721
+ const names = scopeNames(parameterNames2(method), field(method, "body"));
618
722
  cache.byMethod.set(method, names);
619
723
  return names;
620
724
  }
@@ -636,10 +740,17 @@ function ownerOfName(node, name, enclosing) {
636
740
  }
637
741
  return enclosing !== null && names.locals.has(name) ? enclosing : null;
638
742
  }
743
+ function isLocalName(node, name, enclosing) {
744
+ const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
745
+ if (names.blockParams.has(name) && blockDeclaring(node, name) !== null) {
746
+ return true;
747
+ }
748
+ return names.locals.has(name);
749
+ }
639
750
  function blockDeclaring(node, name) {
640
751
  let current = node.parent;
641
752
  while (current !== null) {
642
- if (BLOCK_TYPES2.has(current.type) && parameterNames(current).has(name)) {
753
+ if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
643
754
  return current;
644
755
  }
645
756
  if (OWN_BODY_TYPES.has(current.type)) {
@@ -695,6 +806,17 @@ function collectWrites(method, body) {
695
806
  }
696
807
  return [...groups.values()];
697
808
  }
809
+ function instanceWritesRunInOrder(body, name, targets) {
810
+ return (0, import_resolution2.writesRunInOrder)(
811
+ body,
812
+ name,
813
+ targets.map((target) => ({
814
+ at: target,
815
+ direct: target.parent?.parent?.id === body.id
816
+ })),
817
+ nameReads(new Set(targets.map((target) => target.id)))
818
+ );
819
+ }
698
820
  function orderedWrite(write, scope) {
699
821
  return {
700
822
  at: write.at,
@@ -740,12 +862,28 @@ function readKey(filePath, written, enclosing) {
740
862
  if (node.type !== "identifier" && node.type !== "constant") {
741
863
  return nodeId(filePath, node);
742
864
  }
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);
865
+ if (isMethodOfReceiver(node) || isBareCall(node, enclosing)) {
866
+ return nodeId(filePath, node);
746
867
  }
747
868
  return nameKey(filePath, node, enclosing);
748
869
  }
870
+ function isMethodOfReceiver(node) {
871
+ const parent = node.parent;
872
+ return parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null;
873
+ }
874
+ function isReceiverOfCall(node) {
875
+ const parent = node.parent;
876
+ return parent !== null && parent.type === "call" && field(parent, "receiver")?.id === node.id;
877
+ }
878
+ function isBareCall(node, enclosing) {
879
+ if (node.type !== "identifier") {
880
+ return false;
881
+ }
882
+ if (spellsAName(node) && !isReceiverOfCall(node)) {
883
+ return false;
884
+ }
885
+ return !isLocalName(node, node.text, enclosing);
886
+ }
749
887
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
750
888
  "string",
751
889
  "integer",
@@ -774,11 +912,7 @@ function add(emitter, relation, ...tuple) {
774
912
  emitter.db.add(relation, tuple);
775
913
  }
776
914
  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);
915
+ return readKey(emitter.filePath, written, emitter.enclosing);
782
916
  }
783
917
  function pairKeyText(key) {
784
918
  if (key.type === "hash_key_symbol") {
@@ -792,35 +926,29 @@ function pairKeyText(key) {
792
926
  }
793
927
  return null;
794
928
  }
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;
929
+ function calleeKeyOf(filePath, node, enclosing) {
930
+ if (isBareCall(node, enclosing)) {
931
+ return nameKey(filePath, node, enclosing);
805
932
  }
806
- add(
807
- emitter,
808
- "readsProperty",
809
- nodeId(emitter.filePath, node),
810
- valueKey(emitter, receiver),
811
- method.text
812
- );
933
+ const method = node.type === "call" ? field(node, "method") : null;
934
+ if (method === null) {
935
+ return null;
936
+ }
937
+ return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
813
938
  }
814
939
  function emitCall(emitter, call) {
815
940
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
816
- const method = field(call, "method");
817
- if (method === null) {
941
+ const method = call.type === "identifier" ? call : field(call, "method");
942
+ const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
943
+ if (method === null || calleeKey === null) {
818
944
  return;
819
945
  }
820
946
  const receiver = field(call, "receiver");
821
947
  const callKey = nodeId(emitter.filePath, call);
822
- const calleeKey = receiver === null ? valueKey(emitter, method) : nodeId(emitter.filePath, method);
823
948
  add(emitter, "call", callKey, calleeKey);
949
+ if (!emitter.insideMethod) {
950
+ add(emitter, "callOutsideMethod", callKey);
951
+ }
824
952
  if (receiver !== null) {
825
953
  add(
826
954
  emitter,
@@ -829,6 +957,8 @@ function emitCall(emitter, call) {
829
957
  valueKey(emitter, receiver),
830
958
  method.text
831
959
  );
960
+ } else if (emitter.selfKey !== null) {
961
+ add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
832
962
  }
833
963
  const args = field(call, "arguments");
834
964
  let position = 0;
@@ -953,9 +1083,7 @@ function emitExpressionFacts(emitter, node) {
953
1083
  emitDynamicDefinition(emitter, node, []);
954
1084
  walkExpressions(node, emitter, (child, turns) => {
955
1085
  emitDynamicDefinition(emitter, child, turns);
956
- if (isPropertyRead(child)) {
957
- emitPropertyRead(emitter, child);
958
- } else if (child.type === "call") {
1086
+ if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
959
1087
  emitCall(emitter, child);
960
1088
  }
961
1089
  if (ARRAY_TYPES.has(child.type)) {
@@ -1011,28 +1139,52 @@ function collectInstanceWrite(emitter, node) {
1011
1139
  const operator = field(node, "operator")?.text;
1012
1140
  const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
1013
1141
  const written = collector.get(left.text) ?? [];
1014
- written.push(
1015
- describeWrite(emitter, {
1142
+ written.push({
1143
+ write: describeWrite(emitter, {
1016
1144
  name: left.text,
1017
1145
  target: left,
1018
1146
  value,
1019
1147
  at: node,
1020
1148
  fromParameter: false
1021
- })
1022
- );
1149
+ }),
1150
+ target: left
1151
+ });
1023
1152
  collector.set(left.text, written);
1024
1153
  }
1154
+ function settledWrites(writes, ordered) {
1155
+ const described = writes.map((written) => written.write);
1156
+ const settled = (0, import_resolution3.valueLeftByWrites)(described, ordered);
1157
+ if (settled !== null) {
1158
+ return [settled];
1159
+ }
1160
+ const left = [];
1161
+ for (const write of described) {
1162
+ if (write.value !== null && !write.narrowsName) {
1163
+ left.push(write.value);
1164
+ }
1165
+ }
1166
+ return left;
1167
+ }
1025
1168
  function emitInstanceWrites(emitter, classKey, collected) {
1026
1169
  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);
1170
+ for (const value of settledWrites(writes, false)) {
1171
+ add(emitter, "holdsProperty", classKey, name, value);
1172
+ }
1173
+ }
1174
+ }
1175
+ function emitInstanceStores(emitter, funcKey, body, collected) {
1176
+ for (const [name, writes] of collected) {
1177
+ const deciding = writes.filter((written) => !written.write.narrowsName);
1178
+ if (deciding.length === 0) {
1030
1179
  continue;
1031
1180
  }
1032
- for (const write of writes) {
1033
- if (write.value !== null && !write.narrowsName) {
1034
- add(emitter, "holdsProperty", classKey, name, write.value);
1035
- }
1181
+ const ordered = instanceWritesRunInOrder(
1182
+ body,
1183
+ name,
1184
+ deciding.map((written) => written.target)
1185
+ );
1186
+ for (const value of settledWrites(deciding, ordered)) {
1187
+ add(emitter, "storesProperty", funcKey, name, value);
1036
1188
  }
1037
1189
  }
1038
1190
  }
@@ -1092,7 +1244,7 @@ function emitMethodFacts(emitter, method) {
1092
1244
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1093
1245
  }
1094
1246
  }
1095
- if (child.type === "call") {
1247
+ if (child.type === "call" || isBareCall(child, method)) {
1096
1248
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
1097
1249
  }
1098
1250
  });
@@ -1160,20 +1312,32 @@ function emitScopeWrites(emitter, method, body) {
1160
1312
  }
1161
1313
  }
1162
1314
  function emitCandidates(emitter, key, group) {
1315
+ let unstated = false;
1316
+ let stated = 0;
1163
1317
  for (const write of group.writes) {
1164
1318
  if (write.value === null) {
1165
1319
  add(emitter, "writesUnstated", key);
1320
+ unstated = true;
1166
1321
  continue;
1167
1322
  }
1168
1323
  if (write.fromParameter) {
1324
+ unstated = true;
1169
1325
  continue;
1170
1326
  }
1171
1327
  if (!describeWrite(emitter, write).narrowsName) {
1172
1328
  add(emitter, "mayHold", key, valueKey(emitter, write.value));
1329
+ stated += 1;
1173
1330
  }
1174
1331
  }
1332
+ if (!unstated && stated > 0) {
1333
+ add(emitter, "writesAllStated", key);
1334
+ }
1175
1335
  }
1176
1336
  var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1337
+ var INITIALIZE_METHOD = "initialize";
1338
+ function receiverKeyOf(filePath, method, classKey) {
1339
+ return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
1340
+ }
1177
1341
  var CONSTANT_REF_TYPES = /* @__PURE__ */ new Set(["constant", "scope_resolution"]);
1178
1342
  function mixedInConstants(body, callName) {
1179
1343
  return bareCallArgumentGroups(body, callName).flatMap((group) => [...group].reverse()).reverse().map(readThrough).filter((argument) => CONSTANT_REF_TYPES.has(argument.type));
@@ -1201,6 +1365,7 @@ function emitClassFacts(emitter, cls) {
1201
1365
  const within = {
1202
1366
  ...emitter,
1203
1367
  selfKey: classKey,
1368
+ insideMethod: false,
1204
1369
  instanceWrites: collected
1205
1370
  };
1206
1371
  const statements = body === null ? [] : bodyStatementsRun(body, cls.type === "module", emitter.bodyBlocks);
@@ -1228,10 +1393,30 @@ function emitClassFacts(emitter, cls) {
1228
1393
  emitExpressionFacts(within, statement);
1229
1394
  continue;
1230
1395
  }
1231
- const funcKey = emitMethodFacts(within, statement);
1396
+ const stored = /* @__PURE__ */ new Map();
1397
+ const receiverKey = receiverKeyOf(emitter.filePath, statement, classKey);
1398
+ if (receiverKey !== classKey) {
1399
+ add(emitter, "instanceOf", receiverKey, classKey);
1400
+ }
1401
+ const funcKey = emitMethodFacts(
1402
+ {
1403
+ ...within,
1404
+ selfKey: receiverKey,
1405
+ insideMethod: receiverKey !== classKey,
1406
+ instanceWrites: stored
1407
+ },
1408
+ statement
1409
+ );
1232
1410
  const name = field(statement, "name");
1233
1411
  if (name !== null) {
1234
1412
  add(emitter, "holdsProperty", classKey, name.text, funcKey);
1413
+ if (name.text === INITIALIZE_METHOD) {
1414
+ add(emitter, "initializes", classKey, funcKey);
1415
+ }
1416
+ }
1417
+ const methodBody = field(statement, "body");
1418
+ if (methodBody !== null) {
1419
+ emitInstanceStores(within, funcKey, methodBody, stored);
1235
1420
  }
1236
1421
  }
1237
1422
  emitInstanceWrites(within, classKey, collected);
@@ -1243,6 +1428,7 @@ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
1243
1428
  filePath,
1244
1429
  enclosing: null,
1245
1430
  selfKey: null,
1431
+ insideMethod: false,
1246
1432
  instanceWrites: null,
1247
1433
  bodyBlocks
1248
1434
  };
@@ -1323,7 +1509,7 @@ function rubyLowering(options) {
1323
1509
  statement: statementOf,
1324
1510
  siteOf,
1325
1511
  functionOf,
1326
- writtenTo: (node) => context === null ? null : context.writtenTo(node),
1512
+ writtenTo: (node, site) => context === null ? null : context.writtenTo(node, site),
1327
1513
  callable: (node) => {
1328
1514
  if (!isInlinableCall(node)) {
1329
1515
  return null;
@@ -1840,17 +2026,32 @@ function siteOf(node) {
1840
2026
  current = parent;
1841
2027
  }
1842
2028
  }
2029
+ var BARE_SPLAT_TYPES = /* @__PURE__ */ new Set(["splat_parameter", "hash_splat_parameter"]);
1843
2030
  function parametersOf2(parameters) {
1844
2031
  if (parameters === null) {
1845
2032
  return [];
1846
2033
  }
1847
- return named(parameters).flatMap((parameter) => {
2034
+ const declared = [];
2035
+ let position = 0;
2036
+ for (const parameter of named(parameters)) {
2037
+ if (BARE_SPLAT_TYPES.has(parameter.type) && field(parameter, "name") === null) {
2038
+ continue;
2039
+ }
1848
2040
  if (parameter.type === "identifier") {
1849
- return [{ name: parameter.text, default: null }];
2041
+ declared.push({ name: parameter.text, default: null, position });
2042
+ } else {
2043
+ const name = field(parameter, "name");
2044
+ if (name !== null) {
2045
+ declared.push({
2046
+ name: name.text,
2047
+ default: field(parameter, "value"),
2048
+ position
2049
+ });
2050
+ }
1850
2051
  }
1851
- const name = field(parameter, "name");
1852
- return name === null ? [] : [{ name: name.text, default: field(parameter, "value") }];
1853
- });
2052
+ position += 1;
2053
+ }
2054
+ return declared;
1854
2055
  }
1855
2056
  function functionOf(node) {
1856
2057
  if (node.type === "program") {
@@ -2240,25 +2441,56 @@ var rubyRows = [
2240
2441
 
2241
2442
  // src/values/evaluator.ts
2242
2443
  var evaluators = /* @__PURE__ */ new WeakMap();
2444
+ var contexts = /* @__PURE__ */ new WeakMap();
2243
2445
  var withoutFacts = /* @__PURE__ */ new WeakMap();
2446
+ var filesByTree = /* @__PURE__ */ new WeakMap();
2244
2447
  var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
2245
2448
  function bindEvaluator(db, nodes) {
2246
- evaluators.set(
2247
- db,
2248
- new import_values2.Evaluator(
2249
- rubyLowering({ context: contextOver(db, nodes), rows: rubyRows })
2250
- )
2251
- );
2449
+ for (const entry of nodes.files) {
2450
+ filesByTree.set(entry.root.tree, entry.file);
2451
+ }
2452
+ const context = contextOver(db, nodes);
2453
+ contexts.set(db, context);
2454
+ evaluators.set(db, new import_values2.Evaluator(rubyLowering({ context, rows: rubyRows })));
2252
2455
  }
2253
- function evaluatedValue(node, db, bindings) {
2456
+ function writtenNodeOf(node, db, site) {
2457
+ const context = db === void 0 ? void 0 : contexts.get(db);
2458
+ return context === void 0 ? null : context.writtenTo(node, site);
2459
+ }
2460
+ function evaluatedValue(node, db, bindings, site) {
2254
2461
  const evaluator = evaluatorFor(node, db);
2255
2462
  if (bindings === void 0) {
2256
- return (0, import_values2.force)(evaluator.evaluate(node));
2463
+ return (0, import_values2.force)(
2464
+ site === void 0 ? evaluator.evaluate(node) : evaluator.evaluate(node, { site })
2465
+ );
2257
2466
  }
2258
2467
  const supplied = new Map(
2259
2468
  [...bindings].map(([name, value]) => [name, (0, import_values2.text)(value)])
2260
2469
  );
2261
- return (0, import_values2.force)(evaluator.evaluate(node, { bindings: supplied }));
2470
+ return (0, import_values2.force)(
2471
+ evaluator.evaluate(node, {
2472
+ bindings: supplied,
2473
+ ...site === void 0 ? {} : { site }
2474
+ })
2475
+ );
2476
+ }
2477
+ function constructionSitesOf(node, db) {
2478
+ const cls = enclosingClass(node);
2479
+ const file = cls === null ? void 0 : filesByTree.get(cls.tree);
2480
+ if (db === void 0 || cls === null || file === void 0) {
2481
+ return [];
2482
+ }
2483
+ return constructionSites(db, nodeId(file, cls));
2484
+ }
2485
+ function enclosingClass(node) {
2486
+ let current = node.parent;
2487
+ while (current !== null) {
2488
+ if (current.type === "class") {
2489
+ return current;
2490
+ }
2491
+ current = current.parent;
2492
+ }
2493
+ return null;
2262
2494
  }
2263
2495
  function stringValueOf(node, db, bindings) {
2264
2496
  return (0, import_values2.literalOf)(evaluatedValue(node, db, bindings));
@@ -2310,15 +2542,13 @@ function contextOver(db, nodes) {
2310
2542
  }
2311
2543
  const fileOf = (node) => filesByRoot.get(node.tree.rootNode.id)?.file ?? null;
2312
2544
  return {
2313
- writtenTo: (node) => {
2545
+ writtenTo: (node, site) => {
2314
2546
  const file = fileOf(node);
2315
2547
  if (file === null) {
2316
2548
  return null;
2317
2549
  }
2318
- const answer = writtenValueOf(
2319
- db,
2320
- readKey(file, node, enclosingMethod(node))
2321
- );
2550
+ const key = readKey(file, node, enclosingMethod(node));
2551
+ const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
2322
2552
  return answer === null ? null : (0, import_resolution4.nodeOfKey)(rootsByFile, answer);
2323
2553
  },
2324
2554
  callable: (call) => {
@@ -2336,6 +2566,31 @@ function contextOver(db, nodes) {
2336
2566
  };
2337
2567
  }
2338
2568
 
2569
+ // src/values/literals.ts
2570
+ var import_values4 = require("@suss/values");
2571
+ function literalElementsIn(value) {
2572
+ if (value.kind !== "sequence") {
2573
+ return null;
2574
+ }
2575
+ const elements = [];
2576
+ for (const item of value.items) {
2577
+ const literal = (0, import_values4.literalOf)((0, import_values4.force)(item.value));
2578
+ if (literal === null) {
2579
+ return null;
2580
+ }
2581
+ elements.push(literal);
2582
+ }
2583
+ return elements;
2584
+ }
2585
+ function literalElementsOf(node, db) {
2586
+ return literalElementsIn(evaluatedValue(node, db));
2587
+ }
2588
+ function namesOf(node, db) {
2589
+ const value = evaluatedValue(node, db);
2590
+ const single = (0, import_values4.literalOf)(value);
2591
+ return single === null ? literalElementsIn(value) : [single];
2592
+ }
2593
+
2339
2594
  // src/defineMethod.ts
2340
2595
  var NOTHING = {
2341
2596
  names: /* @__PURE__ */ new Set(),
@@ -2382,8 +2637,8 @@ function readOne(db, rootsByFile, nameKey2) {
2382
2637
  const names = [];
2383
2638
  const patterns = [];
2384
2639
  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);
2640
+ const value = node === null ? (0, import_values5.hole)(nameKey2) : evaluatedValue(node, db, bindings.size === 0 ? void 0 : bindings);
2641
+ const name = (0, import_values5.literalOf)(value);
2387
2642
  if (name !== null) {
2388
2643
  names.push(name);
2389
2644
  continue;
@@ -2397,7 +2652,7 @@ function readOne(db, rootsByFile, nameKey2) {
2397
2652
  return { names, patterns };
2398
2653
  }
2399
2654
  function patternOf(value) {
2400
- const pieces = (0, import_values4.piecesOf)(value);
2655
+ const pieces = (0, import_values5.piecesOf)(value);
2401
2656
  let source = "^";
2402
2657
  let readSomething = false;
2403
2658
  for (const piece of pieces) {
@@ -2439,29 +2694,14 @@ function listsBehind(db, rootsByFile, nameKey2) {
2439
2694
  0,
2440
2695
  nameKey2
2441
2696
  )) {
2442
- const elements = literalElementsOf(rootsByFile, String(overKey), db);
2697
+ const node = (0, import_resolution5.nodeOfKey)(rootsByFile, String(overKey));
2698
+ const elements = node === null ? null : literalElementsOf(node, db);
2443
2699
  if (elements !== null) {
2444
2700
  found.push({ element: String(element), index: String(index), elements });
2445
2701
  }
2446
2702
  }
2447
2703
  return found;
2448
2704
  }
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
2705
 
2466
2706
  // src/scope.ts
2467
2707
  function qualifyConstantRef(node, nesting) {
@@ -2790,107 +3030,11 @@ var import_extractor5 = require("@suss/extractor");
2790
3030
 
2791
3031
  // src/clientCalls.ts
2792
3032
  var import_behavioral_ir = require("@suss/behavioral-ir");
2793
- var import_values7 = require("@suss/values");
3033
+ var import_values9 = require("@suss/values");
2794
3034
 
2795
3035
  // src/paths/effects.ts
2796
3036
  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
- }
3037
+ var import_values7 = require("@suss/values");
2894
3038
 
2895
3039
  // src/paths/lowering.ts
2896
3040
  var RAISE_NAMES = /* @__PURE__ */ new Set(["raise", "fail"]);
@@ -3178,23 +3322,21 @@ var BLOCK_TYPES5 = /* @__PURE__ */ new Set([
3178
3322
  "do_block",
3179
3323
  "block"
3180
3324
  ]);
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
- };
3325
+ function literalArgOf(node, facts) {
3326
+ const value = evaluatedValue(node, facts);
3327
+ const text2 = (0, import_values7.literalOf)(value);
3328
+ if (text2 !== null) {
3329
+ return { kind: "string", value: text2 };
3330
+ }
3331
+ const constant2 = (0, import_values7.constantOf)(value);
3332
+ if (typeof constant2 === "number") {
3333
+ return { kind: "number", value: constant2 };
3334
+ }
3335
+ if (typeof constant2 === "boolean") {
3336
+ return { kind: "boolean", value: constant2 };
3337
+ }
3338
+ return null;
3339
+ }
3198
3340
  function children2(node) {
3199
3341
  return node.namedChildren.filter((child) => child !== null);
3200
3342
  }
@@ -3278,23 +3420,27 @@ function calleeText(call) {
3278
3420
  }
3279
3421
  return receiver === null ? method : `${receiver.text}.${method}`;
3280
3422
  }
3281
- function argOf(node) {
3282
- const literal = LITERAL_ARGS[node.type]?.(node);
3283
- if (literal !== null && literal !== void 0) {
3423
+ function argOf(node, facts) {
3424
+ const literal = literalArgOf(node, facts);
3425
+ if (literal !== null) {
3284
3426
  return literal;
3285
3427
  }
3286
3428
  if (node.type === "call" && !isArglessReceiverCall(node)) {
3287
- return { kind: "call", callee: calleeText(node), args: argsOf(node) };
3429
+ return {
3430
+ kind: "call",
3431
+ callee: calleeText(node),
3432
+ args: argsOf(node, facts)
3433
+ };
3288
3434
  }
3289
3435
  return { kind: "identifier", name: node.text };
3290
3436
  }
3291
- function argsOf(call) {
3437
+ function argsOf(call, facts) {
3292
3438
  const args = field(call, "arguments");
3293
3439
  if (args === null) {
3294
3440
  return [];
3295
3441
  }
3296
3442
  return children2(args).map(
3297
- (child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
3443
+ (child) => child.type === "pair" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3298
3444
  );
3299
3445
  }
3300
3446
  function enclosingStatement(call, body) {
@@ -3307,7 +3453,7 @@ function enclosingStatement(call, body) {
3307
3453
  }
3308
3454
  return current;
3309
3455
  }
3310
- function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS) {
3456
+ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
3311
3457
  const body = field(definitionNode, "body");
3312
3458
  if (body === null) {
3313
3459
  return [];
@@ -3347,7 +3493,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
3347
3493
  return {
3348
3494
  type: "invocation",
3349
3495
  callee: calleeText(call),
3350
- args: argsOf(call),
3496
+ args: argsOf(call, facts),
3351
3497
  async: false,
3352
3498
  ...conditions.length > 0 ? { preconditions: conditions } : {}
3353
3499
  };
@@ -3356,7 +3502,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
3356
3502
 
3357
3503
  // src/responseStatus.ts
3358
3504
  var import_extractor3 = require("@suss/extractor");
3359
- var import_values6 = require("@suss/values");
3505
+ var import_values8 = require("@suss/values");
3360
3506
 
3361
3507
  // src/paths/predicates.ts
3362
3508
  var OPERATORS = {
@@ -3425,7 +3571,7 @@ function binaryOf(node) {
3425
3571
  return opaqueOf(node);
3426
3572
  }
3427
3573
  function valueRefOf(node) {
3428
- const literal = literalOf3(node);
3574
+ const literal = literalOf5(node);
3429
3575
  if (literal !== null) {
3430
3576
  return literal;
3431
3577
  }
@@ -3438,7 +3584,7 @@ function valueRefOf(node) {
3438
3584
  }
3439
3585
  return { type: "unresolved", sourceText: node.text };
3440
3586
  }
3441
- function literalOf3(node) {
3587
+ function literalOf5(node) {
3442
3588
  if (node.type === "integer") {
3443
3589
  const value = Number.parseInt(node.text, 10);
3444
3590
  return Number.isNaN(value) ? null : { type: "literal", value };
@@ -3481,13 +3627,13 @@ function statusArgumentOf(call, declaration) {
3481
3627
  }
3482
3628
  return args.positional[declaration.statusArgument] ?? null;
3483
3629
  }
3484
- function statusNumberOf(node, names) {
3485
- const value = evaluatedValue(node);
3486
- const constant2 = (0, import_values6.constantOf)(value);
3630
+ function statusNumberOf(node, names, facts) {
3631
+ const value = evaluatedValue(node, facts);
3632
+ const constant2 = (0, import_values8.constantOf)(value);
3487
3633
  if (typeof constant2 === "number") {
3488
3634
  return constant2;
3489
3635
  }
3490
- const name = (0, import_values6.literalOf)(value);
3636
+ const name = (0, import_values8.literalOf)(value);
3491
3637
  return name === null ? null : names[name] ?? null;
3492
3638
  }
3493
3639
  function declarationsByName(declarations) {
@@ -3531,12 +3677,12 @@ function collectReturns(node, found) {
3531
3677
  }
3532
3678
  return found;
3533
3679
  }
3534
- function readingOfCall(call, declaration, names) {
3680
+ function readingOfCall(call, declaration, names, facts) {
3535
3681
  const argument = statusArgumentOf(call, declaration);
3536
3682
  if (argument === null) {
3537
3683
  return declaration.defaultStatusCode === void 0 ? import_extractor3.absentReading : (0, import_extractor3.writtenReading)(declaration.defaultStatusCode, rangeOf(call));
3538
3684
  }
3539
- const status = statusNumberOf(argument, names);
3685
+ const status = statusNumberOf(argument, names, facts);
3540
3686
  if (status === null) {
3541
3687
  return (0, import_extractor3.unreadableReading)(
3542
3688
  "This response writes a status that does not settle on a number here, so this outcome claims none",
@@ -3680,7 +3826,7 @@ function responseBranches(method, pattern, effects, extraEffects, options = {})
3680
3826
  const outcomes = [];
3681
3827
  for (const terminal of terminals) {
3682
3828
  const declaration = declarationOf(terminal, byName, locals);
3683
- const reading = declaration === void 0 ? import_extractor3.absentReading : readingOfCall(terminal, declaration, statusNames);
3829
+ const reading = declaration === void 0 ? import_extractor3.absentReading : readingOfCall(terminal, declaration, statusNames, options.facts);
3684
3830
  for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
3685
3831
  outcomes.push({
3686
3832
  conditions: path7.map(conditionOf),
@@ -3714,11 +3860,9 @@ function clientCallUnits(root, pack, pattern, options) {
3714
3860
  continue;
3715
3861
  }
3716
3862
  for (const call of callsUnder(method)) {
3717
- const request = requestCall(call, method, root, pattern, options);
3718
- if (request === null) {
3719
- continue;
3863
+ for (const request of requestCalls(call, pattern, options)) {
3864
+ units.push(clientUnit(method, name, request, pattern, pack, options));
3720
3865
  }
3721
- units.push(clientUnit(method, name, request, pattern, pack, options));
3722
3866
  }
3723
3867
  }
3724
3868
  return units;
@@ -3747,36 +3891,60 @@ function callsUnder(node, found = []) {
3747
3891
  }
3748
3892
  return found;
3749
3893
  }
3750
- function requestCall(call, method, root, pattern, options) {
3894
+ function requestCalls(call, pattern, options) {
3751
3895
  const receiver = field(call, "receiver");
3752
3896
  const called = field(call, "method")?.text;
3753
- if (receiver === null || called === void 0) {
3754
- return null;
3897
+ if (receiver === null || called === void 0 || !isLibraryReceiver(receiver, pattern, options)) {
3898
+ return [];
3755
3899
  }
3756
- const prefix = receiverPrefix(receiver, method, root, pattern, options);
3757
- if (prefix === null) {
3758
- return null;
3900
+ const read = (site) => requestCall(
3901
+ call,
3902
+ called,
3903
+ receiverPrefix(receiver, pattern, options, site),
3904
+ pattern,
3905
+ options,
3906
+ site
3907
+ );
3908
+ const plain = read();
3909
+ if (plain !== null && !(0, import_behavioral_ir.hasNameHole)(plain.path)) {
3910
+ return [plain];
3911
+ }
3912
+ const byBoundary = /* @__PURE__ */ new Map();
3913
+ for (const site of constructionSitesOf(call, options.facts)) {
3914
+ const stated = read(site);
3915
+ if (stated !== null) {
3916
+ byBoundary.set(`${stated.method} ${stated.path}`, stated);
3917
+ }
3759
3918
  }
3919
+ if (byBoundary.size > 0) {
3920
+ return [...byBoundary.values()];
3921
+ }
3922
+ return plain === null ? [] : [plain];
3923
+ }
3924
+ function requestCall(call, called, prefix, pattern, options, site) {
3760
3925
  const args = readCallArgs(field(call, "arguments"));
3761
3926
  const verb = pattern.verbMethodNames[called];
3762
3927
  if (verb !== void 0) {
3763
- const path8 = urlIn(args, pattern, options);
3928
+ const path8 = urlIn(args, pattern, options, site);
3764
3929
  return path8 === null ? null : { method: verb, path: prefix + path8 };
3765
3930
  }
3766
3931
  const sent = pattern.requestObject;
3767
3932
  if (sent === void 0 || sent.attribute !== called) {
3768
3933
  return null;
3769
3934
  }
3770
- const built = requestBuilt(args.positional[0], method, sent);
3935
+ const built = requestBuilt(args.positional[0], sent, options, site);
3771
3936
  if (built === null) {
3772
3937
  return null;
3773
3938
  }
3774
- const path7 = pathAt(built.url, options);
3939
+ const path7 = pathAt(built.url, options, site);
3775
3940
  return path7 === null ? null : { method: built.method, path: prefix + path7 };
3776
3941
  }
3777
- function requestBuilt(argument, method, sent) {
3778
- const written = argument === void 0 ? null : assignedCall(argument, method) ?? argument;
3779
- if (written === null || written.type !== "call") {
3942
+ function requestBuilt(argument, sent, options, site) {
3943
+ if (argument === void 0) {
3944
+ return null;
3945
+ }
3946
+ const written = writtenNodeOf(argument, options.facts, site) ?? argument;
3947
+ if (written.type !== "call") {
3780
3948
  return null;
3781
3949
  }
3782
3950
  const requestClass = field(written, "receiver")?.text;
@@ -3787,28 +3955,18 @@ function requestBuilt(argument, method, sent) {
3787
3955
  const args = readCallArgs(field(written, "arguments"));
3788
3956
  return { method: verb, url: args.positional[sent.urlPosition] };
3789
3957
  }
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;
3958
+ function isLibraryReceiver(receiver, pattern, options) {
3959
+ return namesConstant(receiver, pattern.constantName) || builderCallBehind(receiver, pattern, options.facts) !== null;
3796
3960
  }
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);
3961
+ function receiverPrefix(receiver, pattern, options, site) {
3962
+ const built = builderCallBehind(receiver, pattern, options.facts);
3805
3963
  if (built === null) {
3806
- return null;
3964
+ return "";
3807
3965
  }
3808
3966
  const args = readCallArgs(field(built, "arguments"));
3809
3967
  const keyword = pattern.builderUrlKeyword;
3810
3968
  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));
3969
+ const path7 = base === void 0 ? null : (0, import_values9.pathOf)(evaluatedValue(base, options.facts, void 0, site));
3812
3970
  return path7 === void 0 || path7 === null ? "" : trimmed(path7);
3813
3971
  }
3814
3972
  function trimmed(path7) {
@@ -3820,61 +3978,28 @@ function namesConstant(receiver, constantName2) {
3820
3978
  }
3821
3979
  return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
3822
3980
  }
3823
- function builtBy(name, method, root, pattern) {
3981
+ function builderCallBehind(receiver, pattern, facts) {
3824
3982
  const builders = pattern.receiverBuilders ?? [];
3825
3983
  if (builders.length === 0) {
3826
3984
  return null;
3827
3985
  }
3828
- const value = assignedValue(name, method) ?? valueMethodNamedReturns(name, root);
3829
- if (value === null || value.type !== "call" || !builders.includes(field(value, "method")?.text ?? "")) {
3986
+ const written = writtenNodeOf(receiver, facts);
3987
+ if (written === null || written.type !== "call" || !builders.includes(field(written, "method")?.text ?? "")) {
3830
3988
  return null;
3831
3989
  }
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;
3990
+ const constant2 = field(written, "receiver");
3991
+ return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
3852
3992
  }
3853
- function assignedValue(name, method) {
3854
- const body = field(method, "body");
3855
- if (body === null) {
3856
- return null;
3857
- }
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;
3867
- }
3868
- function urlIn(args, pattern, options) {
3993
+ function urlIn(args, pattern, options, site) {
3869
3994
  const keyword = pattern.url.keyword;
3870
3995
  const written = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
3871
- return pathAt(written, options);
3996
+ return pathAt(written, options, site);
3872
3997
  }
3873
- function pathAt(written, options) {
3998
+ function pathAt(written, options, site) {
3874
3999
  if (written === void 0) {
3875
4000
  return null;
3876
4001
  }
3877
- const path7 = (0, import_values7.pathOf)(evaluatedValue(written, options.facts));
4002
+ const path7 = (0, import_values9.pathOf)(evaluatedValue(written, options.facts, void 0, site));
3878
4003
  return path7 === void 0 || (0, import_behavioral_ir.namesNothing)(path7) ? null : path7;
3879
4004
  }
3880
4005
  function clientUnit(method, name, request, pattern, pack, options) {
@@ -3897,15 +4022,15 @@ function clientUnit(method, name, request, pattern, pack, options) {
3897
4022
  recognition: pack.name
3898
4023
  }),
3899
4024
  parameters: [],
3900
- branches: callerBranches(method, range),
4025
+ branches: callerBranches(method, range, options.facts),
3901
4026
  ...responseAccessors(pattern),
3902
4027
  bodyContent: "statements",
3903
4028
  dependencyCalls: [],
3904
4029
  declaredContract: null
3905
4030
  };
3906
4031
  }
3907
- function callerBranches(method, range) {
3908
- const effects = invocationEffects(method);
4032
+ function callerBranches(method, range, facts) {
4033
+ const effects = invocationEffects(method, void 0, void 0, facts);
3909
4034
  return returnPathBranches(method, effects) ?? [returnBranch(range, effects)];
3910
4035
  }
3911
4036
  function responseAccessors(pattern) {
@@ -4063,7 +4188,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4063
4188
  }
4064
4189
  const form = byName.get(called);
4065
4190
  if (form !== void 0) {
4066
- for (const declaration of declarationsOf(statement, form)) {
4191
+ for (const declaration of declarationsOf(statement, form, read.facts)) {
4067
4192
  declared = [
4068
4193
  ...declared.filter((earlier) => !sameFilter(earlier, declaration)),
4069
4194
  declaration
@@ -4073,7 +4198,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4073
4198
  }
4074
4199
  const skipped = skipNames.get(called);
4075
4200
  if (skipped !== void 0) {
4076
- declared = applySkips(declared, skipsOf(statement, skipped));
4201
+ declared = applySkips(declared, skipsOf(statement, skipped, read.facts));
4077
4202
  }
4078
4203
  }
4079
4204
  const resolved = [];
@@ -4110,14 +4235,14 @@ function filterReference(filter, displayPath) {
4110
4235
  ...filter.filter.onThrow === true ? { onThrow: true } : {}
4111
4236
  };
4112
4237
  }
4113
- function filterUnit(filter, pattern, displayPath, body) {
4238
+ function filterUnit(filter, pattern, displayPath, body, facts) {
4114
4239
  const range = rangeOf(filter.method);
4115
4240
  const branches = responseBranches(
4116
4241
  filter.method,
4117
4242
  pattern,
4118
4243
  body.effects ?? [],
4119
4244
  body.extraEffects,
4120
- { fallthrough: "handOn" }
4245
+ { fallthrough: "handOn", facts }
4121
4246
  );
4122
4247
  return {
4123
4248
  identity: {
@@ -4165,30 +4290,30 @@ function calledName(statement) {
4165
4290
  }
4166
4291
  return field(statement, "method")?.text ?? null;
4167
4292
  }
4168
- function declarationsOf(statement, filter) {
4293
+ function declarationsOf(statement, filter, facts) {
4169
4294
  const args = readCallArgs(field(statement, "arguments"));
4170
4295
  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) => ({
4296
+ const only = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.include], facts);
4297
+ const except = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.exclude], facts);
4298
+ return methodNamesOf(args, filter, facts).map((methodName) => ({
4174
4299
  filter,
4175
4300
  methodName,
4176
4301
  only,
4177
4302
  except: except ?? /* @__PURE__ */ new Set()
4178
4303
  }));
4179
4304
  }
4180
- function methodNamesOf(args, filter) {
4305
+ function methodNamesOf(args, filter, facts) {
4181
4306
  if (filter.methodFrom === "withKeyword") {
4182
4307
  const named2 = args.keyword.with;
4183
- const value = named2 === void 0 ? null : symbolValue(named2);
4308
+ const value = named2 === void 0 ? null : stringValueOf(named2, facts);
4184
4309
  return value === null ? [] : [value];
4185
4310
  }
4186
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null);
4311
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
4187
4312
  }
4188
- function skipsOf(statement, filterName) {
4313
+ function skipsOf(statement, filterName, facts) {
4189
4314
  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 }));
4315
+ const actions = actionsUnder(args.keyword.only, facts);
4316
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
4192
4317
  }
4193
4318
  function sameFilter(one, other) {
4194
4319
  return one.filter.name === other.filter.name && one.methodName === other.methodName;
@@ -4212,26 +4337,12 @@ function applySkips(declared, skips) {
4212
4337
  }
4213
4338
  return left;
4214
4339
  }
4215
- function namesUnder(node) {
4340
+ function actionsUnder(node, facts) {
4216
4341
  if (node === void 0) {
4217
4342
  return null;
4218
4343
  }
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;
4344
+ const found = namesOf(node, facts);
4345
+ return found === null ? null : new Set(found);
4235
4346
  }
4236
4347
 
4237
4348
  // src/storage.ts
@@ -4309,67 +4420,64 @@ function argumentsOf2(call) {
4309
4420
  const args = field(call, "arguments");
4310
4421
  return args === null ? [] : children3(args);
4311
4422
  }
4312
- function isKeywordArgument(argument) {
4313
- return argument.type === "pair" || argument.type === "hash";
4423
+ function recordFieldsOf(argument, facts) {
4424
+ const value = evaluatedValue(argument, facts);
4425
+ return value.kind === "record" ? [...value.fields.keys()] : [];
4314
4426
  }
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) {
4427
+ function keywordKeys(call, facts) {
4328
4428
  const keys = [];
4329
4429
  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
- }
4430
+ if (argument.type !== "pair") {
4431
+ keys.push(...recordFieldsOf(argument, facts));
4432
+ continue;
4433
+ }
4434
+ const key = field(argument, "key");
4435
+ const name = key === null ? null : keyNameOf(key);
4436
+ if (name !== null) {
4437
+ keys.push(name);
4335
4438
  }
4336
4439
  }
4337
4440
  return keys;
4338
4441
  }
4339
- function hasPositionalArgument(call) {
4340
- return argumentsOf2(call).some((argument) => !isKeywordArgument(argument));
4442
+ function keyNameOf(key) {
4443
+ return hashKeySymbolName(key) ?? symbolValue(key) ?? stringLiteralValue(key);
4341
4444
  }
4342
- function columnsAskedFor(call, pattern) {
4445
+ function hasPositionalArgument(call, facts) {
4446
+ return argumentsOf2(call).some(
4447
+ (argument) => argument.type !== "pair" && recordFieldsOf(argument, facts).length === 0
4448
+ );
4449
+ }
4450
+ function columnsAskedFor(call, pattern, facts) {
4343
4451
  if (!(pattern.columnArguments ?? []).includes(methodOf(call))) {
4344
4452
  return [];
4345
4453
  }
4346
- return argumentsOf2(call).filter((argument) => argument.type === "simple_symbol").map(bareName);
4454
+ return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
4347
4455
  }
4348
- function primaryKeySelector(call, pattern) {
4456
+ function primaryKeySelector(call, pattern, facts) {
4349
4457
  const byPrimaryKey = pattern.byPrimaryKey;
4350
4458
  if (byPrimaryKey === void 0) {
4351
4459
  return [];
4352
4460
  }
4353
- const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call);
4461
+ const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call, facts);
4354
4462
  return picks ? [byPrimaryKey.column] : [];
4355
4463
  }
4356
- function selectorOf(call, pattern) {
4464
+ function selectorOf(call, pattern, facts) {
4357
4465
  const picked = [];
4358
4466
  for (const link of chainLinks(call)) {
4359
- picked.push(...primaryKeySelector(link, pattern));
4467
+ picked.push(...primaryKeySelector(link, pattern, facts));
4360
4468
  if (pattern.reads.includes(methodOf(link))) {
4361
- picked.push(...keywordKeys(link));
4469
+ picked.push(...keywordKeys(link, facts));
4362
4470
  }
4363
4471
  }
4364
4472
  return [...new Set(picked)];
4365
4473
  }
4366
- function fieldsOf(call, pattern, kind) {
4474
+ function fieldsOf(call, pattern, kind, facts) {
4367
4475
  if (kind === "write") {
4368
- return [...new Set(keywordKeys(call))];
4476
+ return [...new Set(keywordKeys(call, facts))];
4369
4477
  }
4370
4478
  return [
4371
4479
  ...new Set(
4372
- chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern))
4480
+ chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern, facts))
4373
4481
  )
4374
4482
  ];
4375
4483
  }
@@ -4461,8 +4569,8 @@ function modelCallEffects(call, file, options, enclosing) {
4461
4569
  target.container,
4462
4570
  pattern,
4463
4571
  kind,
4464
- selectorOf(worked, pattern),
4465
- fieldsOf(worked, pattern, kind)
4572
+ selectorOf(worked, pattern, options.facts),
4573
+ fieldsOf(worked, pattern, kind, options.facts)
4466
4574
  )
4467
4575
  ];
4468
4576
  }
@@ -4782,7 +4890,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4782
4890
  filter,
4783
4891
  pattern,
4784
4892
  displayPath,
4785
- bodyOfMethod(filter.method, filter.file, options)
4893
+ bodyOfMethod(filter.method, filter.file, options),
4894
+ options.facts
4786
4895
  );
4787
4896
  units.push(raw);
4788
4897
  options.onReachSeed?.(raw, {
@@ -4873,7 +4982,8 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
4873
4982
  method,
4874
4983
  pattern,
4875
4984
  body.effects ?? [],
4876
- body.extraEffects
4985
+ body.extraEffects,
4986
+ { facts: bodyRead.facts }
4877
4987
  );
4878
4988
  return {
4879
4989
  identity: {
@@ -5003,7 +5113,8 @@ function bodyOfMethod(method, file, bodyRead = {}) {
5003
5113
  const effects = invocationEffects(
5004
5114
  method,
5005
5115
  bodyRead.inheritedMethods,
5006
- EVERY_ARGLESS_CALL
5116
+ EVERY_ARGLESS_CALL,
5117
+ bodyRead.facts
5007
5118
  );
5008
5119
  const storage = bodyRead.storage;
5009
5120
  const extra = [
@@ -5679,7 +5790,7 @@ function emitConstantBindings(db, perFile) {
5679
5790
  var import_node_fs4 = __toESM(require("fs"), 1);
5680
5791
  var import_node_path5 = __toESM(require("path"), 1);
5681
5792
  var import_datalog4 = require("@suss/datalog");
5682
- var import_resolution8 = require("@suss/resolution");
5793
+ var import_resolution9 = require("@suss/resolution");
5683
5794
 
5684
5795
  // src/parser.ts
5685
5796
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -5743,6 +5854,7 @@ var import_node_path4 = __toESM(require("path"), 1);
5743
5854
  var import_behavioral_ir5 = require("@suss/behavioral-ir");
5744
5855
  var import_datalog3 = require("@suss/datalog");
5745
5856
  var import_extractor10 = require("@suss/extractor");
5857
+ var import_resolution8 = require("@suss/resolution");
5746
5858
 
5747
5859
  // src/diagnostics.ts
5748
5860
  var import_extractor6 = require("@suss/extractor");
@@ -6506,20 +6618,41 @@ function associationCallsIn(packs) {
6506
6618
  )
6507
6619
  );
6508
6620
  }
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
- }
6621
+ function packWordsOf(packs) {
6622
+ return {
6623
+ givesBackOne: packs.flatMap(
6624
+ (pack) => (pack.storage ?? []).flatMap(
6625
+ (pattern) => pattern.baseClasses.flatMap(
6626
+ (base) => pattern.givesBack.map((method) => ({ base, method }))
6627
+ )
6628
+ )
6629
+ )
6630
+ };
6631
+ }
6632
+ function factsForFile(options) {
6633
+ const db = new import_datalog3.Database();
6634
+ const bodyBlocks = bodyBlocksIn(options.packs);
6635
+ emitValueFacts(db, options.file, options.root, bodyBlocks);
6636
+ emitConstantBindings(db, [
6637
+ collectFileConstants(
6638
+ options.file,
6639
+ options.root,
6640
+ associationCallsIn(options.packs),
6641
+ inflectionsIn(options.packs)
6642
+ )
6643
+ ]);
6644
+ emitRequireFacts(db, options.file, options.root, /* @__PURE__ */ new Set([options.file]));
6645
+ bindEvaluator(db, {
6646
+ files: [{ file: options.file, root: options.root }],
6647
+ definitions: new Map(methodDefinitionsIn(options.file, options.root))
6648
+ });
6649
+ (0, import_resolution8.addPackWords)(db, packWordsOf(options.packs));
6650
+ return db;
6519
6651
  }
6520
6652
  function alreadyDiscovered(seen, raw) {
6521
6653
  const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
6522
- const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
6654
+ const boundary = raw.boundaryBinding === null ? "" : (0, import_behavioral_ir5.boundaryKey)(raw.boundaryBinding) ?? "";
6655
+ const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}::${boundary}`;
6523
6656
  if (seen.has(key)) {
6524
6657
  return true;
6525
6658
  }
@@ -6586,7 +6719,7 @@ async function extractRubyProject(options) {
6586
6719
  emitRequireFacts(db, file, root, known);
6587
6720
  }
6588
6721
  bindEvaluator(db, { files: parsed, definitions });
6589
- emitStorageFacts(db, options.packs);
6722
+ (0, import_resolution8.addPackWords)(db, packWordsOf(options.packs));
6590
6723
  return readDynamicNames(
6591
6724
  db,
6592
6725
  new Map(parsed.map(({ file, root }) => [file, root]))
@@ -6810,7 +6943,7 @@ function findRubyFiles(root) {
6810
6943
  }
6811
6944
 
6812
6945
  // src/facts/why.ts
6813
- var WITNESS_RULES = [...import_resolution8.RESOLUTION_RULES, ...RUBY_RULES];
6946
+ var WITNESS_RULES = [...import_resolution9.RESOLUTION_RULES, ...RUBY_RULES];
6814
6947
  var METHOD_TYPES5 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
6815
6948
  function namedChildrenOf(node) {
6816
6949
  return node.namedChildren.filter((child) => child !== null);
@@ -6859,6 +6992,10 @@ function displayNameOf(node) {
6859
6992
  function width(node) {
6860
6993
  return node.endIndex - node.startIndex;
6861
6994
  }
6995
+ function askedKey(value) {
6996
+ const enclosing = enclosingMethod2(value.node);
6997
+ return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
6998
+ }
6862
6999
  var RubyWhySession = class {
6863
7000
  root;
6864
7001
  db = new import_datalog4.Database();
@@ -6931,10 +7068,10 @@ var RubyWhySession = class {
6931
7068
  * which the caller says in its own words.
6932
7069
  */
6933
7070
  explain(value, options = {}) {
6934
- return (0, import_resolution8.explainResolvedKey)({
7071
+ return (0, import_resolution9.explainResolvedKey)({
6935
7072
  db: this.db,
6936
7073
  rules: WITNESS_RULES,
6937
- key: readKey(value.file, value.node, enclosingMethod2(value.node)),
7074
+ key: askedKey(value),
6938
7075
  locate: (key) => this.locate(key),
6939
7076
  displayPath: (key) => this.displayPath(key),
6940
7077
  ...options
@@ -7052,6 +7189,7 @@ async function rubyStubEvidence(options) {
7052
7189
  emitValueFacts,
7053
7190
  evaluatedValue,
7054
7191
  extractRubyProject,
7192
+ factsForFile,
7055
7193
  field,
7056
7194
  findRubyFiles,
7057
7195
  graphqlTypeNameFromQualified,