@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.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,118 @@ 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
+ askResolutionUnder(db, [[key, site]], resolutionUnderProgram(RUBY_RULES));
402
+ return sharedWrittenValueUnder(db, key, site);
403
+ }
404
+ function constructionSites(db, classKey) {
405
+ askResolution(db, [classKey], "wantedSites", RUBY_PROGRAM);
406
+ return allocationSitesOf(db, classKey);
407
+ }
407
408
 
408
409
  // src/facts/values.ts
409
410
  import { startsAtName, valueLeftByWrites } from "@suss/resolution";
410
411
 
412
+ // src/paths/bareCalls.ts
413
+ var PARAMETER_LIST_TYPES = [
414
+ "method_parameters",
415
+ "block_parameters",
416
+ "lambda_parameters",
417
+ "parameters"
418
+ ];
419
+ function isFieldOf(parent, node, name) {
420
+ return field(parent, name)?.id === node.id;
421
+ }
422
+ function namedAt(parent, node) {
423
+ return isFieldOf(parent, node, "name");
424
+ }
425
+ function always() {
426
+ return true;
427
+ }
428
+ var SPELLS_A_NAME = {
429
+ call: (parent, node) => isFieldOf(parent, node, "method") || isFieldOf(parent, node, "receiver"),
430
+ method: namedAt,
431
+ singleton_method: namedAt,
432
+ assignment: (parent, node) => isFieldOf(parent, node, "left"),
433
+ operator_assignment: (parent, node) => isFieldOf(parent, node, "left"),
434
+ for: (parent, node) => isFieldOf(parent, node, "pattern"),
435
+ optional_parameter: namedAt,
436
+ splat_parameter: namedAt,
437
+ hash_splat_parameter: namedAt,
438
+ keyword_parameter: namedAt,
439
+ block_parameter: namedAt,
440
+ destructured_parameter: always,
441
+ left_assignment_list: always,
442
+ rest_assignment: always,
443
+ destructured_left_assignment: always,
444
+ exception_variable: always,
445
+ undef: always,
446
+ alias: always,
447
+ ...Object.fromEntries(PARAMETER_LIST_TYPES.map((type) => [type, always]))
448
+ };
449
+ function addNamesUnder(node, names) {
450
+ if (node === null) {
451
+ return;
452
+ }
453
+ if (node.type === "identifier") {
454
+ names.add(node.text);
455
+ return;
456
+ }
457
+ for (const child of bodyStatements(node)) {
458
+ addNamesUnder(child, names);
459
+ }
460
+ }
461
+ function parameterNames(node, names) {
462
+ for (const parameter of bodyStatements(node)) {
463
+ if (parameter.type === "identifier") {
464
+ names.add(parameter.text);
465
+ continue;
466
+ }
467
+ const declared = field(parameter, "name");
468
+ if (declared !== null && declared.type === "identifier") {
469
+ names.add(declared.text);
470
+ continue;
471
+ }
472
+ addNamesUnder(parameter, names);
473
+ }
474
+ }
475
+ function assignedNames(node, names) {
476
+ addNamesUnder(field(node, "left"), names);
477
+ }
478
+ var BINDS_A_NAME = {
479
+ assignment: assignedNames,
480
+ operator_assignment: assignedNames,
481
+ for: (node, names) => addNamesUnder(field(node, "pattern"), names),
482
+ exception_variable: (node, names) => addNamesUnder(node, names),
483
+ ...Object.fromEntries(
484
+ PARAMETER_LIST_TYPES.map((type) => [type, parameterNames])
485
+ )
486
+ };
487
+ function localNamesIn(definitionNode) {
488
+ const names = /* @__PURE__ */ new Set();
489
+ const visit = (node) => {
490
+ BINDS_A_NAME[node.type]?.(node, names);
491
+ for (const child of bodyStatements(node)) {
492
+ visit(child);
493
+ }
494
+ };
495
+ visit(definitionNode);
496
+ return names;
497
+ }
498
+ function spellsAName(node) {
499
+ const parent = node.parent;
500
+ if (parent === null) {
501
+ return true;
502
+ }
503
+ return SPELLS_A_NAME[parent.type]?.(parent, node) === true;
504
+ }
505
+ function isBareMethodCall(node, locals) {
506
+ if (node.type !== "identifier" || locals.has(node.text)) {
507
+ return false;
508
+ }
509
+ return !spellsAName(node);
510
+ }
511
+
411
512
  // src/facts/locals.ts
412
513
  import { writesRunInOrder } from "@suss/resolution";
413
514
  var BLOCK_TYPES2 = /* @__PURE__ */ new Set(["block", "do_block"]);
@@ -432,7 +533,7 @@ function parametersOf(owner) {
432
533
  const params = field(owner, "parameters");
433
534
  return params === null ? [] : bodyStatements(params);
434
535
  }
