@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.js CHANGED
@@ -327,7 +327,7 @@ function autoloadDirectories(root) {
327
327
 
328
328
  // src/defineMethod.ts
329
329
  import { nodeOfKey as nodeOfKey2 } from "@suss/resolution";
330
- import { force as force2, hole as hole2, literalOf as literalOf2, piecesOf } from "@suss/values";
330
+ import { hole as hole2, literalOf as literalOf3, piecesOf } from "@suss/values";
331
331
 
332
332
  // src/values/evaluator.ts
333
333
  import { nodeOfKey } from "@suss/resolution";
@@ -336,63 +336,56 @@ import { Evaluator, force, literalOf, text } from "@suss/values";
336
336
  // src/facts/resolve.ts
337
337
  import { constant, lit, rule, variable as v } from "@suss/datalog";
338
338
  import {
339
+ allocationSitesOf,
340
+ alsoSteps,
339
341
  askResolution,
342
+ askResolutionUnder,
343
+ INSTANCE_STEP,
340
344
  resolutionProgram,
345
+ resolutionUnderProgram,
341
346
  writtenValueOf as sharedWrittenValueOf,
347
+ writtenValueUnder as sharedWrittenValueUnder,
342
348
  VALUE_STEP
343
349
  } from "@suss/resolution";
344
- var RUBY_RULES = [
345
- // `Loader.new` makes one of the class, which the shared rules already say
346
- // about calling a class. Ruby writes it as a method read off the constant
347
- // instead, so the read is what steps to the class here.
350
+ var RUBY_RULES = alsoSteps([
351
+ // Making one of a class is a call of the class, which the shared rules
352
+ // already say. Ruby writes the callee as `new` read off the constant, so
353
+ // what this adds is that the callee is the class.
348
354
  rule(
349
- "stepsTo",
350
- [v("x"), v("cls"), VALUE_STEP],
355
+ "hop",
356
+ [v("x"), v("cls"), INSTANCE_STEP],
351
357
  [
352
358
  lit("readsProperty", v("x"), v("o"), constant("new")),
353
359
  lit("comesTo", v("o"), v("cls")),
354
360
  lit("objectValue", v("cls"))
355
361
  ]
356
362
  ),
357
- // The shared `declared finder` step, for the same spelling: `Account.first`
358
- // and `Account.where(x).first` write no arguments, so Ruby reads them as
359
- // property reads and neither gets a `call` fact to hang that step on.
363
+ // The same spelling again, for the caller asking where a class was
364
+ // made. `callsNamed` reaches a callee written as the name itself, and
365
+ // Ruby writes `new` off the constant instead.
360
366
  rule(
361
- "stepsTo",
362
- [v("x"), v("cls"), VALUE_STEP],
367
+ "constructsNamed",
368
+ [v("r"), v("cls")],
363
369
  [
364
- lit("readsProperty", v("x"), v("o"), v("m")),
365
- lit("objectOf", v("o"), v("cls")),
366
- lit("objectValue", v("cls")),
367
- lit("libraryBase", v("cls"), v("n")),
368
- lit("givesBackOne", v("n"), v("m"))
369
- ],
370
- "declared finder"
371
- ),
372
- // Ruby cannot refer to a method without running it, so
373
- // `Settings.filters` is worth what the method returns. The shared
374
- // rules hang that step on a `call` fact this spelling never gets.
375
- rule(
376
- "invokes",
377
- [v("x"), v("f")],
378
- [
379
- lit("readsProperty", v("x"), v("o"), v("m")),
380
- lit("comesTo", v("x"), v("f")),
381
- lit("func", v("f"))
382
- ],
383
- "property read runs a method"
370
+ lit("binds", v("o"), v("cls")),
371
+ lit("readsProperty", v("c"), v("o"), constant("new")),
372
+ lit("call", v("r"), v("c"))
373
+ ]
384
374
  ),
385
375
  // `%i[a b].freeze` is worth the list it was written as. The evaluator
386
376
  // says the same in its row table, for a value it reads in one file.
387
377
  ...["freeze", "dup"].map(
388
378
  (method) => rule(
389
- "stepsTo",
390
- [v("x"), v("o"), VALUE_STEP],
391
- [lit("readsProperty", v("x"), v("o"), constant(method))],
379
+ "hop",
380
+ [v("r"), v("o"), VALUE_STEP],
381
+ [
382
+ lit("call", v("r"), v("c")),
383
+ lit("readsProperty", v("c"), v("o"), constant(method))
384
+ ],
392
385
  "hands back the receiver"
393
386
  )
394
387
  )
395
- ];
388
+ ]);
396
389
  var RUBY_PROGRAM = resolutionProgram(RUBY_RULES);
397
390
  function resolveValues(db, keys) {
398
391
  askResolution(db, keys, "wanted", RUBY_PROGRAM);
@@ -404,10 +397,125 @@ function writtenValueOf(db, key) {
404
397
  resolveValues(db, [key]);
405
398
  return sharedWrittenValueOf(db, key, (keys) => resolveValues(db, keys));
406
399
  }
400
+ function writtenValueUnder(db, key, site) {
401
+ const outcome = askResolutionUnder(
402
+ db,
403
+ [[key, site]],
404
+ resolutionUnderProgram(RUBY_RULES)
405
+ );
406
+ if (outcome === "abandoned") {
407
+ return null;
408
+ }
409
+ return sharedWrittenValueUnder(db, key, site);
410
+ }
411
+ function constructionSites(db, classKey) {
412
+ askResolution(db, [classKey], "wantedSites", RUBY_PROGRAM);
413
+ return allocationSitesOf(db, classKey);
414
+ }
407
415
 
408
416
  // src/facts/values.ts
409
417
  import { startsAtName, valueLeftByWrites } from "@suss/resolution";
410
418
 
419
+ // src/paths/bareCalls.ts
420
+ var PARAMETER_LIST_TYPES = [
421
+ "method_parameters",
422
+ "block_parameters",
423
+ "lambda_parameters",
424
+ "parameters"
425
+ ];
426
+ function isFieldOf(parent, node, name) {
427
+ return field(parent, name)?.id === node.id;
428
+ }
429
+ function namedAt(parent, node) {
430
+ return isFieldOf(parent, node, "name");
431
+ }
432
+ function always() {
433
+ return true;
434
+ }
435
+ var SPELLS_A_NAME = {
436
+ call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
437
+ method: namedAt,
438
+ singleton_method: namedAt,
439
+ assignment: (parent, node) => isFieldOf(parent, node, "left"),
440
+ operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
441
+ for: (parent, node) => isFieldOf(parent, node, "pattern"),
442
+ optional_parameter: namedAt,
443
+ splat_parameter: namedAt,
444
+ hash_splat_parameter: namedAt,
445
+ keyword_parameter: namedAt,
446
+ block_parameter: namedAt,
447
+ destructured_parameter: always,
448
+ left_assignment_list: always,
449
+ rest_assignment: always,
450
+ destructured_left_assignment: always,
451
+ exception_variable: always,
452
+ undef: always,
453
+ alias: always,
454
+ ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
455
+ };
456
+ function addNamesUnder(node, names) {
457
+ if (node === null) {
458
+ return;
459
+ }
460
+ if (node.type === "identifier") {
461
+ names.add(node.text);
462
+ return;
463
+ }
464
+ for (const child of bodyStatements(node)) {
465
+ addNamesUnder(child, names);
466
+ }
467
+ }
468
+ function parameterNames(node, names) {
469
+ for (const parameter of bodyStatements(node)) {
470
+ if (parameter.type === "identifier") {
471
+ names.add(parameter.text);
472
+ continue;
473
+ }
474
+ const declared = field(parameter, "name");
475
+ if (declared !== null && declared.type === "identifier") {
476
+ names.add(declared.text);
477
+ continue;
478
+ }
479
+ addNamesUnder(parameter, names);
480
+ }
481
+ }
482
+ function assignedNames(node, names) {
483
+ addNamesUnder(field(node, "left"), names);
484
+ }
485
+ var BINDS_A_NAME = {
486
+ assignment: assignedNames,
487
+ operator_assignment: assignedNames,
488
+ for: (node, names) => addNamesUnder(field(node, "pattern"), names),
489
+ exception_variable: (node, names) => addNamesUnder(node, names),
490
+ ...Object.fromEntries(
491
+ PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
492
+ )
493
+ };
494
+ function localNamesIn(definitionNode) {
495
+ const names = /* @__PURE__ */ new Set();
496
+ const visit = (node) => {
497
+ BINDS_A_NAME[node.type]?.(node, names);
498
+ for (const child of bodyStatements(node)) {
499
+ visit(child);
500
+ }
501
+ };
502
+ visit(definitionNode);
503
+ return names;
504
+ }
505
+ function spellsAName(node) {
506
+ const parent = node.parent;
507
+ if (parent === null) {
508
+ return true;
509
+ }
510
+ return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
511
+ }
512
+ function isBareMethodCall(node, locals) {
513
+ if (node.type !== "identifier" || locals.has(node.text)) {
514
+ return false;
515
+ }
516
+ return !spellsAName(node);
517
+ }
518
+
411
519
  // src/facts/locals.ts
412
520
  import { writesRunInOrder } from "@suss/resolution";
413
521
  var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
@@ -432,7 +540,7 @@ function parametersOf(owner) {
432
540
  const params = field(owner, "parameters");
433
541
  return params === null ? [] : bodyStatements(params);
434
542
  }
435
- function parameterNames(owner) {
543
+ function parameterNames2(owner) {
436
544
  const names = /* @__PURE__ */ new Set();
437
545
  for (const param of parametersOf(owner)) {
438
546
  const name = paramNameOf(param);
@@ -480,6 +588,13 @@ function readForVariable(node) {
480
588
  }
481
589
  return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
482
590
  }
591
+ function readRescueVariable(node) {
592
+ const variable = field(node, "variable");
593
+ if (variable === null) {
594
+ return [];
595
+ }
596
+ return bodyStatements(variable).flatMap(targetsOf).map((target) => writeOf(target, null, node, false));
597
+ }
483
598
  function readBlockParameters(node) {
484
599
  const found = [];
485
600
  for (const param of parametersOf(node)) {
@@ -494,6 +609,7 @@ var WRITE_READERS = {
494
609
  assignment: readAssignment,
495
610
  operator_assignment: readOperatorAssignment,
496
611
  for: readForVariable,
612
+ rescue: readRescueVariable,
497
613
  block: readBlockParameters,
498
614
  do_block: readBlockParameters
499
615
  };
@@ -539,7 +655,7 @@ function namesOfMethod(method) {
539
655
  if (remembered !== void 0) {
540
656
  return remembered;
541
657
  }
542
- const names = scopeNames(parameterNames(method), field(method, "body"));
658
+ const names = scopeNames(parameterNames2(method), field(method, "body"));
543
659
  cache.byMethod.set(method, names);
544
660
  return names;
545
661
  }
@@ -561,10 +677,17 @@ function ownerOfName(node, name, enclosing) {
561
677
  }
562
678
  return enclosing !== null && names.locals.has(name) ? enclosing : null;
563
679
  }
680
+ function isLocalName(node, name, enclosing) {
681
+ const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
682
+ if (names.blockParams.has(name) && blockDeclaring(node, name) !== null) {
683
+ return true;
684
+ }
685
+ return names.locals.has(name);
686
+ }
564
687
  function blockDeclaring(node, name) {
565
688
  let current = node.parent;
566
689
  while (current !== null) {
567
- if (BLOCK_TYPES2.has(current.type) && parameterNames(current).has(name)) {
690
+ if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
568
691
  return current;
569
692
  }
570
693
  if (OWN_BODY_TYPES.has(current.type)) {
@@ -620,6 +743,17 @@ function collectWrites(method, body) {
620
743
  }
621
744
  return [...groups.values()];
622
745
  }
746
+ function instanceWritesRunInOrder(body, name, targets) {
747
+ return writesRunInOrder(
748
+ body,
749
+ name,
750
+ targets.map((target) => ({
751
+ at: target,
752
+ direct: target.parent?.parent?.id === body.id
753
+ })),
754
+ nameReads(new Set(targets.map((target) => target.id)))
755
+ );
756
+ }
623
757
  function orderedWrite(write, scope) {
624
758
  return {
625
759
  at: write.at,
@@ -665,12 +799,28 @@ function readKey(filePath, written, enclosing) {
665
799
  if (node.type !== "identifier" && node.type !== "constant") {
666
800
  return nodeId(filePath, node);
667
801
  }
668
- const parent = node.parent;
669
- if (parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null) {
670
- return isPropertyRead(parent) ? nodeId(filePath, parent) : nodeId(filePath, node);
802
+ if (isMethodOfReceiver(node) || isBareCall(node, enclosing)) {
803
+ return nodeId(filePath, node);
671
804
  }
672
805
  return nameKey(filePath, node, enclosing);
673
806
  }
807
+ function isMethodOfReceiver(node) {
808
+ const parent = node.parent;
809
+ return parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null;
810
+ }
811
+ function isReceiverOfCall(node) {
812
+ const parent = node.parent;
813
+ return parent !== null && parent.type === "call" && field(parent, "receiver")?.id === node.id;
814
+ }
815
+ function isBareCall(node, enclosing) {
816
+ if (node.type !== "identifier") {
817
+ return false;
818
+ }
819
+ if (spellsAName(node) && !isReceiverOfCall(node)) {
820
+ return false;
821
+ }
822
+ return !isLocalName(node, node.text, enclosing);
823
+ }
674
824
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
675
825
  "string",
676
826
  "integer",
@@ -699,11 +849,7 @@ function add(emitter, relation, ...tuple) {
699
849
  emitter.db.add(relation, tuple);
700
850
  }
701
851
  function valueKey(emitter, written) {
702
- const value = readThrough(written);
703
- if (value.type !== "identifier" && value.type !== "constant") {
704
- return nodeId(emitter.filePath, value);
705
- }
706
- return nameKey(emitter.filePath, value, emitter.enclosing);
852
+ return readKey(emitter.filePath, written, emitter.enclosing);
707
853
  }
708
854
  function pairKeyText(key) {
709
855
  if (key.type === "hash_key_symbol") {
@@ -717,35 +863,29 @@ function pairKeyText(key) {
717
863
  }
718
864
  return null;
719
865
  }
720
- function isPropertyRead(node) {
721
- return node.type === "call" && field(node, "receiver") !== null && field(node, "arguments") === null && children(node).every(
722
- (child) => child.type !== "do_block" && child.type !== "block"
723
- );
724
- }
725
- function emitPropertyRead(emitter, node) {
726
- const receiver = field(node, "receiver");
727
- const method = field(node, "method");
728
- if (receiver === null || method === null) {
729
- return;
866
+ function calleeKeyOf(filePath, node, enclosing) {
867
+ if (isBareCall(node, enclosing)) {
868
+ return nameKey(filePath, node, enclosing);
730
869
  }
731
- add(
732
- emitter,
733
- "readsProperty",
734
- nodeId(emitter.filePath, node),
735
- valueKey(emitter, receiver),
736
- method.text
737
- );
870
+ const method = node.type === "call" ? field(node, "method") : null;
871
+ if (method === null) {
872
+ return null;
873
+ }
874
+ return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
738
875
  }
739
876
  function emitCall(emitter, call) {
740
877
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
741
- const method = field(call, "method");
742
- if (method === null) {
878
+ const method = call.type === "identifier" ? call : field(call, "method");
879
+ const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
880
+ if (method === null || calleeKey === null) {
743
881
  return;
744
882
  }
745
883
  const receiver = field(call, "receiver");
746
884
  const callKey = nodeId(emitter.filePath, call);
747
- const calleeKey = receiver === null ? valueKey(emitter, method) : nodeId(emitter.filePath, method);
748
885
  add(emitter, "call", callKey, calleeKey);
886
+ if (!emitter.insideMethod) {
887
+ add(emitter, "callOutsideMethod", callKey);
888
+ }
749
889
  if (receiver !== null) {
750
890
  add(
751
891
  emitter,
@@ -754,6 +894,8 @@ function emitCall(emitter, call) {
754
894
  valueKey(emitter, receiver),
755
895
  method.text
756
896
  );
897
+ } else if (emitter.selfKey !== null) {
898
+ add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
757
899
  }
758
900
  const args = field(call, "arguments");
759
901
  let position = 0;
@@ -878,9 +1020,7 @@ function emitExpressionFacts(emitter, node) {
878
1020
  emitDynamicDefinition(emitter, node, []);
879
1021
  walkExpressions(node, emitter, (child, turns) => {
880
1022
  emitDynamicDefinition(emitter, child, turns);
881
- if (isPropertyRead(child)) {
882
- emitPropertyRead(emitter, child);
883
- } else if (child.type === "call") {
1023
+ if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
884
1024
  emitCall(emitter, child);
885
1025
  }
886
1026
  if (ARRAY_TYPES.has(child.type)) {
@@ -936,28 +1076,52 @@ function collectInstanceWrite(emitter, node) {
936
1076
  const operator = field(node, "operator")?.text;
937
1077
  const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
938
1078
  const written = collector.get(left.text) ?? [];
939
- written.push(
940
- describeWrite(emitter, {
1079
+ written.push({
1080
+ write: describeWrite(emitter, {
941
1081
  name: left.text,
942
1082
  target: left,
943
1083
  value,
944
1084
  at: node,
945
1085
  fromParameter: false
946
- })
947
- );
1086
+ }),
1087
+ target: left
1088
+ });
948
1089
  collector.set(left.text, written);
949
1090
  }
1091
+ function settledWrites(writes, ordered) {
1092
+ const described = writes.map((written) => written.write);
1093
+ const settled = valueLeftByWrites(described, ordered);
1094
+ if (settled !== null) {
1095
+ return [settled];
1096
+ }
1097
+ const left = [];
1098
+ for (const write of described) {
1099
+ if (write.value !== null && !write.narrowsName) {
1100
+ left.push(write.value);
1101
+ }
1102
+ }
1103
+ return left;
1104
+ }
950
1105
  function emitInstanceWrites(emitter, classKey, collected) {
951
1106
  for (const [name, writes] of collected) {
952
- const settled = valueLeftByWrites(writes, false);
953
- if (settled !== null) {
954
- add(emitter, "holdsProperty", classKey, name, settled);
1107
+ for (const value of settledWrites(writes, false)) {
1108
+ add(emitter, "holdsProperty", classKey, name, value);
1109
+ }
1110
+ }
1111
+ }
1112
+ function emitInstanceStores(emitter, funcKey, body, collected) {
1113
+ for (const [name, writes] of collected) {
1114
+ const deciding = writes.filter((written) => !written.write.narrowsName);
1115
+ if (deciding.length === 0) {
955
1116
  continue;
956
1117
  }
957
- for (const write of writes) {
958
- if (write.value !== null && !write.narrowsName) {
959
- add(emitter, "holdsProperty", classKey, name, write.value);
960
- }
1118
+ const ordered = instanceWritesRunInOrder(
1119
+ body,
1120
+ name,
1121
+ deciding.map((written) => written.target)
1122
+ );
1123
+ for (const value of settledWrites(deciding, ordered)) {
1124
+ add(emitter, "storesProperty", funcKey, name, value);
961
1125
  }
962
1126
  }
963
1127
  }
@@ -1017,7 +1181,7 @@ function emitMethodFacts(emitter, method) {
1017
1181
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1018
1182
  }
1019
1183
  }
1020
- if (child.type === "call") {
1184
+ if (child.type === "call" || isBareCall(child, method)) {
1021
1185
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
1022
1186
  }
1023
1187
  });
@@ -1085,20 +1249,32 @@ function emitScopeWrites(emitter, method, body) {
1085
1249
  }
1086
1250
  }
1087
1251
  function emitCandidates(emitter, key, group) {
1252
+ let unstated = false;
1253
+ let stated = 0;
1088
1254
  for (const write of group.writes) {
1089
1255
  if (write.value === null) {
1090
1256
  add(emitter, "writesUnstated", key);
1257
+ unstated = true;
1091
1258
  continue;
1092
1259
  }
1093
1260
  if (write.fromParameter) {
1261
+ unstated = true;
1094
1262
  continue;
1095
1263
  }
1096
1264
  if (!describeWrite(emitter, write).narrowsName) {
1097
1265
  add(emitter, "mayHold", key, valueKey(emitter, write.value));
1266
+ stated += 1;
1098
1267
  }
1099
1268
  }
1269
+ if (!unstated && stated > 0) {
1270
+ add(emitter, "writesAllStated", key);
1271
+ }
1100
1272
  }
1101
1273
  var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1274
+ var INITIALIZE_METHOD = "initialize";
1275
+ function receiverKeyOf(filePath, method, classKey) {
1276
+ return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
1277
+ }
1102
1278
  var CONSTANT_REF_TYPES = /* @__PURE__ */ new Set(["constant", "scope_resolution"]);
1103
1279
  function mixedInConstants(body, callName) {
1104
1280
  return bareCallArgumentGroups(body, callName).flatMap((group) => [...group].reverse()).reverse().map(readThrough).filter((argument) => CONSTANT_REF_TYPES.has(argument.type));
@@ -1126,6 +1302,7 @@ function emitClassFacts(emitter, cls) {
1126
1302
  const within = {
1127
1303
  ...emitter,
1128
1304
  selfKey: classKey,
1305
+ insideMethod: false,
1129
1306
  instanceWrites: collected
1130
1307
  };
1131
1308
  const statements = body === null ? [] : bodyStatementsRun(body, cls.type === "module", emitter.bodyBlocks);
@@ -1153,10 +1330,30 @@ function emitClassFacts(emitter, cls) {
1153
1330
  emitExpressionFacts(within, statement);
1154
1331
  continue;
1155
1332
  }
1156
- const funcKey = emitMethodFacts(within, statement);
1333
+ const stored = /* @__PURE__ */ new Map();
1334
+ const receiverKey = receiverKeyOf(emitter.filePath, statement, classKey);
1335
+ if (receiverKey !== classKey) {
1336
+ add(emitter, "instanceOf", receiverKey, classKey);
1337
+ }
1338
+ const funcKey = emitMethodFacts(
1339
+ {
1340
+ ...within,
1341
+ selfKey: receiverKey,
1342
+ insideMethod: receiverKey !== classKey,
1343
+ instanceWrites: stored
1344
+ },
1345
+ statement
1346
+ );
1157
1347
  const name = field(statement, "name");
1158
1348
  if (name !== null) {
1159
1349
  add(emitter, "holdsProperty", classKey, name.text, funcKey);
1350
+ if (name.text === INITIALIZE_METHOD) {
1351
+ add(emitter, "initializes", classKey, funcKey);
1352
+ }
1353
+ }
1354
+ const methodBody = field(statement, "body");
1355
+ if (methodBody !== null) {
1356
+ emitInstanceStores(within, funcKey, methodBody, stored);
1160
1357
  }
1161
1358
  }
1162
1359
  emitInstanceWrites(within, classKey, collected);
@@ -1168,6 +1365,7 @@ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
1168
1365
  filePath,
1169
1366
  enclosing: null,
1170
1367
  selfKey: null,
1368
+ insideMethod: false,
1171
1369
  instanceWrites: null,
1172
1370
  bodyBlocks
1173
1371
  };
@@ -1248,7 +1446,7 @@ function rubyLowering(options) {
1248
1446
  statement: statementOf,
1249
1447
  siteOf,
1250
1448
  functionOf,
1251
- writtenTo: (node) => context === null ? null : context.writtenTo(node),
1449
+ writtenTo: (node, site) => context === null ? null : context.writtenTo(node, site),
1252
1450
  callable: (node) => {
1253
1451
  if (!isInlinableCall(node)) {
1254
1452
  return null;
@@ -1765,17 +1963,32 @@ function siteOf(node) {
1765
1963
  current = parent;
1766
1964
  }
1767
1965
  }
1966
+ var BARE_SPLAT_TYPES = /* @__PURE__ */ new Set(["splat_parameter", "hash_splat_parameter"]);
1768
1967
  function parametersOf2(parameters) {
1769
1968
  if (parameters === null) {
1770
1969
  return [];
1771
1970
  }
1772
- return named(parameters).flatMap((parameter) => {
1971
+ const declared = [];
1972
+ let position = 0;
1973
+ for (const parameter of named(parameters)) {
1974
+ if (BARE_SPLAT_TYPES.has(parameter.type) && field(parameter, "name") === null) {
1975
+ continue;
1976
+ }
1773
1977
  if (parameter.type === "identifier") {
1774
- return [{ name: parameter.text, default: null }];
1978
+ declared.push({ name: parameter.text, default: null, position });
1979
+ } else {
1980
+ const name = field(parameter, "name");
1981
+ if (name !== null) {
1982
+ declared.push({
1983
+ name: name.text,
1984
+ default: field(parameter, "value"),
1985
+ position
1986
+ });
1987
+ }
1775
1988
  }
1776
- const name = field(parameter, "name");
1777
- return name === null ? [] : [{ name: name.text, default: field(parameter, "value") }];
1778
- });
1989
+ position += 1;
1990
+ }
1991
+ return declared;
1779
1992
  }
1780
1993
  function functionOf(node) {
1781
1994
  if (node.type === "program") {
@@ -2183,25 +2396,56 @@ var rubyRows = [
2183
2396
 
2184
2397
  // src/values/evaluator.ts
2185
2398
  var evaluators = /* @__PURE__ */ new WeakMap();
2399
+ var contexts = /* @__PURE__ */ new WeakMap();
2186
2400
  var withoutFacts = /* @__PURE__ */ new WeakMap();
2401
+ var filesByTree = /* @__PURE__ */ new WeakMap();
2187
2402
  var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
2188
2403
  function bindEvaluator(db, nodes) {
2189
- evaluators.set(
2190
- db,
2191
- new Evaluator(
2192
- rubyLowering({ context: contextOver(db, nodes), rows: rubyRows })
2193
- )
2194
- );
2404
+ for (const entry of nodes.files) {
2405
+ filesByTree.set(entry.root.tree, entry.file);
2406
+ }
2407
+ const context = contextOver(db, nodes);
2408
+ contexts.set(db, context);
2409
+ evaluators.set(db, new Evaluator(rubyLowering({ context, rows: rubyRows })));
2195
2410
  }
2196
- function evaluatedValue(node, db, bindings) {
2411
+ function writtenNodeOf(node, db, site) {
2412
+ const context = db === void 0 ? void 0 : contexts.get(db);
2413
+ return context === void 0 ? null : context.writtenTo(node, site);
2414
+ }
2415
+ function evaluatedValue(node, db, bindings, site) {
2197
2416
  const evaluator = evaluatorFor(node, db);
2198
2417
  if (bindings === void 0) {
2199
- return force(evaluator.evaluate(node));
2418
+ return force(
2419
+ site === void 0 ? evaluator.evaluate(node) : evaluator.evaluate(node, { site })
2420
+ );
2200
2421
  }
2201
2422
  const supplied = new Map(
2202
2423
  [...bindings].map(([name, value]) => [name, text(value)])
2203
2424
  );
2204
- return force(evaluator.evaluate(node, { bindings: supplied }));
2425
+ return force(
2426
+ evaluator.evaluate(node, {
2427
+ bindings: supplied,
2428
+ ...site === void 0 ? {} : { site }
2429
+ })
2430
+ );
2431
+ }
2432
+ function constructionSitesOf(node, db) {
2433
+ const cls = enclosingClass(node);
2434
+ const file = cls === null ? void 0 : filesByTree.get(cls.tree);
2435
+ if (db === void 0 || cls === null || file === void 0) {
2436
+ return [];
2437
+ }
2438
+ return constructionSites(db, nodeId(file, cls));
2439
+ }
2440
+ function enclosingClass(node) {
2441
+ let current = node.parent;
2442
+ while (current !== null) {
2443
+ if (current.type === "class") {
2444
+ return current;
2445
+ }
2446
+ current = current.parent;
2447
+ }
2448
+ return null;
2205
2449
  }
2206
2450
  function stringValueOf(node, db, bindings) {
2207
2451
  return literalOf(evaluatedValue(node, db, bindings));
@@ -2253,15 +2497,13 @@ function contextOver(db, nodes) {
2253
2497
  }
2254
2498
  const fileOf = (node) => filesByRoot.get(node.tree.rootNode.id)?.file ?? null;
2255
2499
  return {
2256
- writtenTo: (node) => {
2500
+ writtenTo: (node, site) => {
2257
2501
  const file = fileOf(node);
2258
2502
  if (file === null) {
2259
2503
  return null;
2260
2504
  }
2261
- const answer = writtenValueOf(
2262
- db,
2263
- readKey(file, node, enclosingMethod(node))
2264
- );
2505
+ const key = readKey(file, node, enclosingMethod(node));
2506
+ const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
2265
2507
  return answer === null ? null : nodeOfKey(rootsByFile, answer);
2266
2508
  },
2267
2509
  callable: (call) => {
@@ -2279,6 +2521,31 @@ function contextOver(db, nodes) {
2279
2521
  };
2280
2522
  }
2281
2523
 
2524
+ // src/values/literals.ts
2525
+ import { force as force2, literalOf as literalOf2 } from "@suss/values";
2526
+ function literalElementsIn(value) {
2527
+ if (value.kind !== "sequence") {
2528
+ return null;
2529
+ }
2530
+ const elements = [];
2531
+ for (const item of value.items) {
2532
+ const literal = literalOf2(force2(item.value));
2533
+ if (literal === null) {
2534
+ return null;
2535
+ }
2536
+ elements.push(literal);
2537
+ }
2538
+ return elements;
2539
+ }
2540
+ function literalElementsOf(node, db) {
2541
+ return literalElementsIn(evaluatedValue(node, db));
2542
+ }
2543
+ function namesOf(node, db) {
2544
+ const value = evaluatedValue(node, db);
2545
+ const single = literalOf2(value);
2546
+ return single === null ? literalElementsIn(value) : [single];
2547
+ }
2548
+
2282
2549
  // src/defineMethod.ts
2283
2550
  var NOTHING = {
2284
2551
  names: /* @__PURE__ */ new Set(),
@@ -2326,7 +2593,7 @@ function readOne(db, rootsByFile, nameKey2) {
2326
2593
  const patterns = [];
2327
2594
  for (const bindings of loopTurns(db, rootsByFile, nameKey2)) {
2328
2595
  const value = node === null ? hole2(nameKey2) : evaluatedValue(node, db, bindings.size === 0 ? void 0 : bindings);
2329
- const name = literalOf2(value);
2596
+ const name = literalOf3(value);
2330
2597
  if (name !== null) {
2331
2598
  names.push(name);
2332
2599
  continue;
@@ -2382,29 +2649,14 @@ function listsBehind(db, rootsByFile, nameKey2) {
2382
2649
  0,
2383
2650
  nameKey2
2384
2651
  )) {
2385
- const elements = literalElementsOf(rootsByFile, String(overKey), db);
2652
+ const node = nodeOfKey2(rootsByFile, String(overKey));
2653
+ const elements = node === null ? null : literalElementsOf(node, db);
2386
2654
  if (elements !== null) {
2387
2655
  found.push({ element: String(element), index: String(index), elements });
2388
2656
  }
2389
2657
  }
2390
2658
  return found;
2391
2659
  }
2392
- function literalElementsOf(rootsByFile, key, db) {
2393
- const node = nodeOfKey2(rootsByFile, key);
2394
- const value = node === null ? null : evaluatedValue(node, db);
2395
- if (value === null || value.kind !== "sequence") {
2396
- return null;
2397
- }
2398
- const elements = [];
2399
- for (const item of value.items) {
2400
- const literal = literalOf2(force2(item.value));
2401
- if (literal === null) {
2402
- return null;
2403
- }
2404
- elements.push(literal);
2405
- }
2406
- return elements;
2407
- }
2408
2660
 
2409
2661
  // src/scope.ts
2410
2662
  function qualifyConstantRef(node, nesting) {
@@ -2736,108 +2988,12 @@ import {
2736
2988
  import { absentReading as absentReading2, unreadableReading as unreadableReading2 } from "@suss/extractor";
2737
2989
 
2738
2990
  // src/clientCalls.ts
2739
- import { namesNothing, restBinding } from "@suss/behavioral-ir";
2991
+ import { hasNameHole, namesNothing, restBinding } from "@suss/behavioral-ir";
2740
2992
  import { pathOf } from "@suss/values";
2741
2993
 
2742
2994
  // src/paths/effects.ts
2743
2995
  import { enumerateOrDegrade, sharedGatingConditions } from "@suss/extractor";
2744
-
2745
- // src/paths/bareCalls.ts
2746
- var PARAMETER_LIST_TYPES = [
2747
- "method_parameters",
2748
- "block_parameters",
2749
- "lambda_parameters",
2750
- "parameters"
2751
- ];
2752
- function isFieldOf(parent, node, name) {
2753
- return field(parent, name)?.id === node.id;
2754
- }
2755
- function namedAt(parent, node) {
2756
- return isFieldOf(parent, node, "name");
2757
- }
2758
- function always() {
2759
- return true;
2760
- }
2761
- var SPELLS_A_NAME = {
2762
- call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
2763
- method: namedAt,
2764
- singleton_method: namedAt,
2765
- assignment: (parent, node) => isFieldOf(parent, node, "left"),
2766
- operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
2767
- for: (parent, node) => isFieldOf(parent, node, "pattern"),
2768
- optional_parameter: namedAt,
2769
- splat_parameter: namedAt,
2770
- hash_splat_parameter: namedAt,
2771
- keyword_parameter: namedAt,
2772
- block_parameter: namedAt,
2773
- destructured_parameter: always,
2774
- left_assignment_list: always,
2775
- rest_assignment: always,
2776
- destructured_left_assignment: always,
2777
- exception_variable: always,
2778
- undef: always,
2779
- alias: always,
2780
- ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
2781
- };
2782
- function addNamesUnder(node, names) {
2783
- if (node === null) {
2784
- return;
2785
- }
2786
- if (node.type === "identifier") {
2787
- names.add(node.text);
2788
- return;
2789
- }
2790
- for (const child of bodyStatements(node)) {
2791
- addNamesUnder(child, names);
2792
- }
2793
- }
2794
- function parameterNames2(node, names) {
2795
- for (const parameter of bodyStatements(node)) {
2796
- if (parameter.type === "identifier") {
2797
- names.add(parameter.text);
2798
- continue;
2799
- }
2800
- const declared = field(parameter, "name");
2801
- if (declared !== null && declared.type === "identifier") {
2802
- names.add(declared.text);
2803
- continue;
2804
- }
2805
- addNamesUnder(parameter, names);
2806
- }
2807
- }
2808
- function assignedNames(node, names) {
2809
- addNamesUnder(field(node, "left"), names);
2810
- }
2811
- var BINDS_A_NAME = {
2812
- assignment: assignedNames,
2813
- operator_assignment: assignedNames,
2814
- for: (node, names) => addNamesUnder(field(node, "pattern"), names),
2815
- exception_variable: (node, names) => addNamesUnder(node, names),
2816
- ...Object.fromEntries(
2817
- PARAMETER_LIST_TYPES.map((type) => [type, parameterNames2])
2818
- )
2819
- };
2820
- function localNamesIn(definitionNode) {
2821
- const names = /* @__PURE__ */ new Set();
2822
- const visit = (node) => {
2823
- BINDS_A_NAME[node.type]?.(node, names);
2824
- for (const child of bodyStatements(node)) {
2825
- visit(child);
2826
- }
2827
- };
2828
- visit(definitionNode);
2829
- return names;
2830
- }
2831
- function isBareMethodCall(node, locals) {
2832
- if (node.type !== "identifier" || locals.has(node.text)) {
2833
- return false;
2834
- }
2835
- const parent = node.parent;
2836
- if (parent === null) {
2837
- return false;
2838
- }
2839
- return SPELLS_A_NAME[parent.type]?.(parent, node) !== true;
2840
- }
2996
+ import { constantOf, literalOf as literalOf4 } from "@suss/values";
2841
2997
 
2842
2998
  // src/paths/lowering.ts
2843
2999
  var RAISE_NAMES = /* @__PURE__ */ new Set(["raise", "fail"]);
@@ -3125,23 +3281,21 @@ var BLOCK_TYPES5 = /* @__PURE__ */ new Set([
3125
3281
  "do_block",
3126
3282
  "block"
3127
3283
  ]);
3128
- var LITERAL_ARGS = {
3129
- string: (node) => ({
3130
- kind: "string",
3131
- value: node.text.replace(/^["']|["']$/g, "")
3132
- }),
3133
- integer: (node) => {
3134
- const value = Number.parseInt(node.text, 10);
3135
- return Number.isNaN(value) ? null : { kind: "number", value };
3136
- },
3137
- float: (node) => {
3138
- const value = Number.parseFloat(node.text);
3139
- return Number.isNaN(value) ? null : { kind: "number", value };
3140
- },
3141
- true: () => ({ kind: "boolean", value: true }),
3142
- false: () => ({ kind: "boolean", value: false }),
3143
- simple_symbol: (node) => ({ kind: "string", value: node.text.slice(1) })
3144
- };
3284
+ function literalArgOf(node, facts) {
3285
+ const value = evaluatedValue(node, facts);
3286
+ const text2 = literalOf4(value);
3287
+ if (text2 !== null) {
3288
+ return { kind: "string", value: text2 };
3289
+ }
3290
+ const constant2 = constantOf(value);
3291
+ if (typeof constant2 === "number") {
3292
+ return { kind: "number", value: constant2 };
3293
+ }
3294
+ if (typeof constant2 === "boolean") {
3295
+ return { kind: "boolean", value: constant2 };
3296
+ }
3297
+ return null;
3298
+ }
3145
3299
  function children2(node) {
3146
3300
  return node.namedChildren.filter((child) => child !== null);
3147
3301
  }
@@ -3225,23 +3379,27 @@ function calleeText(call) {
3225
3379
  }
3226
3380
  return receiver === null ? method : `${receiver.text}.${method}`;
3227
3381
  }
3228
- function argOf(node) {
3229
- const literal = LITERAL_ARGS[node.type]?.(node);
3230
- if (literal !== null && literal !== void 0) {
3382
+ function argOf(node, facts) {
3383
+ const literal = literalArgOf(node, facts);
3384
+ if (literal !== null) {
3231
3385
  return literal;
3232
3386
  }
3233
3387
  if (node.type === "call" && !isArglessReceiverCall(node)) {
3234
- return { kind: "call", callee: calleeText(node), args: argsOf(node) };
3388
+ return {
3389
+ kind: "call",
3390
+ callee: calleeText(node),
3391
+ args: argsOf(node, facts)
3392
+ };
3235
3393
  }
3236
3394
  return { kind: "identifier", name: node.text };
3237
3395
  }
3238
- function argsOf(call) {
3396
+ function argsOf(call, facts) {
3239
3397
  const args = field(call, "arguments");
3240
3398
  if (args === null) {
3241
3399
  return [];
3242
3400
  }
3243
3401
  return children2(args).map(
3244
- (child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
3402
+ (child) => child.type === "pair" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3245
3403
  );
3246
3404
  }
3247
3405
  function enclosingStatement(call, body) {
@@ -3254,7 +3412,7 @@ function enclosingStatement(call, body) {
3254
3412
  }
3255
3413
  return current;
3256
3414
  }
3257
- function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS) {
3415
+ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
3258
3416
  const body = field(definitionNode, "body");
3259
3417
  if (body === null) {
3260
3418
  return [];
@@ -3294,7 +3452,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
3294
3452
  return {
3295
3453
  type: "invocation",
3296
3454
  callee: calleeText(call),
3297
- args: argsOf(call),
3455
+ args: argsOf(call, facts),
3298
3456
  async: false,
3299
3457
  ...conditions.length > 0 ? { preconditions: conditions } : {}
3300
3458
  };
@@ -3309,7 +3467,7 @@ import {
3309
3467
  unreadableReading,
3310
3468
  writtenReading
3311
3469
  } from "@suss/extractor";
3312
- import { constantOf, literalOf as literalOf4 } from "@suss/values";
3470
+ import { constantOf as constantOf2, literalOf as literalOf6 } from "@suss/values";
3313
3471
 
3314
3472
  // src/paths/predicates.ts
3315
3473
  var OPERATORS = {
@@ -3378,7 +3536,7 @@ function binaryOf(node) {
3378
3536
  return opaqueOf(node);
3379
3537
  }
3380
3538
  function valueRefOf(node) {
3381
- const literal = literalOf3(node);
3539
+ const literal = literalOf5(node);
3382
3540
  if (literal !== null) {
3383
3541
  return literal;
3384
3542
  }
@@ -3391,7 +3549,7 @@ function valueRefOf(node) {
3391
3549
  }
3392
3550
  return { type: "unresolved", sourceText: node.text };
3393
3551
  }
3394
- function literalOf3(node) {
3552
+ function literalOf5(node) {
3395
3553
  if (node.type === "integer") {
3396
3554
  const value = Number.parseInt(node.text, 10);
3397
3555
  return Number.isNaN(value) ? null : { type: "literal", value };
@@ -3434,13 +3592,13 @@ function statusArgumentOf(call, declaration) {
3434
3592
  }
3435
3593
  return args.positional[declaration.statusArgument] ?? null;
3436
3594
  }
3437
- function statusNumberOf(node, names) {
3438
- const value = evaluatedValue(node);
3439
- const constant2 = constantOf(value);
3595
+ function statusNumberOf(node, names, facts) {
3596
+ const value = evaluatedValue(node, facts);
3597
+ const constant2 = constantOf2(value);
3440
3598
  if (typeof constant2 === "number") {
3441
3599
  return constant2;
3442
3600
  }
3443
- const name = literalOf4(value);
3601
+ const name = literalOf6(value);
3444
3602
  return name === null ? null : names[name] ?? null;
3445
3603
  }
3446
3604
  function declarationsByName(declarations) {
@@ -3484,12 +3642,12 @@ function collectReturns(node, found) {
3484
3642
  }
3485
3643
  return found;
3486
3644
  }
3487
- function readingOfCall(call, declaration, names) {
3645
+ function readingOfCall(call, declaration, names, facts) {
3488
3646
  const argument = statusArgumentOf(call, declaration);
3489
3647
  if (argument === null) {
3490
3648
  return declaration.defaultStatusCode === void 0 ? absentReading : writtenReading(declaration.defaultStatusCode, rangeOf(call));
3491
3649
  }
3492
- const status = statusNumberOf(argument, names);
3650
+ const status = statusNumberOf(argument, names, facts);
3493
3651
  if (status === null) {
3494
3652
  return unreadableReading(
3495
3653
  "This response writes a status that does not settle on a number here, so this outcome claims none",
@@ -3633,7 +3791,7 @@ function responseBranches(method, pattern, effects, extraEffects, options = {})
3633
3791
  const outcomes = [];
3634
3792
  for (const terminal of terminals) {
3635
3793
  const declaration = declarationOf(terminal, byName, locals);
3636
- const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames);
3794
+ const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames, options.facts);
3637
3795
  for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
3638
3796
  outcomes.push({
3639
3797
  conditions: path7.map(conditionOf),
@@ -3667,11 +3825,9 @@ function clientCallUnits(root, pack, pattern, options) {
3667
3825
  continue;
3668
3826
  }
3669
3827
  for (const call of callsUnder(method)) {
3670
- const request = requestCall(call, method, root, pattern, options);
3671
- if (request === null) {
3672
- continue;
3828
+ for (const request of requestCalls(call, pattern, options)) {
3829
+ units.push(clientUnit(method, name, request, pattern, pack, options));
3673
3830
  }
3674
- units.push(clientUnit(method, name, request, pattern, pack, options));
3675
3831
  }
3676
3832
  }
3677
3833
  return units;
@@ -3700,36 +3856,60 @@ function callsUnder(node, found = []) {
3700
3856
  }
3701
3857
  return found;
3702
3858
  }
3703
- function requestCall(call, method, root, pattern, options) {
3859
+ function requestCalls(call, pattern, options) {
3704
3860
  const receiver = field(call, "receiver");
3705
3861
  const called = field(call, "method")?.text;
3706
- if (receiver === null || called === void 0) {
3707
- return null;
3862
+ if (receiver === null || called === void 0 || !isLibraryReceiver(receiver, pattern, options)) {
3863
+ return [];
3708
3864
  }
3709
- const prefix = receiverPrefix(receiver, method, root, pattern, options);
3710
- if (prefix === null) {
3711
- return null;
3865
+ const read = (site) => requestCall(
3866
+ call,
3867
+ called,
3868
+ receiverPrefix(receiver, pattern, options, site),
3869
+ pattern,
3870
+ options,
3871
+ site
3872
+ );
3873
+ const plain = read();
3874
+ if (plain !== null && !hasNameHole(plain.path)) {
3875
+ return [plain];
3876
+ }
3877
+ const byBoundary = /* @__PURE__ */ new Map();
3878
+ for (const site of constructionSitesOf(call, options.facts)) {
3879
+ const stated = read(site);
3880
+ if (stated !== null) {
3881
+ byBoundary.set(`${stated.method} ${stated.path}`, stated);
3882
+ }
3712
3883
  }
3884
+ if (byBoundary.size > 0) {
3885
+ return [...byBoundary.values()];
3886
+ }
3887
+ return plain === null ? [] : [plain];
3888
+ }
3889
+ function requestCall(call, called, prefix, pattern, options, site) {
3713
3890
  const args = readCallArgs(field(call, "arguments"));
3714
3891
  const verb = pattern.verbMethodNames[called];
3715
3892
  if (verb !== void 0) {
3716
- const path8 = urlIn(args, pattern, options);
3893
+ const path8 = urlIn(args, pattern, options, site);
3717
3894
  return path8 === null ? null : { method: verb, path: prefix + path8 };
3718
3895
  }
3719
3896
  const sent = pattern.requestObject;
3720
3897
  if (sent === void 0 || sent.attribute !== called) {
3721
3898
  return null;
3722
3899
  }
3723
- const built = requestBuilt(args.positional[0], method, sent);
3900
+ const built = requestBuilt(args.positional[0], sent, options, site);
3724
3901
  if (built === null) {
3725
3902
  return null;
3726
3903
  }
3727
- const path7 = pathAt(built.url, options);
3904
+ const path7 = pathAt(built.url, options, site);
3728
3905
  return path7 === null ? null : { method: built.method, path: prefix + path7 };
3729
3906
  }
3730
- function requestBuilt(argument, method, sent) {
3731
- const written = argument === void 0 ? null : assignedCall(argument, method) ?? argument;
3732
- if (written === null || written.type !== "call") {
3907
+ function requestBuilt(argument, sent, options, site) {
3908
+ if (argument === void 0) {
3909
+ return null;
3910
+ }
3911
+ const written = writtenNodeOf(argument, options.facts, site) ?? argument;
3912
+ if (written.type !== "call") {
3733
3913
  return null;
3734
3914
  }
3735
3915
  const requestClass = field(written, "receiver")?.text;
@@ -3740,28 +3920,18 @@ function requestBuilt(argument, method, sent) {
3740
3920
  const args = readCallArgs(field(written, "arguments"));
3741
3921
  return { method: verb, url: args.positional[sent.urlPosition] };
3742
3922
  }
3743
- function assignedCall(node, method) {
3744
- if (node.type !== "identifier") {
3745
- return null;
3746
- }
3747
- const value = assignedValue(node.text, method);
3748
- return value !== null && value.type === "call" ? value : null;
3923
+ function isLibraryReceiver(receiver, pattern, options) {
3924
+ return namesConstant(receiver, pattern.constantName) || builderCallBehind(receiver, pattern, options.facts) !== null;
3749
3925
  }
3750
- function receiverPrefix(receiver, method, root, pattern, options) {
3751
- if (namesConstant(receiver, pattern.constantName)) {
3752
- return "";
3753
- }
3754
- if (receiver.type !== "identifier") {
3755
- return null;
3756
- }
3757
- const built = builtBy(receiver.text, method, root, pattern);
3926
+ function receiverPrefix(receiver, pattern, options, site) {
3927
+ const built = builderCallBehind(receiver, pattern, options.facts);
3758
3928
  if (built === null) {
3759
- return null;
3929
+ return "";
3760
3930
  }
3761
3931
  const args = readCallArgs(field(built, "arguments"));
3762
3932
  const keyword = pattern.builderUrlKeyword;
3763
3933
  const base = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[0];
3764
- const path7 = base === void 0 ? null : pathOf(evaluatedValue(base, options.facts));
3934
+ const path7 = base === void 0 ? null : pathOf(evaluatedValue(base, options.facts, void 0, site));
3765
3935
  return path7 === void 0 || path7 === null ? "" : trimmed(path7);
3766
3936
  }
3767
3937
  function trimmed(path7) {
@@ -3773,61 +3943,28 @@ function namesConstant(receiver, constantName2) {
3773
3943
  }
3774
3944
  return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
3775
3945
  }
3776
- function builtBy(name, method, root, pattern) {
3946
+ function builderCallBehind(receiver, pattern, facts) {
3777
3947
  const builders = pattern.receiverBuilders ?? [];
3778
3948
  if (builders.length === 0) {
3779
3949
  return null;
3780
3950
  }
3781
- const value = assignedValue(name, method) ?? valueMethodNamedReturns(name, root);
3782
- if (value === null || value.type !== "call" || !builders.includes(field(value, "method")?.text ?? "")) {
3783
- return null;
3784
- }
3785
- const receiver = field(value, "receiver");
3786
- return receiver !== null && namesConstant(receiver, pattern.constantName) ? value : null;
3787
- }
3788
- function valueMethodNamedReturns(name, root) {
3789
- const defined = methodDefinitions(root).find(
3790
- (candidate) => field(candidate, "name")?.text === name
3791
- );
3792
- if (defined === void 0) {
3793
- return null;
3794
- }
3795
- const body = field(defined, "body");
3796
- const statements = body === null ? [] : body.namedChildren;
3797
- const last = statements[statements.length - 1];
3798
- if (last === void 0) {
3799
- return null;
3800
- }
3801
- if (last.type === "operator_assignment" || last.type === "assignment") {
3802
- return field(last, "right");
3803
- }
3804
- return last.type === "return" ? last.namedChildren[0] ?? null : last;
3805
- }
3806
- function assignedValue(name, method) {
3807
- const body = field(method, "body");
3808
- if (body === null) {
3951
+ const written = writtenNodeOf(receiver, facts);
3952
+ if (written === null || written.type !== "call" || !builders.includes(field(written, "method")?.text ?? "")) {
3809
3953
  return null;
3810
3954
  }
3811
- for (const statement of runStatements(body)) {
3812
- if (statement.type !== "assignment") {
3813
- continue;
3814
- }
3815
- if (field(statement, "left")?.text === name) {
3816
- return field(statement, "right");
3817
- }
3818
- }
3819
- return null;
3955
+ const constant2 = field(written, "receiver");
3956
+ return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
3820
3957
  }
3821
- function urlIn(args, pattern, options) {
3958
+ function urlIn(args, pattern, options, site) {
3822
3959
  const keyword = pattern.url.keyword;
3823
3960
  const written = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
3824
- return pathAt(written, options);
3961
+ return pathAt(written, options, site);
3825
3962
  }
3826
- function pathAt(written, options) {
3963
+ function pathAt(written, options, site) {
3827
3964
  if (written === void 0) {
3828
3965
  return null;
3829
3966
  }
3830
- const path7 = pathOf(evaluatedValue(written, options.facts));
3967
+ const path7 = pathOf(evaluatedValue(written, options.facts, void 0, site));
3831
3968
  return path7 === void 0 || namesNothing(path7) ? null : path7;
3832
3969
  }
3833
3970
  function clientUnit(method, name, request, pattern, pack, options) {
@@ -3850,15 +3987,15 @@ function clientUnit(method, name, request, pattern, pack, options) {
3850
3987
  recognition: pack.name
3851
3988
  }),
3852
3989
  parameters: [],
3853
- branches: callerBranches(method, range),
3990
+ branches: callerBranches(method, range, options.facts),
3854
3991
  ...responseAccessors(pattern),
3855
3992
  bodyContent: "statements",
3856
3993
  dependencyCalls: [],
3857
3994
  declaredContract: null
3858
3995
  };
3859
3996
  }
3860
- function callerBranches(method, range) {
3861
- const effects = invocationEffects(method);
3997
+ function callerBranches(method, range, facts) {
3998
+ const effects = invocationEffects(method, void 0, void 0, facts);
3862
3999
  return returnPathBranches(method, effects) ?? [returnBranch(range, effects)];
3863
4000
  }
3864
4001
  function responseAccessors(pattern) {
@@ -4016,7 +4153,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4016
4153
  }
4017
4154
  const form = byName.get(called);
4018
4155
  if (form !== void 0) {
4019
- for (const declaration of declarationsOf(statement, form)) {
4156
+ for (const declaration of declarationsOf(statement, form, read.facts)) {
4020
4157
  declared = [
4021
4158
  ...declared.filter((earlier) => !sameFilter(earlier, declaration)),
4022
4159
  declaration
@@ -4026,7 +4163,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4026
4163
  }
4027
4164
  const skipped = skipNames.get(called);
4028
4165
  if (skipped !== void 0) {
4029
- declared = applySkips(declared, skipsOf(statement, skipped));
4166
+ declared = applySkips(declared, skipsOf(statement, skipped, read.facts));
4030
4167
  }
4031
4168
  }
4032
4169
  const resolved = [];
@@ -4063,14 +4200,14 @@ function filterReference(filter, displayPath) {
4063
4200
  ...filter.filter.onThrow === true ? { onThrow: true } : {}
4064
4201
  };
4065
4202
  }
4066
- function filterUnit(filter, pattern, displayPath, body) {
4203
+ function filterUnit(filter, pattern, displayPath, body, facts) {
4067
4204
  const range = rangeOf(filter.method);
4068
4205
  const branches = responseBranches(
4069
4206
  filter.method,
4070
4207
  pattern,
4071
4208
  body.effects ?? [],
4072
4209
  body.extraEffects,
4073
- { fallthrough: "handOn" }
4210
+ { fallthrough: "handOn", facts }
4074
4211
  );
4075
4212
  return {
4076
4213
  identity: {
@@ -4118,30 +4255,30 @@ function calledName(statement) {
4118
4255
  }
4119
4256
  return field(statement, "method")?.text ?? null;
4120
4257
  }
4121
- function declarationsOf(statement, filter) {
4258
+ function declarationsOf(statement, filter, facts) {
4122
4259
  const args = readCallArgs(field(statement, "arguments"));
4123
4260
  const keywords = filter.actionKeywords;
4124
- const only = keywords === void 0 ? null : namesUnder(args.keyword[keywords.include]);
4125
- const except = keywords === void 0 ? null : namesUnder(args.keyword[keywords.exclude]);
4126
- return methodNamesOf(args, filter).map((methodName) => ({
4261
+ const only = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.include], facts);
4262
+ const except = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.exclude], facts);
4263
+ return methodNamesOf(args, filter, facts).map((methodName) => ({
4127
4264
  filter,
4128
4265
  methodName,
4129
4266
  only,
4130
4267
  except: except ?? /* @__PURE__ */ new Set()
4131
4268
  }));
4132
4269
  }
4133
- function methodNamesOf(args, filter) {
4270
+ function methodNamesOf(args, filter, facts) {
4134
4271
  if (filter.methodFrom === "withKeyword") {
4135
4272
  const named2 = args.keyword.with;
4136
- const value = named2 === void 0 ? null : symbolValue(named2);
4273
+ const value = named2 === void 0 ? null : stringValueOf(named2, facts);
4137
4274
  return value === null ? [] : [value];
4138
4275
  }
4139
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null);
4276
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
4140
4277
  }
4141
- function skipsOf(statement, filterName) {
4278
+ function skipsOf(statement, filterName, facts) {
4142
4279
  const args = readCallArgs(field(statement, "arguments"));
4143
- const actions = namesUnder(args.keyword.only);
4144
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
4280
+ const actions = actionsUnder(args.keyword.only, facts);
4281
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
4145
4282
  }
4146
4283
  function sameFilter(one, other) {
4147
4284
  return one.filter.name === other.filter.name && one.methodName === other.methodName;
@@ -4165,26 +4302,12 @@ function applySkips(declared, skips) {
4165
4302
  }
4166
4303
  return left;
4167
4304
  }
4168
- function namesUnder(node) {
4305
+ function actionsUnder(node, facts) {
4169
4306
  if (node === void 0) {
4170
4307
  return null;
4171
4308
  }
4172
- const single = symbolValue(node);
4173
- if (single !== null) {
4174
- return /* @__PURE__ */ new Set([single]);
4175
- }
4176
- if (node.type !== "array" && node.type !== "symbol_array") {
4177
- return null;
4178
- }
4179
- const names = /* @__PURE__ */ new Set();
4180
- for (const child of bodyStatements(node)) {
4181
- const bare = child.type === "bare_symbol" ? child.text : null;
4182
- const name = symbolValue(child) ?? bare;
4183
- if (name !== null) {
4184
- names.add(name);
4185
- }
4186
- }
4187
- return names;
4309
+ const found = namesOf(node, facts);
4310
+ return found === null ? null : new Set(found);
4188
4311
  }
4189
4312
 
4190
4313
  // src/storage.ts
@@ -4262,67 +4385,64 @@ function argumentsOf2(call) {
4262
4385
  const args = field(call, "arguments");
4263
4386
  return args === null ? [] : children3(args);
4264
4387
  }
4265
- function isKeywordArgument(argument) {
4266
- return argument.type === "pair" || argument.type === "hash";
4267
- }
4268
- function bareName(node) {
4269
- return node.text.replace(/^:/, "").replace(/:$/, "");
4388
+ function recordFieldsOf(argument, facts) {
4389
+ const value = evaluatedValue(argument, facts);
4390
+ return value.kind === "record" ? [...value.fields.keys()] : [];
4270
4391
  }
4271
- function pairsIn(argument) {
4272
- if (argument.type === "pair") {
4273
- return [argument];
4274
- }
4275
- if (argument.type === "hash") {
4276
- return children3(argument).filter((child) => child.type === "pair");
4277
- }
4278
- return [];
4279
- }
4280
- function keywordKeys(call) {
4392
+ function keywordKeys(call, facts) {
4281
4393
  const keys = [];
4282
4394
  for (const argument of argumentsOf2(call)) {
4283
- for (const pair of pairsIn(argument)) {
4284
- const key = field(pair, "key");
4285
- if (key !== null) {
4286
- keys.push(bareName(key));
4287
- }
4395
+ if (argument.type !== "pair") {
4396
+ keys.push(...recordFieldsOf(argument, facts));
4397
+ continue;
4398
+ }
4399
+ const key = field(argument, "key");
4400
+ const name = key === null ? null : keyNameOf(key);
4401
+ if (name !== null) {
4402
+ keys.push(name);
4288
4403
  }
4289
4404
  }
4290
4405
  return keys;
4291
4406
  }
4292
- function hasPositionalArgument(call) {
4293
- return argumentsOf2(call).some((argument) => !isKeywordArgument(argument));
4407
+ function keyNameOf(key) {
4408
+ return hashKeySymbolName(key) ?? symbolValue(key) ?? stringLiteralValue(key);
4294
4409
  }
4295
- function columnsAskedFor(call, pattern) {
4410
+ function hasPositionalArgument(call, facts) {
4411
+ return argumentsOf2(call).some(
4412
+ (argument) => argument.type !== "pair" && recordFieldsOf(argument, facts).length === 0
4413
+ );
4414
+ }
4415
+ function columnsAskedFor(call, pattern, facts) {
4296
4416
  if (!(pattern.columnArguments ?? []).includes(methodOf(call))) {
4297
4417
  return [];
4298
4418
  }
4299
- return argumentsOf2(call).filter((argument) => argument.type === "simple_symbol").map(bareName);
4419
+ return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
4300
4420
  }
4301
- function primaryKeySelector(call, pattern) {
4421
+ function primaryKeySelector(call, pattern, facts) {
4302
4422
  const byPrimaryKey = pattern.byPrimaryKey;
4303
4423
  if (byPrimaryKey === void 0) {
4304
4424
  return [];
4305
4425
  }
4306
- const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call);
4426
+ const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call, facts);
4307
4427
  return picks ? [byPrimaryKey.column] : [];
4308
4428
  }
4309
- function selectorOf(call, pattern) {
4429
+ function selectorOf(call, pattern, facts) {
4310
4430
  const picked = [];
4311
4431
  for (const link of chainLinks(call)) {
4312
- picked.push(...primaryKeySelector(link, pattern));
4432
+ picked.push(...primaryKeySelector(link, pattern, facts));
4313
4433
  if (pattern.reads.includes(methodOf(link))) {
4314
- picked.push(...keywordKeys(link));
4434
+ picked.push(...keywordKeys(link, facts));
4315
4435
  }
4316
4436
  }
4317
4437
  return [...new Set(picked)];
4318
4438
  }
4319
- function fieldsOf(call, pattern, kind) {
4439
+ function fieldsOf(call, pattern, kind, facts) {
4320
4440
  if (kind === "write") {
4321
- return [...new Set(keywordKeys(call))];
4441
+ return [...new Set(keywordKeys(call, facts))];
4322
4442
  }
4323
4443
  return [
4324
4444
  ...new Set(
4325
- chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern))
4445
+ chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern, facts))
4326
4446
  )
4327
4447
  ];
4328
4448
  }
@@ -4414,8 +4534,8 @@ function modelCallEffects(call, file, options, enclosing) {
4414
4534
  target.container,
4415
4535
  pattern,
4416
4536
  kind,
4417
- selectorOf(worked, pattern),
4418
- fieldsOf(worked, pattern, kind)
4537
+ selectorOf(worked, pattern, options.facts),
4538
+ fieldsOf(worked, pattern, kind, options.facts)
4419
4539
  )
4420
4540
  ];
4421
4541
  }
@@ -4735,7 +4855,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4735
4855
  filter,
4736
4856
  pattern,
4737
4857
  displayPath,
4738
- bodyOfMethod(filter.method, filter.file, options)
4858
+ bodyOfMethod(filter.method, filter.file, options),
4859
+ options.facts
4739
4860
  );
4740
4861
  units.push(raw);
4741
4862
  options.onReachSeed?.(raw, {
@@ -4826,7 +4947,8 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
4826
4947
  method,
4827
4948
  pattern,
4828
4949
  body.effects ?? [],
4829
- body.extraEffects
4950
+ body.extraEffects,
4951
+ { facts: bodyRead.facts }
4830
4952
  );
4831
4953
  return {
4832
4954
  identity: {
@@ -4956,7 +5078,8 @@ function bodyOfMethod(method, file, bodyRead = {}) {
4956
5078
  const effects = invocationEffects(
4957
5079
  method,
4958
5080
  bodyRead.inheritedMethods,
4959
- EVERY_ARGLESS_CALL
5081
+ EVERY_ARGLESS_CALL,
5082
+ bodyRead.facts
4960
5083
  );
4961
5084
  const storage = bodyRead.storage;
4962
5085
  const extra = [
@@ -5694,6 +5817,7 @@ function parseWith(language, source) {
5694
5817
  import fs3 from "fs";
5695
5818
  import path4 from "path";
5696
5819
  import {
5820
+ boundaryKey,
5697
5821
  disambiguateSummaryIds,
5698
5822
  linkCallsToSummaries,
5699
5823
  placeArgTargets as placeArgTargets2,
@@ -5714,6 +5838,7 @@ import {
5714
5838
  runDigest,
5715
5839
  stampModuleImports
5716
5840
  } from "@suss/extractor";
5841
+ import { addPackWords } from "@suss/resolution";
5717
5842
 
5718
5843
  // src/diagnostics.ts
5719
5844
  import {
@@ -6494,20 +6619,41 @@ function associationCallsIn(packs) {
6494
6619
  )
6495
6620
  );
6496
6621
  }
6497
- function emitStorageFacts(db, packs) {
6498
- for (const pack of packs) {
6499
- for (const pattern of pack.storage ?? []) {
6500
- for (const base of pattern.baseClasses) {
6501
- for (const method of pattern.givesBack) {
6502
- db.add("givesBackOne", [base, method]);
6503
- }
6504
- }
6505
- }
6506
- }
6622
+ function packWordsOf(packs) {
6623
+ return {
6624
+ givesBackOne: packs.flatMap(
6625
+ (pack) => (pack.storage ?? []).flatMap(
6626
+ (pattern) => pattern.baseClasses.flatMap(
6627
+ (base) => pattern.givesBack.map((method) => ({ base, method }))
6628
+ )
6629
+ )
6630
+ )
6631
+ };
6632
+ }
6633
+ function factsForFile(options) {
6634
+ const db = new Database2();
6635
+ const bodyBlocks = bodyBlocksIn(options.packs);
6636
+ emitValueFacts(db, options.file, options.root, bodyBlocks);
6637
+ emitConstantBindings(db, [
6638
+ collectFileConstants(
6639
+ options.file,
6640
+ options.root,
6641
+ associationCallsIn(options.packs),
6642
+ inflectionsIn(options.packs)
6643
+ )
6644
+ ]);
6645
+ emitRequireFacts(db, options.file, options.root, /* @__PURE__ */ new Set([options.file]));
6646
+ bindEvaluator(db, {
6647
+ files: [{ file: options.file, root: options.root }],
6648
+ definitions: new Map(methodDefinitionsIn(options.file, options.root))
6649
+ });
6650
+ addPackWords(db, packWordsOf(options.packs));
6651
+ return db;
6507
6652
  }
6508
6653
  function alreadyDiscovered(seen, raw) {
6509
6654
  const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
6510
- const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
6655
+ const boundary = raw.boundaryBinding === null ? "" : boundaryKey(raw.boundaryBinding) ?? "";
6656
+ const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}::${boundary}`;
6511
6657
  if (seen.has(key)) {
6512
6658
  return true;
6513
6659
  }
@@ -6574,7 +6720,7 @@ async function extractRubyProject(options) {
6574
6720
  emitRequireFacts(db, file, root, known);
6575
6721
  }
6576
6722
  bindEvaluator(db, { files: parsed, definitions });
6577
- emitStorageFacts(db, options.packs);
6723
+ addPackWords(db, packWordsOf(options.packs));
6578
6724
  return readDynamicNames(
6579
6725
  db,
6580
6726
  new Map(parsed.map(({ file, root }) => [file, root]))
@@ -6847,6 +6993,10 @@ function displayNameOf(node) {
6847
6993
  function width(node) {
6848
6994
  return node.endIndex - node.startIndex;
6849
6995
  }
6996
+ function askedKey(value) {
6997
+ const enclosing = enclosingMethod2(value.node);
6998
+ return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
6999
+ }
6850
7000
  var RubyWhySession = class {
6851
7001
  root;
6852
7002
  db = new Database3();
@@ -6922,7 +7072,7 @@ var RubyWhySession = class {
6922
7072
  return explainResolvedKey({
6923
7073
  db: this.db,
6924
7074
  rules: WITNESS_RULES,
6925
- key: readKey(value.file, value.node, enclosingMethod2(value.node)),
7075
+ key: askedKey(value),
6926
7076
  locate: (key) => this.locate(key),
6927
7077
  displayPath: (key) => this.displayPath(key),
6928
7078
  ...options
@@ -7039,6 +7189,7 @@ export {
7039
7189
  emitValueFacts,
7040
7190
  evaluatedValue,
7041
7191
  extractRubyProject,
7192
+ factsForFile,
7042
7193
  field,
7043
7194
  findRubyFiles,
7044
7195
  graphqlTypeNameFromQualified,