435
- function parameterNames(owner) {
536
+ function parameterNames2(owner) {
436
537
  const names = /* @__PURE__ */ new Set();
437
538
  for (const param of parametersOf(owner)) {
438
539
  const name = paramNameOf(param);
@@ -480,6 +581,13 @@ function readForVariable(node) {
480
581
  }
481
582
  return targetsOf(pattern).map((target) => writeOf(target, null, node, false));
482
583
  }
584
+ function readRescueVariable(node) {
585
+ const variable = field(node, "variable");
586
+ if (variable === null) {
587
+ return [];
588
+ }
589
+ return bodyStatements(variable).flatMap(targetsOf).map((target) => writeOf(target, null, node, false));
590
+ }
483
591
  function readBlockParameters(node) {
484
592
  const found = [];
485
593
  for (const param of parametersOf(node)) {
@@ -494,6 +602,7 @@ var WRITE_READERS = {
494
602
  assignment: readAssignment,
495
603
  operator_assignment: readOperatorAssignment,
496
604
  for: readForVariable,
605
+ rescue: readRescueVariable,
497
606
  block: readBlockParameters,
498
607
  do_block: readBlockParameters
499
608
  };
@@ -539,7 +648,7 @@ function namesOfMethod(method) {
539
648
  if (remembered !== void 0) {
540
649
  return remembered;
541
650
  }
542
- const names = scopeNames(parameterNames(method), field(method, "body"));
651
+ const names = scopeNames(parameterNames2(method), field(method, "body"));
543
652
  cache.byMethod.set(method, names);
544
653
  return names;
545
654
  }
@@ -561,10 +670,17 @@ function ownerOfName(node, name, enclosing) {
561
670
  }
562
671
  return enclosing !== null && names.locals.has(name) ? enclosing : null;
563
672
  }
673
+ function isLocalName(node, name, enclosing) {
674
+ const names = enclosing === null ? namesOfFile(node.tree) : namesOfMethod(enclosing);
675
+ if (names.blockParams.has(name) && blockDeclaring(node, name) !== null) {
676
+ return true;
677
+ }
678
+ return names.locals.has(name);
679
+ }
564
680
  function blockDeclaring(node, name) {
565
681
  let current = node.parent;
566
682
  while (current !== null) {
567
- if (BLOCK_TYPES2.has(current.type) && parameterNames(current).has(name)) {
683
+ if (BLOCK_TYPES2.has(current.type) && parameterNames2(current).has(name)) {
568
684
  return current;
569
685
  }
570
686
  if (OWN_BODY_TYPES.has(current.type)) {
@@ -620,6 +736,17 @@ function collectWrites(method, body) {
620
736
  }
621
737
  return [...groups.values()];
622
738
  }
739
+ function instanceWritesRunInOrder(body, name, targets) {
740
+ return writesRunInOrder(
741
+ body,
742
+ name,
743
+ targets.map((target) => ({
744
+ at: target,
745
+ direct: target.parent?.parent?.id === body.id
746
+ })),
747
+ nameReads(new Set(targets.map((target) => target.id)))
748
+ );
749
+ }
623
750
  function orderedWrite(write, scope) {
624
751
  return {
625
752
  at: write.at,
@@ -665,12 +792,28 @@ function readKey(filePath, written, enclosing) {
665
792
  if (node.type !== "identifier" && node.type !== "constant") {
666
793
  return nodeId(filePath, node);
667
794
  }
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);
795
+ if (isMethodOfReceiver(node) || isBareCall(node, enclosing)) {
796
+ return nodeId(filePath, node);
671
797
  }
672
798
  return nameKey(filePath, node, enclosing);
673
799
  }
800
+ function isMethodOfReceiver(node) {
801
+ const parent = node.parent;
802
+ return parent !== null && parent.type === "call" && field(parent, "method")?.id === node.id && field(parent, "receiver") !== null;
803
+ }
804
+ function isReceiverOfCall(node) {
805
+ const parent = node.parent;
806
+ return parent !== null && parent.type === "call" && field(parent, "receiver")?.id === node.id;
807
+ }
808
+ function isBareCall(node, enclosing) {
809
+ if (node.type !== "identifier") {
810
+ return false;
811
+ }
812
+ if (spellsAName(node) && !isReceiverOfCall(node)) {
813
+ return false;
814
+ }
815
+ return !isLocalName(node, node.text, enclosing);
816
+ }
674
817
  var WRITTEN_VALUE_TYPES = /* @__PURE__ */ new Set([
675
818
  "string",
676
819
  "integer",
@@ -699,11 +842,7 @@ function add(emitter, relation, ...tuple) {
699
842
  emitter.db.add(relation, tuple);
700
843
  }
701
844
  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);
845
+ return readKey(emitter.filePath, written, emitter.enclosing);
707
846
  }
708
847
  function pairKeyText(key) {
709
848
  if (key.type === "hash_key_symbol") {
@@ -717,35 +856,29 @@ function pairKeyText(key) {
717
856
  }
718
857
  return null;
719
858
  }
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;
859
+ function calleeKeyOf(filePath, node, enclosing) {
860
+ if (isBareCall(node, enclosing)) {
861
+ return nameKey(filePath, node, enclosing);
730
862
  }
731
- add(
732
- emitter,
733
- "readsProperty",
734
- nodeId(emitter.filePath, node),
735
- valueKey(emitter, receiver),
736
- method.text
737
- );
863
+ const method = node.type === "call" ? field(node, "method") : null;
864
+ if (method === null) {
865
+ return null;
866
+ }
867
+ return field(node, "receiver") === null ? nameKey(filePath, method, enclosing) : nodeId(filePath, method);
738
868
  }
739
869
  function emitCall(emitter, call) {
740
870
  add(emitter, "writtenValue", nodeId(emitter.filePath, call));
741
- const method = field(call, "method");
742
- if (method === null) {
871
+ const method = call.type === "identifier" ? call : field(call, "method");
872
+ const calleeKey = calleeKeyOf(emitter.filePath, call, emitter.enclosing);
873
+ if (method === null || calleeKey === null) {
743
874
  return;
744
875
  }
745
876
  const receiver = field(call, "receiver");
746
877
  const callKey = nodeId(emitter.filePath, call);
747
- const calleeKey = receiver === null ? valueKey(emitter, method) : nodeId(emitter.filePath, method);
748
878
  add(emitter, "call", callKey, calleeKey);
879
+ if (!emitter.insideMethod) {
880
+ add(emitter, "callOutsideMethod", callKey);
881
+ }
749
882
  if (receiver !== null) {
750
883
  add(
751
884
  emitter,
@@ -754,6 +887,8 @@ function emitCall(emitter, call) {
754
887
  valueKey(emitter, receiver),
755
888
  method.text
756
889
  );
890
+ } else if (emitter.selfKey !== null) {
891
+ add(emitter, "readsProperty", calleeKey, emitter.selfKey, method.text);
757
892
  }
758
893
  const args = field(call, "arguments");
759
894
  let position = 0;
@@ -878,9 +1013,7 @@ function emitExpressionFacts(emitter, node) {
878
1013
  emitDynamicDefinition(emitter, node, []);
879
1014
  walkExpressions(node, emitter, (child, turns) => {
880
1015
  emitDynamicDefinition(emitter, child, turns);
881
- if (isPropertyRead(child)) {
882
- emitPropertyRead(emitter, child);
883
- } else if (child.type === "call") {
1016
+ if (child.type === "call" || isBareCall(child, emitter.enclosing)) {
884
1017
  emitCall(emitter, child);
885
1018
  }
886
1019
  if (ARRAY_TYPES.has(child.type)) {
@@ -936,28 +1069,52 @@ function collectInstanceWrite(emitter, node) {
936
1069
  const operator = field(node, "operator")?.text;
937
1070
  const value = node.type === "assignment" || WHOLE_VALUE_OPERATORS.has(operator ?? "") ? right : null;
938
1071
  const written = collector.get(left.text) ?? [];
939
- written.push(
940
- describeWrite(emitter, {
1072
+ written.push({
1073
+ write: describeWrite(emitter, {
941
1074
  name: left.text,
942
1075
  target: left,
943
1076
  value,
944
1077
  at: node,
945
1078
  fromParameter: false
946
- })
947
- );
1079
+ }),
1080
+ target: left
1081
+ });
948
1082
  collector.set(left.text, written);
949
1083
  }
1084
+ function settledWrites(writes, ordered) {
1085
+ const described = writes.map((written) => written.write);
1086
+ const settled = valueLeftByWrites(described, ordered);
1087
+ if (settled !== null) {
1088
+ return [settled];
1089
+ }
1090
+ const left = [];
1091
+ for (const write of described) {
1092
+ if (write.value !== null && !write.narrowsName) {
1093
+ left.push(write.value);
1094
+ }
1095
+ }
1096
+ return left;
1097
+ }
950
1098
  function emitInstanceWrites(emitter, classKey, collected) {
951
1099
  for (const [name, writes] of collected) {
952
- const settled = valueLeftByWrites(writes, false);
953
- if (settled !== null) {
954
- add(emitter, "holdsProperty", classKey, name, settled);
1100
+ for (const value of settledWrites(writes, false)) {
1101
+ add(emitter, "holdsProperty", classKey, name, value);
1102
+ }
1103
+ }
1104
+ }
1105
+ function emitInstanceStores(emitter, funcKey, body, collected) {
1106
+ for (const [name, writes] of collected) {
1107
+ const deciding = writes.filter((written) => !written.write.narrowsName);
1108
+ if (deciding.length === 0) {
955
1109
  continue;
956
1110
  }
957
- for (const write of writes) {
958
- if (write.value !== null && !write.narrowsName) {
959
- add(emitter, "holdsProperty", classKey, name, write.value);
960
- }
1111
+ const ordered = instanceWritesRunInOrder(
1112
+ body,
1113
+ name,
1114
+ deciding.map((written) => written.target)
1115
+ );
1116
+ for (const value of settledWrites(deciding, ordered)) {
1117
+ add(emitter, "storesProperty", funcKey, name, value);
961
1118
  }
962
1119
  }
963
1120
  }
@@ -1017,7 +1174,7 @@ function emitMethodFacts(emitter, method) {
1017
1174
  add(inside, "returnsValue", funcKey, valueKey(inside, returned));
1018
1175
  }
1019
1176
  }
1020
- if (child.type === "call") {
1177
+ if (child.type === "call" || isBareCall(child, method)) {
1021
1178
  add(inside, "bodyCalls", funcKey, nodeId(inside.filePath, child));
1022
1179
  }
1023
1180
  });
@@ -1085,20 +1242,32 @@ function emitScopeWrites(emitter, method, body) {
1085
1242
  }
1086
1243
  }
1087
1244
  function emitCandidates(emitter, key, group) {
1245
+ let unstated = false;
1246
+ let stated = 0;
1088
1247
  for (const write of group.writes) {
1089
1248
  if (write.value === null) {
1090
1249
  add(emitter, "writesUnstated", key);
1250
+ unstated = true;
1091
1251
  continue;
1092
1252
  }
1093
1253
  if (write.fromParameter) {
1254
+ unstated = true;
1094
1255
  continue;
1095
1256
  }
1096
1257
  if (!describeWrite(emitter, write).narrowsName) {
1097
1258
  add(emitter, "mayHold", key, valueKey(emitter, write.value));
1259
+ stated += 1;
1098
1260
  }
1099
1261
  }
1262
+ if (!unstated && stated > 0) {
1263
+ add(emitter, "writesAllStated", key);
1264
+ }
1100
1265
  }
1101
1266
  var METHOD_TYPES = /* @__PURE__ */ new Set(["method", "singleton_method"]);
1267
+ var INITIALIZE_METHOD = "initialize";
1268
+ function receiverKeyOf(filePath, method, classKey) {
1269
+ return method.type === "singleton_method" ? classKey : `${nodeId(filePath, method)}#self`;
1270
+ }
1102
1271
  var CONSTANT_REF_TYPES = /* @__PURE__ */ new Set(["constant", "scope_resolution"]);
1103
1272
  function mixedInConstants(body, callName) {
1104
1273
  return bareCallArgumentGroups(body, callName).flatMap((group) => [...group].reverse()).reverse().map(readThrough).filter((argument) => CONSTANT_REF_TYPES.has(argument.type));
@@ -1126,6 +1295,7 @@ function emitClassFacts(emitter, cls) {
1126
1295
  const within = {
1127
1296
  ...emitter,
1128
1297
  selfKey: classKey,
1298
+ insideMethod: false,
1129
1299
  instanceWrites: collected
1130
1300
  };
1131
1301
  const statements = body === null ? [] : bodyStatementsRun(body, cls.type === "module", emitter.bodyBlocks);
@@ -1153,10 +1323,30 @@ function emitClassFacts(emitter, cls) {
1153
1323
  emitExpressionFacts(within, statement);
1154
1324
  continue;
1155
1325
  }
1156
- const funcKey = emitMethodFacts(within, statement);
1326
+ const stored = /* @__PURE__ */ new Map();
1327
+ const receiverKey = receiverKeyOf(emitter.filePath, statement, classKey);
1328
+ if (receiverKey !== classKey) {
1329
+ add(emitter, "instanceOf", receiverKey, classKey);
1330
+ }
1331
+ const funcKey = emitMethodFacts(
1332
+ {
1333
+ ...within,
1334
+ selfKey: receiverKey,
1335
+ insideMethod: receiverKey !== classKey,
1336
+ instanceWrites: stored
1337
+ },
1338
+ statement
1339
+ );
1157
1340
  const name = field(statement, "name");
1158
1341
  if (name !== null) {
1159
1342
  add(emitter, "holdsProperty", classKey, name.text, funcKey);
1343
+ if (name.text === INITIALIZE_METHOD) {
1344
+ add(emitter, "initializes", classKey, funcKey);
1345
+ }
1346
+ }
1347
+ const methodBody = field(statement, "body");
1348
+ if (methodBody !== null) {
1349
+ emitInstanceStores(within, funcKey, methodBody, stored);
1160
1350
  }
1161
1351
  }
1162
1352
  emitInstanceWrites(within, classKey, collected);
@@ -1168,6 +1358,7 @@ function emitValueFacts(db, filePath, root, bodyBlocks = NO_BODY_BLOCKS) {
1168
1358
  filePath,
1169
1359
  enclosing: null,
1170
1360
  selfKey: null,
1361
+ insideMethod: false,
1171
1362
  instanceWrites: null,
1172
1363
  bodyBlocks
1173
1364
  };
@@ -1248,7 +1439,7 @@ function rubyLowering(options) {
1248
1439
  statement: statementOf,
1249
1440
  siteOf,
1250
1441
  functionOf,
1251
- writtenTo: (node) => context === null ? null : context.writtenTo(node),
1442
+ writtenTo: (node, site) => context === null ? null : context.writtenTo(node, site),
1252
1443
  callable: (node) => {
1253
1444
  if (!isInlinableCall(node)) {
1254
1445
  return null;
@@ -1765,17 +1956,32 @@ function siteOf(node) {
1765
1956
  current = parent;
1766
1957
  }
1767
1958
  }
1959
+ var BARE_SPLAT_TYPES = /* @__PURE__ */ new Set(["splat_parameter", "hash_splat_parameter"]);
1768
1960
  function parametersOf2(parameters) {
1769
1961
  if (parameters === null) {
1770
1962
  return [];
1771
1963
  }
1772
- return named(parameters).flatMap((parameter) => {
1964
+ const declared = [];
1965
+ let position = 0;
1966
+ for (const parameter of named(parameters)) {
1967
+ if (BARE_SPLAT_TYPES.has(parameter.type) && field(parameter, "name") === null) {
1968
+ continue;
1969
+ }
1773
1970
  if (parameter.type === "identifier") {
1774
- return [{ name: parameter.text, default: null }];
1971
+ declared.push({ name: parameter.text, default: null, position });
1972
+ } else {
1973
+ const name = field(parameter, "name");
1974
+ if (name !== null) {
1975
+ declared.push({
1976
+ name: name.text,
1977
+ default: field(parameter, "value"),
1978
+ position
1979
+ });
1980
+ }
1775
1981
  }
1776
- const name = field(parameter, "name");
1777
- return name === null ? [] : [{ name: name.text, default: field(parameter, "value") }];
1778
- });
1982
+ position += 1;
1983
+ }
1984
+ return declared;
1779
1985
  }
1780
1986
  function functionOf(node) {
1781
1987
  if (node.type === "program") {
@@ -2183,25 +2389,56 @@ var rubyRows = [
2183
2389
 
2184
2390
  // src/values/evaluator.ts
2185
2391
  var evaluators = /* @__PURE__ */ new WeakMap();
2392
+ var contexts = /* @__PURE__ */ new WeakMap();
2186
2393
  var withoutFacts = /* @__PURE__ */ new WeakMap();
2394
+ var filesByTree = /* @__PURE__ */ new WeakMap();
2187
2395
  var METHOD_TYPES3 = /* @__PURE__ */ new Set(["method", "singleton_method"]);
2188
2396
  function bindEvaluator(db, nodes) {
2189
- evaluators.set(
2190
- db,
2191
- new Evaluator(
2192
- rubyLowering({ context: contextOver(db, nodes), rows: rubyRows })
2193
- )
2194
- );
2397
+ for (const entry of nodes.files) {
2398
+ filesByTree.set(entry.root.tree, entry.file);
2399
+ }
2400
+ const context = contextOver(db, nodes);
2401
+ contexts.set(db, context);
2402
+ evaluators.set(db, new Evaluator(rubyLowering({ context, rows: rubyRows })));
2195
2403
  }
2196
- function evaluatedValue(node, db, bindings) {
2404
+ function writtenNodeOf(node, db, site) {
2405
+ const context = db === void 0 ? void 0 : contexts.get(db);
2406
+ return context === void 0 ? null : context.writtenTo(node, site);
2407
+ }
2408
+ function evaluatedValue(node, db, bindings, site) {
2197
2409
  const evaluator = evaluatorFor(node, db);
2198
2410
  if (bindings === void 0) {
2199
- return force(evaluator.evaluate(node));
2411
+ return force(
2412
+ site === void 0 ? evaluator.evaluate(node) : evaluator.evaluate(node, { site })
2413
+ );
2200
2414
  }
2201
2415
  const supplied = new Map(
2202
2416
  [...bindings].map(([name, value]) => [name, text(value)])
2203
2417
  );
2204
- return force(evaluator.evaluate(node, { bindings: supplied }));
2418
+ return force(
2419
+ evaluator.evaluate(node, {
2420
+ bindings: supplied,
2421
+ ...site === void 0 ? {} : { site }
2422
+ })
2423
+ );
2424
+ }
2425
+ function constructionSitesOf(node, db) {
2426
+ const cls = enclosingClass(node);
2427
+ const file = cls === null ? void 0 : filesByTree.get(cls.tree);
2428
+ if (db === void 0 || cls === null || file === void 0) {
2429
+ return [];
2430
+ }
2431
+ return constructionSites(db, nodeId(file, cls));
2432
+ }
2433
+ function enclosingClass(node) {
2434
+ let current = node.parent;
2435
+ while (current !== null) {
2436
+ if (current.type === "class") {
2437
+ return current;
2438
+ }
2439
+ current = current.parent;
2440
+ }
2441
+ return null;
2205
2442
  }
2206
2443
  function stringValueOf(node, db, bindings) {
2207
2444
  return literalOf(evaluatedValue(node, db, bindings));
@@ -2253,15 +2490,13 @@ function contextOver(db, nodes) {
2253
2490
  }
2254
2491
  const fileOf = (node) => filesByRoot.get(node.tree.rootNode.id)?.file ?? null;
2255
2492
  return {
2256
- writtenTo: (node) => {
2493
+ writtenTo: (node, site) => {
2257
2494
  const file = fileOf(node);
2258
2495
  if (file === null) {
2259
2496
  return null;
2260
2497
  }
2261
- const answer = writtenValueOf(
2262
- db,
2263
- readKey(file, node, enclosingMethod(node))
2264
- );
2498
+ const key = readKey(file, node, enclosingMethod(node));
2499
+ const answer = site === void 0 ? writtenValueOf(db, key) : writtenValueUnder(db, key, site);
2265
2500
  return answer === null ? null : nodeOfKey(rootsByFile, answer);
2266
2501
  },
2267
2502
  callable: (call) => {
@@ -2279,6 +2514,31 @@ function contextOver(db, nodes) {
2279
2514
  };
2280
2515
  }
2281
2516
 
2517
+ // src/values/literals.ts
2518
+ import { force as force2, literalOf as literalOf2 } from "@suss/values";
2519
+ function literalElementsIn(value) {
2520
+ if (value.kind !== "sequence") {
2521
+ return null;
2522
+ }
2523
+ const elements = [];
2524
+ for (const item of value.items) {
2525
+ const literal = literalOf2(force2(item.value));
2526
+ if (literal === null) {
2527
+ return null;
2528
+ }
2529
+ elements.push(literal);
2530
+ }
2531
+ return elements;
2532
+ }
2533
+ function literalElementsOf(node, db) {
2534
+ return literalElementsIn(evaluatedValue(node, db));
2535
+ }
2536
+ function namesOf(node, db) {
2537
+ const value = evaluatedValue(node, db);
2538
+ const single = literalOf2(value);
2539
+ return single === null ? literalElementsIn(value) : [single];
2540
+ }
2541
+
2282
2542
  // src/defineMethod.ts
2283
2543
  var NOTHING = {
2284
2544
  names: /* @__PURE__ */ new Set(),
@@ -2326,7 +2586,7 @@ function readOne(db, rootsByFile, nameKey2) {
2326
2586
  const patterns = [];
2327
2587
  for (const bindings of loopTurns(db, rootsByFile, nameKey2)) {
2328
2588
  const value = node === null ? hole2(nameKey2) : evaluatedValue(node, db, bindings.size === 0 ? void 0 : bindings);
2329
- const name = literalOf2(value);
2589
+ const name = literalOf3(value);
2330
2590
  if (name !== null) {
2331
2591
  names.push(name);
2332
2592
  continue;
@@ -2382,29 +2642,14 @@ function listsBehind(db, rootsByFile, nameKey2) {
2382
2642
  0,
2383
2643
  nameKey2
2384
2644
  )) {
2385
- const elements = literalElementsOf(rootsByFile, String(overKey), db);
2645
+ const node = nodeOfKey2(rootsByFile, String(overKey));
2646
+ const elements = node === null ? null : literalElementsOf(node, db);
2386
2647
  if (elements !== null) {
2387
2648
  found.push({ element: String(element), index: String(index), elements });
2388
2649
  }
2389
2650
  }
2390
2651
  return found;
2391
2652
  }
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
2653
 
2409
2654
  // src/scope.ts
2410
2655
  function qualifyConstantRef(node, nesting) {
@@ -2736,108 +2981,12 @@ import {
2736
2981
  import { absentReading as absentReading2, unreadableReading as unreadableReading2 } from "@suss/extractor";
2737
2982
 
2738
2983
  // src/clientCalls.ts
2739
- import { namesNothing, restBinding } from "@suss/behavioral-ir";
2984
+ import { hasNameHole, namesNothing, restBinding } from "@suss/behavioral-ir";
2740
2985
  import { pathOf } from "@suss/values";
2741
2986
 
2742
2987
  // src/paths/effects.ts
2743
2988
  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
- }
2989
+ import { constantOf, literalOf as literalOf4 } from "@suss/values";
2841
2990
 
2842
2991
  // src/paths/lowering.ts
2843
2992
  var RAISE_NAMES = /* @__PURE__ */ new Set(["raise", "fail"]);
@@ -3125,23 +3274,21 @@ var BLOCK_TYPES5 = /* @__PURE__ */ new Set([
3125
3274
  "do_block",
3126
3275
  "block"
3127
3276
  ]);
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
- };
3277
+ function literalArgOf(node, facts) {
3278
+ const value = evaluatedValue(node, facts);
3279
+ const text2 = literalOf4(value);
3280
+ if (text2 !== null) {
3281
+ return { kind: "string", value: text2 };
3282
+ }
3283
+ const constant2 = constantOf(value);
3284
+ if (typeof constant2 === "number") {
3285
+ return { kind: "number", value: constant2 };
3286
+ }
3287
+ if (typeof constant2 === "boolean") {
3288
+ return { kind: "boolean", value: constant2 };
3289
+ }
3290
+ return null;
3291
+ }
3145
3292
  function children2(node) {
3146
3293
  return node.namedChildren.filter((child) => child !== null);
3147
3294
  }
@@ -3225,23 +3372,27 @@ function calleeText(call) {
3225
3372
  }
3226
3373
  return receiver === null ? method : `${receiver.text}.${method}`;
3227
3374
  }
3228
- function argOf(node) {
3229
- const literal = LITERAL_ARGS[node.type]?.(node);
3230
- if (literal !== null && literal !== void 0) {
3375
+ function argOf(node, facts) {
3376
+ const literal = literalArgOf(node, facts);
3377
+ if (literal !== null) {
3231
3378
  return literal;
3232
3379
  }
3233
3380
  if (node.type === "call" && !isArglessReceiverCall(node)) {
3234
- return { kind: "call", callee: calleeText(node), args: argsOf(node) };
3381
+ return {
3382
+ kind: "call",
3383
+ callee: calleeText(node),
3384
+ args: argsOf(node, facts)
3385
+ };
3235
3386
  }
3236
3387
  return { kind: "identifier", name: node.text };
3237
3388
  }
3238
- function argsOf(call) {
3389
+ function argsOf(call, facts) {
3239
3390
  const args = field(call, "arguments");
3240
3391
  if (args === null) {
3241
3392
  return [];
3242
3393
  }
3243
3394
  return children2(args).map(
3244
- (child) => child.type === "pair" ? argOf(field(child, "value") ?? child) : argOf(child)
3395
+ (child) => child.type === "pair" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3245
3396
  );
3246
3397
  }
3247
3398
  function enclosingStatement(call, body) {
@@ -3254,7 +3405,7 @@ function enclosingStatement(call, body) {
3254
3405
  }
3255
3406
  return current;
3256
3407
  }
3257
- function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS) {
3408
+ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, keepsArglessCall = NO_ARGLESS_CALLS, facts) {
3258
3409
  const body = field(definitionNode, "body");
3259
3410
  if (body === null) {
3260
3411
  return [];
@@ -3294,7 +3445,7 @@ function invocationEffects(definitionNode, inherited = NO_INHERITED_METHODS, kee
3294
3445
  return {
3295
3446
  type: "invocation",
3296
3447
  callee: calleeText(call),
3297
- args: argsOf(call),
3448
+ args: argsOf(call, facts),
3298
3449
  async: false,
3299
3450
  ...conditions.length > 0 ? { preconditions: conditions } : {}
3300
3451
  };
@@ -3309,7 +3460,7 @@ import {
3309
3460
  unreadableReading,
3310
3461
  writtenReading
3311
3462
  } from "@suss/extractor";
3312
- import { constantOf, literalOf as literalOf4 } from "@suss/values";
3463
+ import { constantOf as constantOf2, literalOf as literalOf6 } from "@suss/values";
3313
3464
 
3314
3465
  // src/paths/predicates.ts
3315
3466
  var OPERATORS = {
@@ -3378,7 +3529,7 @@ function binaryOf(node) {
3378
3529
  return opaqueOf(node);
3379
3530
  }
3380
3531
  function valueRefOf(node) {
3381
- const literal = literalOf3(node);
3532
+ const literal = literalOf5(node);
3382
3533
  if (literal !== null) {
3383
3534
  return literal;
3384
3535
  }
@@ -3391,7 +3542,7 @@ function valueRefOf(node) {
3391
3542
  }
3392
3543
  return { type: "unresolved", sourceText: node.text };
3393
3544
  }
3394
- function literalOf3(node) {
3545
+ function literalOf5(node) {
3395
3546
  if (node.type === "integer") {
3396
3547
  const value = Number.parseInt(node.text, 10);
3397
3548
  return Number.isNaN(value) ? null : { type: "literal", value };
@@ -3434,13 +3585,13 @@ function statusArgumentOf(call, declaration) {
3434
3585
  }
3435
3586
  return args.positional[declaration.statusArgument] ?? null;
3436
3587
  }
3437
- function statusNumberOf(node, names) {
3438
- const value = evaluatedValue(node);
3439
- const constant2 = constantOf(value);
3588
+ function statusNumberOf(node, names, facts) {
3589
+ const value = evaluatedValue(node, facts);
3590
+ const constant2 = constantOf2(value);
3440
3591
  if (typeof constant2 === "number") {
3441
3592
  return constant2;
3442
3593
  }
3443
- const name = literalOf4(value);
3594
+ const name = literalOf6(value);
3444
3595
  return name === null ? null : names[name] ?? null;
3445
3596
  }
3446
3597
  function declarationsByName(declarations) {
@@ -3484,12 +3635,12 @@ function collectReturns(node, found) {
3484
3635
  }
3485
3636
  return found;
3486
3637
  }
3487
- function readingOfCall(call, declaration, names) {
3638
+ function readingOfCall(call, declaration, names, facts) {
3488
3639
  const argument = statusArgumentOf(call, declaration);
3489
3640
  if (argument === null) {
3490
3641
  return declaration.defaultStatusCode === void 0 ? absentReading : writtenReading(declaration.defaultStatusCode, rangeOf(call));
3491
3642
  }
3492
- const status = statusNumberOf(argument, names);
3643
+ const status = statusNumberOf(argument, names, facts);
3493
3644
  if (status === null) {
3494
3645
  return unreadableReading(
3495
3646
  "This response writes a status that does not settle on a number here, so this outcome claims none",
@@ -3633,7 +3784,7 @@ function responseBranches(method, pattern, effects, extraEffects, options = {})
3633
3784
  const outcomes = [];
3634
3785
  for (const terminal of terminals) {
3635
3786
  const declaration = declarationOf(terminal, byName, locals);
3636
- const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames);
3787
+ const reading = declaration === void 0 ? absentReading : readingOfCall(terminal, declaration, statusNames, options.facts);
3637
3788
  for (const path7 of enumerated.byTerminal.get(terminal) ?? []) {
3638
3789
  outcomes.push({
3639
3790
  conditions: path7.map(conditionOf),
@@ -3667,11 +3818,9 @@ function clientCallUnits(root, pack, pattern, options) {
3667
3818
  continue;
3668
3819
  }
3669
3820
  for (const call of callsUnder(method)) {
3670
- const request = requestCall(call, method, root, pattern, options);
3671
- if (request === null) {
3672
- continue;
3821
+ for (const request of requestCalls(call, pattern, options)) {
3822
+ units.push(clientUnit(method, name, request, pattern, pack, options));
3673
3823
  }
3674
- units.push(clientUnit(method, name, request, pattern, pack, options));
3675
3824
  }
3676
3825
  }
3677
3826
  return units;
@@ -3700,36 +3849,60 @@ function callsUnder(node, found = []) {
3700
3849
  }
3701
3850
  return found;
3702
3851
  }
3703
- function requestCall(call, method, root, pattern, options) {
3852
+ function requestCalls(call, pattern, options) {
3704
3853
  const receiver = field(call, "receiver");
3705
3854
  const called = field(call, "method")?.text;
3706
- if (receiver === null || called === void 0) {
3707
- return null;
3855
+ if (receiver === null || called === void 0 || !isLibraryReceiver(receiver, pattern, options)) {
3856
+ return [];
3708
3857
  }
3709
- const prefix = receiverPrefix(receiver, method, root, pattern, options);
3710
- if (prefix === null) {
3711
- return null;
3858
+ const read = (site) => requestCall(
3859
+ call,
3860
+ called,
3861
+ receiverPrefix(receiver, pattern, options, site),
3862
+ pattern,
3863
+ options,
3864
+ site
3865
+ );
3866
+ const plain = read();
3867
+ if (plain !== null && !hasNameHole(plain.path)) {
3868
+ return [plain];
3869
+ }
3870
+ const byBoundary = /* @__PURE__ */ new Map();
3871
+ for (const site of constructionSitesOf(call, options.facts)) {
3872
+ const stated = read(site);
3873
+ if (stated !== null) {
3874
+ byBoundary.set(`${stated.method} ${stated.path}`, stated);
3875
+ }
3876
+ }
3877
+ if (byBoundary.size > 0) {
3878
+ return [...byBoundary.values()];
3712
3879
  }
3880
+ return plain === null ? [] : [plain];
3881
+ }
3882
+ function requestCall(call, called, prefix, pattern, options, site) {
3713
3883
  const args = readCallArgs(field(call, "arguments"));
3714
3884
  const verb = pattern.verbMethodNames[called];
3715
3885
  if (verb !== void 0) {
3716
- const path8 = urlIn(args, pattern, options);
3886
+ const path8 = urlIn(args, pattern, options, site);
3717
3887
  return path8 === null ? null : { method: verb, path: prefix + path8 };
3718
3888
  }
3719
3889
  const sent = pattern.requestObject;
3720
3890
  if (sent === void 0 || sent.attribute !== called) {
3721
3891
  return null;
3722
3892
  }
3723
- const built = requestBuilt(args.positional[0], method, sent);
3893
+ const built = requestBuilt(args.positional[0], sent, options, site);
3724
3894
  if (built === null) {
3725
3895
  return null;
3726
3896
  }
3727
- const path7 = pathAt(built.url, options);
3897
+ const path7 = pathAt(built.url, options, site);
3728
3898
  return path7 === null ? null : { method: built.method, path: prefix + path7 };
3729
3899
  }
3730
- function requestBuilt(argument, method, sent) {
3731
- const written = argument === void 0 ? null : assignedCall(argument, method) ?? argument;
3732
- if (written === null || written.type !== "call") {
3900
+ function requestBuilt(argument, sent, options, site) {
3901
+ if (argument === void 0) {
3902
+ return null;
3903
+ }
3904
+ const written = writtenNodeOf(argument, options.facts, site) ?? argument;
3905
+ if (written.type !== "call") {
3733
3906
  return null;
3734
3907
  }
3735
3908
  const requestClass = field(written, "receiver")?.text;
@@ -3740,28 +3913,18 @@ function requestBuilt(argument, method, sent) {
3740
3913
  const args = readCallArgs(field(written, "arguments"));
3741
3914
  return { method: verb, url: args.positional[sent.urlPosition] };
3742
3915
  }
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;
3916
+ function isLibraryReceiver(receiver, pattern, options) {
3917
+ return namesConstant(receiver, pattern.constantName) || builderCallBehind(receiver, pattern, options.facts) !== null;
3749
3918
  }
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);
3919
+ function receiverPrefix(receiver, pattern, options, site) {
3920
+ const built = builderCallBehind(receiver, pattern, options.facts);
3758
3921
  if (built === null) {
3759
- return null;
3922
+ return "";
3760
3923
  }
3761
3924
  const args = readCallArgs(field(built, "arguments"));
3762
3925
  const keyword = pattern.builderUrlKeyword;
3763
3926
  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));
3927
+ const path7 = base === void 0 ? null : pathOf(evaluatedValue(base, options.facts, void 0, site));
3765
3928
  return path7 === void 0 || path7 === null ? "" : trimmed(path7);
3766
3929
  }
3767
3930
  function trimmed(path7) {
@@ -3773,61 +3936,28 @@ function namesConstant(receiver, constantName2) {
3773
3936
  }
3774
3937
  return receiver.type === "scope_resolution" && compoundName(receiver) === constantName2;
3775
3938
  }
3776
- function builtBy(name, method, root, pattern) {
3939
+ function builderCallBehind(receiver, pattern, facts) {
3777
3940
  const builders = pattern.receiverBuilders ?? [];
3778
3941
  if (builders.length === 0) {
3779
3942
  return null;
3780
3943
  }
3781
- const value = assignedValue(name, method) ?? valueMethodNamedReturns(name, root);
3782
- if (value === null || value.type !== "call" || !builders.includes(field(value, "method")?.text ?? "")) {
3944
+ const written = writtenNodeOf(receiver, facts);
3945
+ if (written === null || written.type !== "call" || !builders.includes(field(written, "method")?.text ?? "")) {
3783
3946
  return null;
3784
3947
  }
3785
- const receiver = field(value, "receiver");
3786
- return receiver !== null && namesConstant(receiver, pattern.constantName) ? value : null;
3948
+ const constant2 = field(written, "receiver");
3949
+ return constant2 !== null && namesConstant(constant2, pattern.constantName) ? written : null;
3787
3950
  }
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) {
3809
- return null;
3810
- }
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;
3820
- }
3821
- function urlIn(args, pattern, options) {
3951
+ function urlIn(args, pattern, options, site) {
3822
3952
  const keyword = pattern.url.keyword;
3823
3953
  const written = (keyword === void 0 ? void 0 : args.keyword[keyword]) ?? args.positional[pattern.url.position];
3824
- return pathAt(written, options);
3954
+ return pathAt(written, options, site);
3825
3955
  }
3826
- function pathAt(written, options) {
3956
+ function pathAt(written, options, site) {
3827
3957
  if (written === void 0) {
3828
3958
  return null;
3829
3959
  }
3830
- const path7 = pathOf(evaluatedValue(written, options.facts));
3960
+ const path7 = pathOf(evaluatedValue(written, options.facts, void 0, site));
3831
3961
  return path7 === void 0 || namesNothing(path7) ? null : path7;
3832
3962
  }
3833
3963
  function clientUnit(method, name, request, pattern, pack, options) {
@@ -3850,15 +3980,15 @@ function clientUnit(method, name, request, pattern, pack, options) {
3850
3980
  recognition: pack.name
3851
3981
  }),
3852
3982
  parameters: [],
3853
- branches: callerBranches(method, range),
3983
+ branches: callerBranches(method, range, options.facts),
3854
3984
  ...responseAccessors(pattern),
3855
3985
  bodyContent: "statements",
3856
3986
  dependencyCalls: [],
3857
3987
  declaredContract: null
3858
3988
  };
3859
3989
  }
3860
- function callerBranches(method, range) {
3861
- const effects = invocationEffects(method);
3990
+ function callerBranches(method, range, facts) {
3991
+ const effects = invocationEffects(method, void 0, void 0, facts);
3862
3992
  return returnPathBranches(method, effects) ?? [returnBranch(range, effects)];
3863
3993
  }
3864
3994
  function responseAccessors(pattern) {
@@ -4016,7 +4146,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4016
4146
  }
4017
4147
  const form = byName.get(called);
4018
4148
  if (form !== void 0) {
4019
- for (const declaration of declarationsOf(statement, form)) {
4149
+ for (const declaration of declarationsOf(statement, form, read.facts)) {
4020
4150
  declared = [
4021
4151
  ...declared.filter((earlier) => !sameFilter(earlier, declaration)),
4022
4152
  declaration
@@ -4026,7 +4156,7 @@ function controllerFilters(pattern, ancestry, read = {}) {
4026
4156
  }
4027
4157
  const skipped = skipNames.get(called);
4028
4158
  if (skipped !== void 0) {
4029
- declared = applySkips(declared, skipsOf(statement, skipped));
4159
+ declared = applySkips(declared, skipsOf(statement, skipped, read.facts));
4030
4160
  }
4031
4161
  }
4032
4162
  const resolved = [];
@@ -4063,14 +4193,14 @@ function filterReference(filter, displayPath) {
4063
4193
  ...filter.filter.onThrow === true ? { onThrow: true } : {}
4064
4194
  };
4065
4195
  }
4066
- function filterUnit(filter, pattern, displayPath, body) {
4196
+ function filterUnit(filter, pattern, displayPath, body, facts) {
4067
4197
  const range = rangeOf(filter.method);
4068
4198
  const branches = responseBranches(
4069
4199
  filter.method,
4070
4200
  pattern,
4071
4201
  body.effects ?? [],
4072
4202
  body.extraEffects,
4073
- { fallthrough: "handOn" }
4203
+ { fallthrough: "handOn", facts }
4074
4204
  );
4075
4205
  return {
4076
4206
  identity: {
@@ -4118,30 +4248,30 @@ function calledName(statement) {
4118
4248
  }
4119
4249
  return field(statement, "method")?.text ?? null;
4120
4250
  }
4121
- function declarationsOf(statement, filter) {
4251
+ function declarationsOf(statement, filter, facts) {
4122
4252
  const args = readCallArgs(field(statement, "arguments"));
4123
4253
  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) => ({
4254
+ const only = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.include], facts);
4255
+ const except = keywords === void 0 ? null : actionsUnder(args.keyword[keywords.exclude], facts);
4256
+ return methodNamesOf(args, filter, facts).map((methodName) => ({
4127
4257
  filter,
4128
4258
  methodName,
4129
4259
  only,
4130
4260
  except: except ?? /* @__PURE__ */ new Set()
4131
4261
  }));
4132
4262
  }
4133
- function methodNamesOf(args, filter) {
4263
+ function methodNamesOf(args, filter, facts) {
4134
4264
  if (filter.methodFrom === "withKeyword") {
4135
4265
  const named2 = args.keyword.with;
4136
- const value = named2 === void 0 ? null : symbolValue(named2);
4266
+ const value = named2 === void 0 ? null : stringValueOf(named2, facts);
4137
4267
  return value === null ? [] : [value];
4138
4268
  }
4139
- return args.positional.map((arg) => symbolValue(arg)).filter((name) => name !== null);
4269
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null);
4140
4270
  }
4141
- function skipsOf(statement, filterName) {
4271
+ function skipsOf(statement, filterName, facts) {
4142
4272
  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 }));
4273
+ const actions = actionsUnder(args.keyword.only, facts);
4274
+ return args.positional.map((arg) => stringValueOf(arg, facts)).filter((name) => name !== null).map((methodName) => ({ filterName, methodName, actions }));
4145
4275
  }
4146
4276
  function sameFilter(one, other) {
4147
4277
  return one.filter.name === other.filter.name && one.methodName === other.methodName;
@@ -4165,26 +4295,12 @@ function applySkips(declared, skips) {
4165
4295
  }
4166
4296
  return left;
4167
4297
  }
4168
- function namesUnder(node) {
4298
+ function actionsUnder(node, facts) {
4169
4299
  if (node === void 0) {
4170
4300
  return null;
4171
4301
  }
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;
4302
+ const found = namesOf(node, facts);
4303
+ return found === null ? null : new Set(found);
4188
4304
  }
4189
4305
 
4190
4306
  // src/storage.ts
@@ -4262,67 +4378,64 @@ function argumentsOf2(call) {
4262
4378
  const args = field(call, "arguments");
4263
4379
  return args === null ? [] : children3(args);
4264
4380
  }
4265
- function isKeywordArgument(argument) {
4266
- return argument.type === "pair" || argument.type === "hash";
4267
- }
4268
- function bareName(node) {
4269
- return node.text.replace(/^:/, "").replace(/:$/, "");
4270
- }
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 [];
4381
+ function recordFieldsOf(argument, facts) {
4382
+ const value = evaluatedValue(argument, facts);
4383
+ return value.kind === "record" ? [...value.fields.keys()] : [];
4279
4384
  }
4280
- function keywordKeys(call) {
4385
+ function keywordKeys(call, facts) {
4281
4386
  const keys = [];
4282
4387
  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
- }
4388
+ if (argument.type !== "pair") {
4389
+ keys.push(...recordFieldsOf(argument, facts));
4390
+ continue;
4391
+ }
4392
+ const key = field(argument, "key");
4393
+ const name = key === null ? null : keyNameOf(key);
4394
+ if (name !== null) {
4395
+ keys.push(name);
4288
4396
  }
4289
4397
  }
4290
4398
  return keys;
4291
4399
  }
4292
- function hasPositionalArgument(call) {
4293
- return argumentsOf2(call).some((argument) => !isKeywordArgument(argument));
4400
+ function keyNameOf(key) {
4401
+ return hashKeySymbolName(key) ?? symbolValue(key) ?? stringLiteralValue(key);
4294
4402
  }
4295
- function columnsAskedFor(call, pattern) {
4403
+ function hasPositionalArgument(call, facts) {
4404
+ return argumentsOf2(call).some(
4405
+ (argument) => argument.type !== "pair" && recordFieldsOf(argument, facts).length === 0
4406
+ );
4407
+ }
4408
+ function columnsAskedFor(call, pattern, facts) {
4296
4409
  if (!(pattern.columnArguments ?? []).includes(methodOf(call))) {
4297
4410
  return [];
4298
4411
  }
4299
- return argumentsOf2(call).filter((argument) => argument.type === "simple_symbol").map(bareName);
4412
+ return argumentsOf2(call).map((argument) => stringValueOf(argument, facts)).filter((column) => column !== null);
4300
4413
  }
4301
- function primaryKeySelector(call, pattern) {
4414
+ function primaryKeySelector(call, pattern, facts) {
4302
4415
  const byPrimaryKey = pattern.byPrimaryKey;
4303
4416
  if (byPrimaryKey === void 0) {
4304
4417
  return [];
4305
4418
  }
4306
- const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call);
4419
+ const picks = byPrimaryKey.methods.includes(methodOf(call)) && hasPositionalArgument(call, facts);
4307
4420
  return picks ? [byPrimaryKey.column] : [];
4308
4421
  }
4309
- function selectorOf(call, pattern) {
4422
+ function selectorOf(call, pattern, facts) {
4310
4423
  const picked = [];
4311
4424
  for (const link of chainLinks(call)) {
4312
- picked.push(...primaryKeySelector(link, pattern));
4425
+ picked.push(...primaryKeySelector(link, pattern, facts));
4313
4426
  if (pattern.reads.includes(methodOf(link))) {
4314
- picked.push(...keywordKeys(link));
4427
+ picked.push(...keywordKeys(link, facts));
4315
4428
  }
4316
4429
  }
4317
4430
  return [...new Set(picked)];
4318
4431
  }
4319
- function fieldsOf(call, pattern, kind) {
4432
+ function fieldsOf(call, pattern, kind, facts) {
4320
4433
  if (kind === "write") {
4321
- return [...new Set(keywordKeys(call))];
4434
+ return [...new Set(keywordKeys(call, facts))];
4322
4435
  }
4323
4436
  return [
4324
4437
  ...new Set(
4325
- chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern))
4438
+ chainLinks(call).flatMap((link) => columnsAskedFor(link, pattern, facts))
4326
4439
  )
4327
4440
  ];
4328
4441
  }
@@ -4414,8 +4527,8 @@ function modelCallEffects(call, file, options, enclosing) {
4414
4527
  target.container,
4415
4528
  pattern,
4416
4529
  kind,
4417
- selectorOf(worked, pattern),
4418
- fieldsOf(worked, pattern, kind)
4530
+ selectorOf(worked, pattern, options.facts),
4531
+ fieldsOf(worked, pattern, kind, options.facts)
4419
4532
  )
4420
4533
  ];
4421
4534
  }
@@ -4735,7 +4848,8 @@ async function controllerActionUnits(pattern, pack, info, ownBlocks, fileBlocks,
4735
4848
  filter,
4736
4849
  pattern,
4737
4850
  displayPath,
4738
- bodyOfMethod(filter.method, filter.file, options)
4851
+ bodyOfMethod(filter.method, filter.file, options),
4852
+ options.facts
4739
4853
  );
4740
4854
  units.push(raw);
4741
4855
  options.onReachSeed?.(raw, {
@@ -4826,7 +4940,8 @@ function buildControllerActionUnit(pack, pattern, controllerQualifiedName, actio
4826
4940
  method,
4827
4941
  pattern,
4828
4942
  body.effects ?? [],
4829
- body.extraEffects
4943
+ body.extraEffects,
4944
+ { facts: bodyRead.facts }
4830
4945
  );
4831
4946
  return {
4832
4947
  identity: {
@@ -4956,7 +5071,8 @@ function bodyOfMethod(method, file, bodyRead = {}) {
4956
5071
  const effects = invocationEffects(
4957
5072
  method,
4958
5073
  bodyRead.inheritedMethods,
4959
- EVERY_ARGLESS_CALL
5074
+ EVERY_ARGLESS_CALL,
5075
+ bodyRead.facts
4960
5076
  );
4961
5077
  const storage = bodyRead.storage;
4962
5078
  const extra = [
@@ -5694,6 +5810,7 @@ function parseWith(language, source) {
5694
5810
  import fs3 from "fs";
5695
5811
  import path4 from "path";
5696
5812
  import {
5813
+ boundaryKey,
5697
5814
  disambiguateSummaryIds,
5698
5815
  linkCallsToSummaries,
5699
5816
  placeArgTargets as placeArgTargets2,
@@ -5714,6 +5831,7 @@ import {
5714
5831
  runDigest,
5715
5832
  stampModuleImports
5716
5833
  } from "@suss/extractor";
5834
+ import { addPackWords } from "@suss/resolution";
5717
5835
 
5718
5836
  // src/diagnostics.ts
5719
5837
  import {
@@ -6494,20 +6612,41 @@ function associationCallsIn(packs) {
6494
6612
  )
6495
6613
  );
6496
6614
  }
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
- }
6615
+ function packWordsOf(packs) {
6616
+ return {
6617
+ givesBackOne: packs.flatMap(
6618
+ (pack) => (pack.storage ?? []).flatMap(
6619
+ (pattern) => pattern.baseClasses.flatMap(
6620
+ (base) => pattern.givesBack.map((method) => ({ base, method }))
6621
+ )
6622
+ )
6623
+ )
6624
+ };
6625
+ }
6626
+ function factsForFile(options) {
6627
+ const db = new Database2();
6628
+ const bodyBlocks = bodyBlocksIn(options.packs);
6629
+ emitValueFacts(db, options.file, options.root, bodyBlocks);
6630
+ emitConstantBindings(db, [
6631
+ collectFileConstants(
6632
+ options.file,
6633
+ options.root,
6634
+ associationCallsIn(options.packs),
6635
+ inflectionsIn(options.packs)
6636
+ )
6637
+ ]);
6638
+ emitRequireFacts(db, options.file, options.root, /* @__PURE__ */ new Set([options.file]));
6639
+ bindEvaluator(db, {
6640
+ files: [{ file: options.file, root: options.root }],
6641
+ definitions: new Map(methodDefinitionsIn(options.file, options.root))
6642
+ });
6643
+ addPackWords(db, packWordsOf(options.packs));
6644
+ return db;
6507
6645
  }
6508
6646
  function alreadyDiscovered(seen, raw) {
6509
6647
  const reported = raw.identity.exportPath?.join(".") ?? raw.identity.name;
6510
- const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}`;
6648
+ const boundary = raw.boundaryBinding === null ? "" : boundaryKey(raw.boundaryBinding) ?? "";
6649
+ const key = `${raw.identity.file}::${reported}::${raw.identity.range.start}::${boundary}`;
6511
6650
  if (seen.has(key)) {
6512
6651
  return true;
6513
6652
  }
@@ -6574,7 +6713,7 @@ async function extractRubyProject(options) {
6574
6713
  emitRequireFacts(db, file, root, known);
6575
6714
  }
6576
6715
  bindEvaluator(db, { files: parsed, definitions });
6577
- emitStorageFacts(db, options.packs);
6716
+ addPackWords(db, packWordsOf(options.packs));
6578
6717
  return readDynamicNames(
6579
6718
  db,
6580
6719
  new Map(parsed.map(({ file, root }) => [file, root]))
@@ -6847,6 +6986,10 @@ function displayNameOf(node) {
6847
6986
  function width(node) {
6848
6987
  return node.endIndex - node.startIndex;
6849
6988
  }
6989
+ function askedKey(value) {
6990
+ const enclosing = enclosingMethod2(value.node);
6991
+ return calleeKeyOf(value.file, value.node, enclosing) ?? readKey(value.file, value.node, enclosing);
6992
+ }
6850
6993
  var RubyWhySession = class {
6851
6994
  root;
6852
6995
  db = new Database3();
@@ -6922,7 +7065,7 @@ var RubyWhySession = class {
6922
7065
  return explainResolvedKey({
6923
7066
  db: this.db,
6924
7067
  rules: WITNESS_RULES,
6925
- key: readKey(value.file, value.node, enclosingMethod2(value.node)),
7068
+ key: askedKey(value),
6926
7069
  locate: (key) => this.locate(key),
6927
7070
  displayPath: (key) => this.displayPath(key),
6928
7071
  ...options
@@ -7039,6 +7182,7 @@ export {
7039
7182
  emitValueFacts,
7040
7183
  evaluatedValue,
7041
7184
  extractRubyProject,
7185
+ factsForFile,
7042
7186
  field,
7043
7187
  findRubyFiles,
7044
7188
  graphqlTypeNameFromQualified,