@suss/adapter-python 0.31.2 → 0.33.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
@@ -29,6 +29,12 @@ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([
29
29
  "lambda",
30
30
  "class_definition"
31
31
  ]);
32
+ function isModule(node) {
33
+ return node.type === "module";
34
+ }
35
+ function runsAtModuleLoad(node) {
36
+ return !LATER_BODY_TYPES.has(node.type) && node.type !== "decorator";
37
+ }
32
38
  function enclosingFunction(node) {
33
39
  let current = node.parent;
34
40
  while (current !== null) {
@@ -724,6 +730,15 @@ import {
724
730
  function resolveCalls(db, callKeys) {
725
731
  askResolution(db, callKeys);
726
732
  }
733
+ function resolveEnvObjects(db, objectKeys) {
734
+ askResolution(db, objectKeys, "wantedEnvObject");
735
+ }
736
+ function resolvedFunctions(db, key) {
737
+ return [
738
+ ...db.lookup("wantedResolves", 0, key),
739
+ ...db.lookup("wantedGivesBack", 0, key)
740
+ ].map((row) => String(row[1]));
741
+ }
727
742
  function writtenValueUnder(db, key, site) {
728
743
  if (askResolutionUnder(db, [[key, site]]) === "abandoned") {
729
744
  return null;
@@ -742,6 +757,13 @@ function writtenValuesOf(db, key) {
742
757
  resolveCalls(db, [key]);
743
758
  return sharedWrittenValuesOf(db, key, (keys) => resolveCalls(db, keys));
744
759
  }
760
+ function settleWrittenValues(db, keys) {
761
+ if (keys.length === 0) {
762
+ return;
763
+ }
764
+ resolveCalls(db, keys);
765
+ writtenValuesByKey(db, keys, (behind) => resolveCalls(db, behind));
766
+ }
745
767
  function originsOf(db, nameKey2) {
746
768
  askResolution(db, [nameKey2], "wantedOrigin");
747
769
  return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey2).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
@@ -936,31 +958,46 @@ function emitCall(emitter, call) {
936
958
  if (!emitter.insideMethod) {
937
959
  add(emitter, "callOutsideMethod", callKey);
938
960
  }
961
+ for (const argument of callArguments(call)) {
962
+ if (argument.kind === "keyword") {
963
+ add(
964
+ emitter,
965
+ "callKeywordArg",
966
+ callKey,
967
+ argument.name,
968
+ valueKey(emitter, argument.node)
969
+ );
970
+ continue;
971
+ }
972
+ add(
973
+ emitter,
974
+ "callArg",
975
+ callKey,
976
+ String(argument.position),
977
+ valueKey(emitter, argument.node)
978
+ );
979
+ }
980
+ }
981
+ function callArguments(call) {
982
+ const args = field(call, "arguments");
983
+ if (args === null) {
984
+ return [];
985
+ }
986
+ const written = [];
939
987
  let position = 0;
940
988
  for (const argument of children(args)) {
941
989
  if (argument.type === "keyword_argument") {
942
990
  const name = field(argument, "name");
943
991
  const value = field(argument, "value");
944
992
  if (name !== null && value !== null) {
945
- add(
946
- emitter,
947
- "callKeywordArg",
948
- callKey,
949
- name.text,
950
- valueKey(emitter, value)
951
- );
993
+ written.push({ kind: "keyword", name: name.text, node: value });
952
994
  }
953
995
  continue;
954
996
  }
955
- add(
956
- emitter,
957
- "callArg",
958
- callKey,
959
- String(position),
960
- valueKey(emitter, argument)
961
- );
997
+ written.push({ kind: "positional", position, node: argument });
962
998
  position += 1;
963
999
  }
1000
+ return written;
964
1001
  }
965
1002
  function valueKey(emitter, value) {
966
1003
  if (value.type !== "identifier") {
@@ -1027,9 +1064,51 @@ function walkExpressions(emitter, node, visit) {
1027
1064
  walkExpressions(emitter, child, visit);
1028
1065
  }
1029
1066
  }
1067
+ var WRITTEN_KEY_TYPES = /* @__PURE__ */ new Set(["string", "integer", "concatenated_string"]);
1068
+ function emitKeyedRead(emitter, site, container, key) {
1069
+ if (WRITTEN_KEY_TYPES.has(key.type)) {
1070
+ return;
1071
+ }
1072
+ add(
1073
+ emitter,
1074
+ "readsKeyed",
1075
+ nodeId(emitter.filePath, site),
1076
+ valueKey(emitter, container),
1077
+ valueKey(emitter, key)
1078
+ );
1079
+ }
1080
+ function singleSubscript(node) {
1081
+ const written = fields(node, "subscript");
1082
+ return written.length === 1 ? written[0] ?? null : null;
1083
+ }
1084
+ function mappingGetRead(call) {
1085
+ const callee = field(call, "function");
1086
+ if (callee === null || callee.type !== "attribute") {
1087
+ return null;
1088
+ }
1089
+ const container = field(callee, "object");
1090
+ if (container === null || field(callee, "attribute")?.text !== "get") {
1091
+ return null;
1092
+ }
1093
+ const first = callArguments(call).find(
1094
+ (argument) => argument.kind === "positional" && argument.position === 0
1095
+ );
1096
+ return first === void 0 ? null : { container, key: first.node };
1097
+ }
1030
1098
  function emitExpressionFact(emitter, child) {
1031
1099
  if (child.type === "call") {
1032
1100
  emitCall(emitter, child);
1101
+ const mapping = mappingGetRead(child);
1102
+ if (mapping !== null) {
1103
+ emitKeyedRead(emitter, child, mapping.container, mapping.key);
1104
+ }
1105
+ }
1106
+ if (child.type === "subscript") {
1107
+ const index = singleSubscript(child);
1108
+ const container = field(child, "value");
1109
+ if (index !== null && container !== null) {
1110
+ emitKeyedRead(emitter, child, container, index);
1111
+ }
1033
1112
  }
1034
1113
  if (child.type === "dictionary") {
1035
1114
  emitDictionary(emitter, child);
@@ -1097,6 +1176,9 @@ function emitFunctionFacts(emitter, fn, classKey) {
1097
1176
  return funcKey;
1098
1177
  }
1099
1178
  emitNestedDefinitions(inside, body);
1179
+ if (fn.type === "lambda") {
1180
+ emitExpressionFact(inside, body);
1181
+ }
1100
1182
  let statesReturn = false;
1101
1183
  const stores = /* @__PURE__ */ new Map();
1102
1184
  walkExpressions(inside, body, (child) => {
@@ -2476,6 +2558,10 @@ function writtenNodeOf(node, db) {
2476
2558
  const bound = db === void 0 ? void 0 : projects.get(db);
2477
2559
  return bound?.context.writtenTo(node) ?? null;
2478
2560
  }
2561
+ function resolutionKeyOf(node, db) {
2562
+ const bound = db === void 0 ? void 0 : projects.get(db);
2563
+ return bound?.keyOf(node) ?? null;
2564
+ }
2479
2565
  var NOTHING_BUILT = { type: "noCall" };
2480
2566
  function constructionBehind(node, db) {
2481
2567
  if (db === void 0) {
@@ -2510,8 +2596,17 @@ function askWrittenValues(nodes, db) {
2510
2596
  if (bound === void 0) {
2511
2597
  return;
2512
2598
  }
2513
- const keys = nodes.map((node) => bound.keyOf(node)).filter((key) => key !== null);
2514
- resolveCalls(db, keys);
2599
+ settleWrittenValues(db, resolutionKeysOf(nodes, bound));
2600
+ }
2601
+ function resolutionKeysOf(nodes, bound) {
2602
+ const keys = /* @__PURE__ */ new Set();
2603
+ for (const node of nodes) {
2604
+ const key = bound.keyOf(node);
2605
+ if (key !== null) {
2606
+ keys.add(key);
2607
+ }
2608
+ }
2609
+ return [...keys];
2515
2610
  }
2516
2611
  function evaluatedValue(node, db, site) {
2517
2612
  const evaluator = evaluatorFor(node, db);
@@ -2579,7 +2674,7 @@ function projectOver(db, nodes) {
2579
2674
  filesByRoot.set(entry.root.id, entry);
2580
2675
  rootsByFile.set(entry.file, entry.root);
2581
2676
  }
2582
- const keyOf2 = (node) => {
2677
+ const keyOf3 = (node) => {
2583
2678
  const entry = filesByRoot.get(node.tree.rootNode.id);
2584
2679
  if (entry === void 0) {
2585
2680
  return null;
@@ -2588,7 +2683,7 @@ function projectOver(db, nodes) {
2588
2683
  };
2589
2684
  const context = {
2590
2685
  writtenTo: (node, site) => {
2591
- const key = keyOf2(node);
2686
+ const key = keyOf3(node);
2592
2687
  if (key === null) {
2593
2688
  return null;
2594
2689
  }
@@ -2597,17 +2692,17 @@ function projectOver(db, nodes) {
2597
2692
  },
2598
2693
  callable: (call) => {
2599
2694
  const callee = field(call, "function");
2600
- const key = callee === null ? null : keyOf2(callee);
2695
+ const key = callee === null ? null : keyOf3(callee);
2601
2696
  if (key === null) {
2602
2697
  return null;
2603
2698
  }
2604
2699
  resolveCalls(db, [key]);
2605
- const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2606
- const settled = resolved.length === 1 ? resolved[0] : void 0;
2700
+ const resolved = db.lookup("wantedResolves", 0, key);
2701
+ const settled = resolved.length === 1 ? String(resolved[0]?.[1]) : void 0;
2607
2702
  return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
2608
2703
  }
2609
2704
  };
2610
- return { keyOf: keyOf2, roots: rootsByFile, context };
2705
+ return { keyOf: keyOf3, roots: rootsByFile, context };
2611
2706
  }
2612
2707
 
2613
2708
  // src/decorators.ts
@@ -3245,6 +3340,18 @@ function bodyCalls(node, found = []) {
3245
3340
  }
3246
3341
  return found;
3247
3342
  }
3343
+ function moduleLoadCalls(node, found = []) {
3344
+ for (const child of node.namedChildren) {
3345
+ if (child === null || !runsAtModuleLoad(child)) {
3346
+ continue;
3347
+ }
3348
+ if (child.type === "call") {
3349
+ found.push(child);
3350
+ }
3351
+ moduleLoadCalls(child, found);
3352
+ }
3353
+ return found;
3354
+ }
3248
3355
  function enclosingStatement(call, body) {
3249
3356
  let current = call;
3250
3357
  while (current !== null && current.parent !== null) {
@@ -3269,15 +3376,18 @@ function argOf(node, facts) {
3269
3376
  }
3270
3377
  return { kind: "identifier", name: node.text };
3271
3378
  }
3272
- function argsOf(call, facts) {
3379
+ function argumentNodes(call) {
3273
3380
  const args = field(call, "arguments");
3274
3381
  if (args === null) {
3275
3382
  return [];
3276
3383
  }
3277
3384
  return args.namedChildren.filter((child) => child !== null).map(
3278
- (child) => child.type === "keyword_argument" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3385
+ (child) => child.type === "keyword_argument" ? field(child, "value") ?? child : child
3279
3386
  );
3280
3387
  }
3388
+ function argsOf(call, facts) {
3389
+ return argumentNodes(call).map((node) => argOf(node, facts));
3390
+ }
3281
3391
  function calleeText(call) {
3282
3392
  return field(call, "function")?.text ?? call.text;
3283
3393
  }
@@ -3286,7 +3396,40 @@ function invocationEffects(definitionNode, facts) {
3286
3396
  if (body === null) {
3287
3397
  return [];
3288
3398
  }
3289
- const calls = bodyCalls(body).sort((a, b) => a.endIndex - b.endIndex);
3399
+ return invocationEffectsIn(body, bodyCalls(body), facts);
3400
+ }
3401
+ function moduleLoadInvocationEffects(moduleNode, facts) {
3402
+ return invocationEffectsIn(moduleNode, moduleLoadCalls(moduleNode), facts);
3403
+ }
3404
+ var ASKED_ABOUT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "call"]);
3405
+ function askedNodesUnder(node, found = []) {
3406
+ if (ASKED_ABOUT_TYPES.has(node.type)) {
3407
+ found.push(node);
3408
+ }
3409
+ for (const child of node.namedChildren) {
3410
+ if (child !== null && !NESTED_DEFINITION_TYPES.has(child.type)) {
3411
+ askedNodesUnder(child, found);
3412
+ }
3413
+ }
3414
+ return found;
3415
+ }
3416
+ function bodyValueNodes(definitionNode) {
3417
+ const body = field(definitionNode, "body");
3418
+ return body === null ? [] : askedNodesUnder(body);
3419
+ }
3420
+ function effectValueNodes(call, gating) {
3421
+ const found = argumentNodes(call).flatMap((node) => askedNodesUnder(node));
3422
+ for (const path6 of gating ?? []) {
3423
+ for (const condition of path6) {
3424
+ if (condition.expression !== null) {
3425
+ askedNodesUnder(condition.expression, found);
3426
+ }
3427
+ }
3428
+ }
3429
+ return found;
3430
+ }
3431
+ function invocationEffectsIn(body, written, facts) {
3432
+ const calls = [...written].sort((a, b) => a.endIndex - b.endIndex);
3290
3433
  if (calls.length === 0) {
3291
3434
  return [];
3292
3435
  }
@@ -3308,10 +3451,17 @@ function invocationEffects(definitionNode, facts) {
3308
3451
  },
3309
3452
  statements
3310
3453
  );
3311
- return calls.map((call) => {
3454
+ const gatingOf = calls.map((call) => {
3312
3455
  const statement = statementOf3.get(call.id);
3456
+ return statement === void 0 ? void 0 : enumerated.byTerminal.get(statement);
3457
+ });
3458
+ askWrittenValues(
3459
+ calls.flatMap((call, index) => effectValueNodes(call, gatingOf[index])),
3460
+ facts
3461
+ );
3462
+ return calls.map((call, index) => {
3313
3463
  const conditions = sharedGatingConditions(
3314
- statement === void 0 ? void 0 : enumerated.byTerminal.get(statement),
3464
+ gatingOf[index],
3315
3465
  (condition) => predicateOf(condition, facts)
3316
3466
  );
3317
3467
  return {
@@ -3342,6 +3492,22 @@ function clientCallUnits(root, module, pack, pattern, options) {
3342
3492
  }
3343
3493
  return units;
3344
3494
  }
3495
+ function clientCallReceivers(root, module) {
3496
+ const found = [];
3497
+ for (const definition of functionDefinitions(root)) {
3498
+ for (const call of bodyCalls(definition)) {
3499
+ const callee = field(call, "function");
3500
+ if (callee === null || originOf(callee, module) !== null) {
3501
+ continue;
3502
+ }
3503
+ const object = field(callee, "object");
3504
+ if (object !== null && field(callee, "attribute") !== null) {
3505
+ found.push(object);
3506
+ }
3507
+ }
3508
+ }
3509
+ return found;
3510
+ }
3345
3511
  function functionDefinitions(node, found = []) {
3346
3512
  for (const child of children(node)) {
3347
3513
  if (isFunction(child)) {
@@ -3550,14 +3716,22 @@ import { runtimeConfigBinding } from "@suss/behavioral-ir";
3550
3716
  import { SKIP_CHILDREN, walkDescendants } from "@suss/extractor";
3551
3717
  var PYTHON_ENV_RECOGNITION = "python-env";
3552
3718
  var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
3553
- function envReadEffects(root, module) {
3554
- const effects = [];
3719
+ function envReadEffects(root, module, facts) {
3720
+ const slots = [];
3721
+ const readsHelpers = facts !== void 0 && sitesByDb.has(facts);
3555
3722
  const startScope = module.scopeFor.get(root.id) ?? module.moduleScope;
3556
3723
  walkDescendants(root, startScope, {
3557
3724
  at: (node, scope) => {
3558
- const read = envReadAt(node, scope);
3559
- if (read !== null) {
3560
- effects.push(configReadEffect(read));
3725
+ const syntax = envReadSyntaxAt(node, scope);
3726
+ if (syntax !== null) {
3727
+ const name = stringLiteralValue(syntax.name);
3728
+ if (name !== null) {
3729
+ slots.push({ read: { name, defaulted: syntax.defaulted } });
3730
+ }
3731
+ return;
3732
+ }
3733
+ if (readsHelpers && node.type === "call") {
3734
+ slots.push({ call: node });
3561
3735
  }
3562
3736
  },
3563
3737
  into: (node, scope) => {
@@ -3567,7 +3741,16 @@ function envReadEffects(root, module) {
3567
3741
  return module.scopeFor.get(node.id) ?? scope;
3568
3742
  }
3569
3743
  });
3570
- return effects;
3744
+ const throughHelpers = helperReadsByCall(
3745
+ slots.flatMap((slot) => "call" in slot ? [slot.call] : []),
3746
+ facts
3747
+ );
3748
+ return slots.flatMap((slot) => {
3749
+ if ("read" in slot) {
3750
+ return [configReadEffect(slot.read)];
3751
+ }
3752
+ return throughHelpers.get(slot.call.id) ?? [];
3753
+ });
3571
3754
  }
3572
3755
  function configReadEffect(read) {
3573
3756
  return {
@@ -3585,7 +3768,7 @@ function configReadEffect(read) {
3585
3768
  }
3586
3769
  };
3587
3770
  }
3588
- function envReadAt(node, scope) {
3771
+ function envReadSyntaxAt(node, scope) {
3589
3772
  if (node.type === "subscript") {
3590
3773
  return subscriptRead(node, scope);
3591
3774
  }
@@ -3600,11 +3783,10 @@ function subscriptRead(node, scope) {
3600
3783
  if (value === null || index === null || !isEnviron(value, scope)) {
3601
3784
  return null;
3602
3785
  }
3603
- const name = stringLiteralValue(index);
3604
- if (name === null || isAssignedTo(node)) {
3786
+ if (isAssignedTo(node)) {
3605
3787
  return null;
3606
3788
  }
3607
- return { name, defaulted: isDefaultedAt(node) };
3789
+ return { name: index, defaulted: isDefaultedAt(node) };
3608
3790
  }
3609
3791
  function isAssignedTo(node) {
3610
3792
  const parent = node.parent;
@@ -3618,22 +3800,20 @@ function isAssignedTo(node) {
3618
3800
  }
3619
3801
  function callRead(node, scope) {
3620
3802
  const callee = field(node, "function");
3621
- const args = field(node, "arguments");
3622
- if (callee === null || args === null || !isEnvGetter(callee, scope)) {
3803
+ if (callee === null || !isEnvGetter(callee, scope)) {
3623
3804
  return null;
3624
3805
  }
3625
- const positional = args.namedChildren.filter(
3626
- (arg) => arg !== null && arg.type !== "keyword_argument"
3806
+ const written = callArguments(node);
3807
+ const name = written.find(
3808
+ (argument) => argument.kind === "positional" && argument.position === 0
3627
3809
  );
3628
- const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
3629
- if (name === null) {
3810
+ if (name === void 0) {
3630
3811
  return null;
3631
3812
  }
3632
- const keywords = args.namedChildren.filter(
3633
- (arg) => arg !== null && arg.type === "keyword_argument"
3813
+ const hasDefault = written.some(
3814
+ (argument) => argument.kind === "positional" && argument.position === 1 || argument.kind === "keyword" && argument.name === "default"
3634
3815
  );
3635
- const hasDefault = positional.length > 1 || keywords.some((arg) => field(arg, "name")?.text === "default");
3636
- return { name, defaulted: hasDefault || isDefaultedAt(node) };
3816
+ return { name: name.node, defaulted: hasDefault || isDefaultedAt(node) };
3637
3817
  }
3638
3818
  function isEnvGetter(callee, scope) {
3639
3819
  if (callee.type === "attribute") {
@@ -3690,6 +3870,194 @@ function isDefaultedAt(node) {
3690
3870
  }
3691
3871
  return false;
3692
3872
  }
3873
+ function envFactsIn(filePath, root, module) {
3874
+ const found = { sites: [], objects: [] };
3875
+ walkDescendants(root, module.moduleScope, {
3876
+ at: (node, scope) => {
3877
+ if (isEnviron(node, scope) && handsOnward(node)) {
3878
+ found.objects.push(keyOf(filePath, node));
3879
+ }
3880
+ const syntax = envReadSyntaxAt(node, scope);
3881
+ if (syntax === null || stringLiteralValue(syntax.name) !== null) {
3882
+ return;
3883
+ }
3884
+ found.sites.push({
3885
+ site: nodeId(filePath, node),
3886
+ nameKey: keyOf(filePath, syntax.name),
3887
+ defaulted: syntax.defaulted,
3888
+ functionKey: functionReadKey(filePath, node, scope)
3889
+ });
3890
+ },
3891
+ into: (node, scope) => module.scopeFor.get(node.id) ?? scope
3892
+ });
3893
+ return found;
3894
+ }
3895
+ var keyOf = (filePath, node) => readKey(filePath, node, enclosingFunction(node));
3896
+ function functionReadKey(filePath, node, scope) {
3897
+ if (node.type !== "call") {
3898
+ return null;
3899
+ }
3900
+ const callee = field(node, "function");
3901
+ if (callee === null || isEnviron(field(callee, "object") ?? callee, scope)) {
3902
+ return null;
3903
+ }
3904
+ return keyOf(filePath, callee);
3905
+ }
3906
+ function handsOnward(node) {
3907
+ const parent = node.parent;
3908
+ if (parent === null) {
3909
+ return true;
3910
+ }
3911
+ if (parent.type === "subscript" && field(parent, "value")?.id === node.id) {
3912
+ return !writesTheKey(field(parent, "subscript"));
3913
+ }
3914
+ if (parent.type === "attribute" && field(parent, "object")?.id === node.id) {
3915
+ const call = parent.parent;
3916
+ return field(parent, "attribute")?.text === "get" && call !== null && call.type === "call" && !writesTheKey(firstArgumentOf(call));
3917
+ }
3918
+ return true;
3919
+ }
3920
+ var writesTheKey = (key) => key !== null && stringLiteralValue(key) !== null;
3921
+ function firstArgumentOf(call) {
3922
+ const first = callArguments(call).find(
3923
+ (argument) => argument.kind === "positional" && argument.position === 0
3924
+ );
3925
+ return first?.node ?? null;
3926
+ }
3927
+ var sitesByDb = /* @__PURE__ */ new WeakMap();
3928
+ function bindEnvFacts(db, found) {
3929
+ const sites = found.flatMap((one) => one.sites);
3930
+ const objects = found.flatMap((one) => one.objects);
3931
+ if (sites.length === 0 && objects.length === 0) {
3932
+ return;
3933
+ }
3934
+ let index = sitesByDb.get(db);
3935
+ if (index === void 0) {
3936
+ index = { byId: /* @__PURE__ */ new Map(), objects: /* @__PURE__ */ new Set(), sitesByParameter: null };
3937
+ sitesByDb.set(db, index);
3938
+ }
3939
+ for (const object of objects) {
3940
+ db.add("environmentObject", [object]);
3941
+ index.objects.add(object);
3942
+ }
3943
+ for (const site of sites) {
3944
+ index.byId.set(site.site, site);
3945
+ if (site.functionKey === null) {
3946
+ continue;
3947
+ }
3948
+ db.add("environmentObject", [site.functionKey]);
3949
+ db.add("readsKeyed", [site.site, site.functionKey, site.nameKey]);
3950
+ index.objects.add(site.functionKey);
3951
+ }
3952
+ }
3953
+ function sitesEachParameterNames(db, index) {
3954
+ if (index.sitesByParameter !== null) {
3955
+ return index.sitesByParameter;
3956
+ }
3957
+ resolveEnvObjects(db, [...index.objects]);
3958
+ const found = /* @__PURE__ */ new Map();
3959
+ for (const row of db.facts("wantedParamNamesEnv")) {
3960
+ const parameter = String(row[0]);
3961
+ const named = found.get(parameter) ?? [];
3962
+ named.push(String(row[1]));
3963
+ found.set(parameter, named);
3964
+ }
3965
+ index.sitesByParameter = found;
3966
+ return found;
3967
+ }
3968
+ function helperReadsByCall(calls, db) {
3969
+ const found = /* @__PURE__ */ new Map();
3970
+ const index = db === void 0 ? void 0 : sitesByDb.get(db);
3971
+ if (db === void 0 || index === void 0 || calls.length === 0) {
3972
+ return found;
3973
+ }
3974
+ const named = sitesEachParameterNames(db, index);
3975
+ if (named.size === 0) {
3976
+ return found;
3977
+ }
3978
+ const calleeKeys = /* @__PURE__ */ new Map();
3979
+ for (const call of calls) {
3980
+ const callee = field(call, "function");
3981
+ const key = callee === null ? null : resolutionKeyOf(callee, db);
3982
+ if (key !== null) {
3983
+ calleeKeys.set(call.id, key);
3984
+ }
3985
+ }
3986
+ if (calleeKeys.size === 0) {
3987
+ return found;
3988
+ }
3989
+ resolveCalls(db, [...new Set(calleeKeys.values())]);
3990
+ for (const call of calls) {
3991
+ const key = calleeKeys.get(call.id);
3992
+ if (key === void 0) {
3993
+ continue;
3994
+ }
3995
+ const argumentAt2 = argumentsByParameter(db, call, key);
3996
+ if (argumentAt2.size === 0) {
3997
+ continue;
3998
+ }
3999
+ const reads = readsThroughHelper(db, call, argumentAt2, index.byId, named);
4000
+ if (reads.length > 0) {
4001
+ found.set(call.id, reads);
4002
+ }
4003
+ }
4004
+ return found;
4005
+ }
4006
+ function argumentsByParameter(db, call, calleeKey) {
4007
+ const written = callArguments(call);
4008
+ if (written.length === 0) {
4009
+ return /* @__PURE__ */ new Map();
4010
+ }
4011
+ const positional = /* @__PURE__ */ new Map();
4012
+ const byName = /* @__PURE__ */ new Map();
4013
+ for (const argument of written) {
4014
+ if (argument.kind === "keyword") {
4015
+ byName.set(argument.name, argument.node);
4016
+ continue;
4017
+ }
4018
+ positional.set(String(argument.position), argument.node);
4019
+ }
4020
+ const argumentAt2 = /* @__PURE__ */ new Map();
4021
+ for (const callee of resolvedFunctions(db, calleeKey)) {
4022
+ for (const row of db.lookup("paramOf", 0, callee)) {
4023
+ const at = positional.get(String(row[1]));
4024
+ if (at !== void 0) {
4025
+ argumentAt2.set(String(row[2]), at);
4026
+ }
4027
+ }
4028
+ for (const row of db.lookup("paramNamed", 0, callee)) {
4029
+ const at = byName.get(String(row[1]));
4030
+ if (at !== void 0) {
4031
+ argumentAt2.set(String(row[2]), at);
4032
+ }
4033
+ }
4034
+ }
4035
+ return argumentAt2;
4036
+ }
4037
+ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
4038
+ const reads = /* @__PURE__ */ new Map();
4039
+ for (const [parameter, argument] of argumentAt2) {
4040
+ const sites = named.get(parameter);
4041
+ if (sites === void 0) {
4042
+ continue;
4043
+ }
4044
+ const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
4045
+ if (name === null) {
4046
+ continue;
4047
+ }
4048
+ for (const id of sites) {
4049
+ const defaulted = siteById.get(id)?.defaulted ?? false;
4050
+ reads.set(name, (reads.get(name) ?? true) && defaulted);
4051
+ }
4052
+ }
4053
+ if (reads.size === 0) {
4054
+ return [];
4055
+ }
4056
+ const defaultedAtCall = isDefaultedAt(call);
4057
+ return [...reads].map(
4058
+ ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall })
4059
+ );
4060
+ }
3693
4061
 
3694
4062
  // src/paths/raisedResponses.ts
3695
4063
  import { constantOf as constantOf3 } from "@suss/values";
@@ -3898,72 +4266,302 @@ function returnedBodyShape(statement) {
3898
4266
 
3899
4267
  // src/rawSql.ts
3900
4268
  import { storageBinding } from "@suss/ir-core";
3901
- import { readSqlAccess, sqlFromParts } from "@suss/sql";
3902
- function rawSqlEffects(calls, options) {
3903
- if (options.patterns.length === 0) {
3904
- return [];
4269
+ import { readSqlAccess, splitQualifiedTable, sqlFromParts } from "@suss/sql";
4270
+ import { force as force2 } from "@suss/values";
4271
+
4272
+ // src/receiverTypes.ts
4273
+ var UNDECLARED_MODULE = "builtins";
4274
+ function annotationTarget(annotation) {
4275
+ if (annotation.type === "type" && annotation.namedChildren[0]) {
4276
+ return annotationTarget(annotation.namedChildren[0]);
3905
4277
  }
3906
- const effects = [];
3907
- for (const call of calls) {
3908
- const pattern = patternFor(call, options);
3909
- if (pattern === void 0) {
3910
- continue;
4278
+ if (annotation.type === "binary_operator") {
4279
+ const named = [field(annotation, "left"), field(annotation, "right")].find(
4280
+ (side) => side !== null && side.type !== "none"
4281
+ );
4282
+ return named === void 0 || named === null ? null : annotationTarget(named);
4283
+ }
4284
+ if (annotation.type === "generic_type") {
4285
+ const outer = annotation.namedChildren[0];
4286
+ const first = genericTypeArgs(annotation)[0];
4287
+ if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4288
+ return annotationTarget(first);
3911
4289
  }
3912
- const statement = statementIn(call, options.facts);
3913
- if (statement === null) {
4290
+ return outer ?? null;
4291
+ }
4292
+ return annotation;
4293
+ }
4294
+ function typeNameOf(annotation) {
4295
+ const target = annotationTarget(annotation);
4296
+ if (target === null) {
4297
+ return null;
4298
+ }
4299
+ return target.type === "identifier" ? target.text : stringLiteralValue(target);
4300
+ }
4301
+ function firstInBody(node, read) {
4302
+ const here = read(node);
4303
+ if (here !== null) {
4304
+ return here;
4305
+ }
4306
+ for (const child of children(node)) {
4307
+ if (child.type === "function_definition" || child.type === "lambda") {
3914
4308
  continue;
3915
4309
  }
3916
- for (const access of readSqlAccess(statement, {
3917
- dialect: pattern.storageSystem
3918
- })) {
3919
- effects.push({
3920
- type: "interaction",
3921
- binding: storageBinding({
3922
- recognition: `python-${pattern.module}`,
3923
- storageSystem: pattern.storageSystem,
3924
- scope: "default",
3925
- container: access.table
3926
- }),
3927
- callee: field(call, "function")?.text ?? "",
3928
- interaction: {
3929
- class: "storage-access",
3930
- kind: access.kind,
3931
- fields: access.fields,
3932
- ...access.selector.length > 0 ? { selector: access.selector } : {},
3933
- operation: field(call, "function")?.text ?? ""
3934
- }
3935
- });
4310
+ const found = firstInBody(child, read);
4311
+ if (found !== null) {
4312
+ return found;
3936
4313
  }
3937
4314
  }
3938
- return effects;
4315
+ return null;
3939
4316
  }
3940
- function patternFor(call, options) {
4317
+ function statedBy(statement, name) {
4318
+ if (statement.type === "assignment") {
4319
+ const annotation = field(statement, "type");
4320
+ return field(statement, "left")?.text === name ? annotation : null;
4321
+ }
4322
+ if (statement.type === "as_pattern") {
4323
+ const alias = field(statement, "alias");
4324
+ const value = statement.namedChildren[0] ?? null;
4325
+ const callee = value?.type === "call" ? field(value, "function") : null;
4326
+ return alias?.text === name ? callee : null;
4327
+ }
4328
+ return null;
4329
+ }
4330
+ function statedTypeNode(name, from) {
4331
+ const fn = enclosingFunction(from);
4332
+ if (fn === null) {
4333
+ return null;
4334
+ }
4335
+ const params = field(fn, "parameters");
4336
+ for (const param of params === null ? [] : children(params)) {
4337
+ const info = parameterNameAndType(param);
4338
+ if (info?.name === name && info.typeNode !== null) {
4339
+ return info.typeNode;
4340
+ }
4341
+ }
4342
+ const body = field(fn, "body");
4343
+ return body === null ? null : firstInBody(body, (statement) => statedBy(statement, name));
4344
+ }
4345
+ function statedTypeName(name, from) {
4346
+ const stated = statedTypeNode(name, from);
4347
+ return stated === null ? null : typeNameOf(stated);
4348
+ }
4349
+ function statedOrigins(stated, options) {
4350
+ const name = typeNameOf(stated);
4351
+ const byRule = name === null ? [] : originsOf(options.facts, `${options.filePath}#${name}`);
4352
+ const target = annotationTarget(stated);
4353
+ const imported = target === null ? null : originOf(target, moduleOf(target));
4354
+ if (imported === null || imported.module === UNDECLARED_MODULE) {
4355
+ return byRule;
4356
+ }
4357
+ return [...byRule, imported];
4358
+ }
4359
+ function receiverTypeOrigins(receiver, options) {
4360
+ const stated = statedTypeNode(receiver.text, receiver);
4361
+ if (stated !== null) {
4362
+ return statedOrigins(stated, options);
4363
+ }
4364
+ const built = constructionBehind(receiver, options.facts);
4365
+ if (built.type === "oneCall") {
4366
+ return [built.construction.origin];
4367
+ }
4368
+ const key = nameKeyIn(
4369
+ options.filePath,
4370
+ enclosingFunction(receiver),
4371
+ receiver.text
4372
+ );
4373
+ return subjectConstructions(options.facts, [key]).get(key)?.origins ?? [];
4374
+ }
4375
+
4376
+ // src/rawSql.ts
4377
+ function rawSqlEffects(calls, options) {
4378
+ return matchesIn(calls, options).flatMap(
4379
+ (match) => match.accesses.flatMap((access) => effectFor(match, access))
4380
+ );
4381
+ }
4382
+ function rawSqlCallIds(calls, options) {
4383
+ return new Set(matchesIn(calls, options).map((match) => match.call.id));
4384
+ }
4385
+ function matchesIn(calls, options) {
4386
+ if (options.patterns.length === 0 && (options.clients ?? []).length === 0) {
4387
+ return [];
4388
+ }
4389
+ const matched = [];
4390
+ for (const call of calls) {
4391
+ const one = importedFunctionMatch(call, options) ?? clientMatch(call, options);
4392
+ if (one !== null) {
4393
+ matched.push(one);
4394
+ }
4395
+ }
4396
+ return matched;
4397
+ }
4398
+ function effectFor(match, access) {
4399
+ const operation = field(match.call, "function")?.text ?? "";
4400
+ return [
4401
+ {
4402
+ type: "interaction",
4403
+ binding: storageBinding({
4404
+ recognition: match.recognition,
4405
+ storageSystem: match.storageSystem,
4406
+ scope: access.qualifier[access.qualifier.length - 1] ?? NO_GROUP,
4407
+ container: access.table
4408
+ }),
4409
+ callee: operation,
4410
+ interaction: {
4411
+ class: "storage-access",
4412
+ kind: access.kind,
4413
+ fields: access.fields,
4414
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
4415
+ operation
4416
+ }
4417
+ }
4418
+ ];
4419
+ }
4420
+ var NO_GROUP = "default";
4421
+ function importedFunctionMatch(call, options) {
3941
4422
  const callee = field(call, "function");
3942
4423
  if (callee === null || callee.type !== "identifier") {
3943
- return void 0;
4424
+ return null;
3944
4425
  }
3945
4426
  const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
3946
4427
  if (from === void 0) {
3947
- return void 0;
4428
+ return null;
3948
4429
  }
3949
4430
  const module = String(from[1]);
3950
- return options.patterns.find(
3951
- (pattern) => pattern.module === module && pattern.functions.includes(callee.text)
4431
+ const pattern = options.patterns.find(
4432
+ (candidate) => candidate.module === module && candidate.functions.includes(callee.text)
3952
4433
  );
3953
- }
3954
- function statementIn(call, facts) {
4434
+ if (pattern === void 0) {
4435
+ return null;
4436
+ }
3955
4437
  const args = field(call, "arguments");
3956
4438
  const first = args?.namedChildren.find((child) => child !== null) ?? null;
3957
- if (first === null) {
4439
+ const statement = first === null ? null : statementAt(first, void 0, options.facts);
4440
+ if (statement === null) {
3958
4441
  return null;
3959
4442
  }
3960
- const parts = literalParts(evaluatedValue(first, facts));
4443
+ return {
4444
+ call,
4445
+ recognition: `python-${pattern.module}`,
4446
+ storageSystem: pattern.storageSystem,
4447
+ accesses: readSqlAccess(statement, { dialect: pattern.storageSystem })
4448
+ };
4449
+ }
4450
+ function clientMatch(call, options) {
4451
+ const callee = field(call, "function");
4452
+ if (callee === null || callee.type !== "attribute") {
4453
+ return null;
4454
+ }
4455
+ const method = field(callee, "attribute")?.text ?? "";
4456
+ const receiver = field(callee, "object");
4457
+ if (receiver === null) {
4458
+ return null;
4459
+ }
4460
+ for (const pattern of options.clients ?? []) {
4461
+ const accesses = accessesOf(call, method, pattern, options);
4462
+ if (accesses === null || !isClientOf(receiver, pattern, options)) {
4463
+ continue;
4464
+ }
4465
+ return {
4466
+ call,
4467
+ recognition: `python-${pattern.module}`,
4468
+ storageSystem: pattern.storageSystem,
4469
+ accesses
4470
+ };
4471
+ }
4472
+ return null;
4473
+ }
4474
+ function accessesOf(call, method, pattern, options) {
4475
+ const dialect = pattern.dialect ?? pattern.storageSystem;
4476
+ for (const takes of pattern.statements ?? []) {
4477
+ if (takes.method !== method) {
4478
+ continue;
4479
+ }
4480
+ const written = argumentNode(call, takes);
4481
+ const statement = written === null ? null : statementAt(written, takes.path, options.facts);
4482
+ return statement === null ? [] : readSqlAccess(statement, { dialect });
4483
+ }
4484
+ for (const takes of pattern.tables ?? []) {
4485
+ if (takes.method !== method) {
4486
+ continue;
4487
+ }
4488
+ const written = argumentNode(call, takes);
4489
+ const name = written === null ? null : stringValueOf(written, options.facts);
4490
+ const split = name === null ? null : splitQualifiedTable(name);
4491
+ return split === null ? [] : [{ ...split, kind: takes.kind, fields: [], selector: [] }];
4492
+ }
4493
+ return null;
4494
+ }
4495
+ function isClientOf(receiver, pattern, options) {
4496
+ return receiverOrigins(receiver, options).some(
4497
+ (origin) => origin.module === pattern.module && pattern.clientTypes.includes(origin.name)
4498
+ );
4499
+ }
4500
+ function receiverOrigins(receiver, options) {
4501
+ if (receiver.type === "identifier") {
4502
+ return receiverTypeOrigins(receiver, options);
4503
+ }
4504
+ return receiver.type === "call" ? handedBack(receiver, options) : [];
4505
+ }
4506
+ function handedBack(call, options) {
4507
+ const callee = field(call, "function");
4508
+ if (callee === null || callee.type !== "attribute") {
4509
+ return [];
4510
+ }
4511
+ const method = field(callee, "attribute")?.text ?? "";
4512
+ const inner = field(callee, "object");
4513
+ if (inner === null) {
4514
+ return [];
4515
+ }
4516
+ const found = [];
4517
+ for (const pattern of options.clients ?? []) {
4518
+ const handoff = (pattern.handsBack ?? []).find(
4519
+ (one) => one.method === method
4520
+ );
4521
+ if (handoff !== void 0 && isClientOf(inner, pattern, options)) {
4522
+ found.push({ module: handoff.module, name: handoff.name });
4523
+ }
4524
+ }
4525
+ return found;
4526
+ }
4527
+ function argumentNode(call, at) {
4528
+ const args = field(call, "arguments");
4529
+ const written = args === null ? [] : children(args);
4530
+ const named = written.find(
4531
+ (one) => one.type === "keyword_argument" && at.keyword !== void 0 && field(one, "name")?.text === at.keyword
4532
+ );
4533
+ const value = named === void 0 ? null : field(named, "value");
4534
+ if (value !== null) {
4535
+ return value;
4536
+ }
4537
+ if (at.argument === void 0) {
4538
+ return null;
4539
+ }
4540
+ return written.filter((one) => one.type !== "keyword_argument")[at.argument] ?? null;
4541
+ }
4542
+ function statementAt(node, path6, facts) {
4543
+ const reached = valueAt(evaluatedValue(node, facts), path6 ?? []);
4544
+ const parts = reached === null ? null : literalParts(reached);
3961
4545
  if (parts === null) {
3962
4546
  return null;
3963
4547
  }
3964
4548
  const statement = sqlFromParts(parts);
3965
4549
  return statement.trim() === "" ? null : statement;
3966
4550
  }
4551
+ function valueAt(value, path6) {
4552
+ let reached = value;
4553
+ for (const key of path6) {
4554
+ if (reached.kind !== "record") {
4555
+ return null;
4556
+ }
4557
+ const item = reached.fields.get(key);
4558
+ if (item === void 0) {
4559
+ return null;
4560
+ }
4561
+ reached = force2(item.value);
4562
+ }
4563
+ return reached;
4564
+ }
3967
4565
  function literalParts(value) {
3968
4566
  if (value.kind !== "string") {
3969
4567
  return null;
@@ -4061,78 +4659,7 @@ function receiverName(chain) {
4061
4659
  return null;
4062
4660
  }
4063
4661
  const object = field(callee, "object");
4064
- return object !== null && object.type === "identifier" ? object.text : null;
4065
- }
4066
- function typeNameOf(annotation) {
4067
- if (annotation.type === "type" && annotation.namedChildren[0]) {
4068
- return typeNameOf(annotation.namedChildren[0]);
4069
- }
4070
- if (annotation.type === "identifier") {
4071
- return annotation.text;
4072
- }
4073
- const quoted = stringLiteralValue(annotation);
4074
- if (quoted !== null) {
4075
- return quoted;
4076
- }
4077
- if (annotation.type === "binary_operator") {
4078
- const named = [field(annotation, "left"), field(annotation, "right")].find(
4079
- (side) => side !== null && side.type !== "none"
4080
- );
4081
- return named === void 0 || named === null ? null : typeNameOf(named);
4082
- }
4083
- if (annotation.type === "generic_type") {
4084
- const outer = annotation.namedChildren[0];
4085
- const first = genericTypeArgs(annotation)[0];
4086
- if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4087
- return typeNameOf(first);
4088
- }
4089
- return outer?.type === "identifier" ? outer.text : null;
4090
- }
4091
- return null;
4092
- }
4093
- function statedTypeName(name, from) {
4094
- const fn = enclosingFunction(from);
4095
- if (fn === null) {
4096
- return null;
4097
- }
4098
- const params = field(fn, "parameters");
4099
- for (const param of params === null ? [] : children(params)) {
4100
- const info = parameterNameAndType(param);
4101
- if (info?.name === name && info.typeNode !== null) {
4102
- return typeNameOf(info.typeNode);
4103
- }
4104
- }
4105
- const body = field(fn, "body");
4106
- return body === null ? null : firstInBody(body, (statement) => statedBy(statement, name));
4107
- }
4108
- function firstInBody(node, read) {
4109
- const here = read(node);
4110
- if (here !== null) {
4111
- return here;
4112
- }
4113
- for (const child of children(node)) {
4114
- if (child.type === "function_definition" || child.type === "lambda") {
4115
- continue;
4116
- }
4117
- const found = firstInBody(child, read);
4118
- if (found !== null) {
4119
- return found;
4120
- }
4121
- }
4122
- return null;
4123
- }
4124
- function statedBy(statement, name) {
4125
- if (statement.type === "assignment") {
4126
- const annotation = field(statement, "type");
4127
- return field(statement, "left")?.text === name && annotation !== null ? typeNameOf(annotation) : null;
4128
- }
4129
- if (statement.type === "as_pattern") {
4130
- const alias = field(statement, "alias");
4131
- const value = statement.namedChildren[0] ?? null;
4132
- const callee = value?.type === "call" ? field(value, "function") : null;
4133
- return alias?.text === name && callee?.type === "identifier" ? callee.text : null;
4134
- }
4135
- return null;
4662
+ return object !== null && object.type === "identifier" ? object : null;
4136
4663
  }
4137
4664
  function builtType(call, options) {
4138
4665
  const root = rootOf(call);
@@ -4180,7 +4707,7 @@ function typedReceiverPattern(options, chain) {
4180
4707
  if (receiver === null) {
4181
4708
  return void 0;
4182
4709
  }
4183
- for (const origin of receiverTypeOrigins(options, receiver, chain.root)) {
4710
+ for (const origin of receiverTypeOrigins(receiver, options)) {
4184
4711
  const pattern = queryTypePattern(options, origin.module, origin.name);
4185
4712
  if (pattern !== void 0) {
4186
4713
  return pattern;
@@ -4188,14 +4715,6 @@ function typedReceiverPattern(options, chain) {
4188
4715
  }
4189
4716
  return void 0;
4190
4717
  }
4191
- function receiverTypeOrigins(options, name, from) {
4192
- const stated = statedTypeName(name, from);
4193
- if (stated !== null) {
4194
- return originsOf(options.facts, `${options.filePath}#${stated}`);
4195
- }
4196
- const key = nameKeyIn(options.filePath, enclosingFunction(from), name);
4197
- return subjectConstructions(options.facts, [key]).get(key)?.origins ?? [];
4198
- }
4199
4718
  function queryTypePattern(options, module, name) {
4200
4719
  return options.patterns.find(
4201
4720
  (pattern) => module === pattern.module && pattern.queryTypes.includes(name)
@@ -4317,7 +4836,7 @@ function classBehind(typeName, options) {
4317
4836
  function isClassName(name) {
4318
4837
  return /^[A-Z][A-Za-z0-9_]*$/.test(name) && name !== name.toUpperCase();
4319
4838
  }
4320
- function effectFor(pattern, chain, operation, options) {
4839
+ function effectFor2(pattern, chain, operation, options) {
4321
4840
  const valueMethods = pattern.valueMethods ?? [];
4322
4841
  const picked = selectorOf(chain, valueMethods);
4323
4842
  return {
@@ -4427,7 +4946,7 @@ function storageCallIds(calls, options) {
4427
4946
  }
4428
4947
  function storageEffects(calls, options) {
4429
4948
  return matchedChains(calls, options).flatMap(
4430
- ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation, options)]
4949
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor2(pattern, chain, operation, options)]
4431
4950
  );
4432
4951
  }
4433
4952
 
@@ -4462,8 +4981,10 @@ function decoratedStatements(root, module, facts) {
4462
4981
  }));
4463
4982
  }
4464
4983
  function discoverUnits(root, module, options) {
4984
+ const decorated = decoratedStatements(root, module, options.facts);
4985
+ askWrittenValues(clientCallReceivers(root, module), options.facts);
4465
4986
  return [
4466
- ...decoratedStatements(root, module, options.facts).flatMap(
4987
+ ...decorated.flatMap(
4467
4988
  (decoratedStatement) => decoratedUnits(decoratedStatement, module, options)
4468
4989
  ),
4469
4990
  ...clientUnits(root, module, options)
@@ -5153,7 +5674,12 @@ function buildRouteUnit(options) {
5153
5674
  () => readReturnAnnotation(definitionNode, enclosingScope, ctx)
5154
5675
  );
5155
5676
  const effects = invocationEffects(definitionNode, options.facts);
5156
- const extra = recognizedBodyEffects(definitionNode, module, storageLookup);
5677
+ const extra = recognizedBodyEffects(
5678
+ definitionNode,
5679
+ module,
5680
+ storageLookup,
5681
+ options.facts
5682
+ );
5157
5683
  const perTerminal = branchesPerTerminal({
5158
5684
  definitionNode,
5159
5685
  effects,
@@ -5238,28 +5764,35 @@ function unreadRoleReadings(parameters, range) {
5238
5764
  )
5239
5765
  ];
5240
5766
  }
5241
- function recognizedBodyEffects(definitionNode, module, storageLookup) {
5767
+ function recognizedBodyEffects(definitionNode, module, storageLookup, facts) {
5242
5768
  const storage = storageLookup === void 0 ? [] : [
5243
5769
  ...storageEffects(bodyCalls(definitionNode), {
5244
5770
  ...storageLookup,
5245
5771
  filePath: storageLookup.factsPath
5246
5772
  }),
5247
- ...rawSqlEffects(bodyCalls(definitionNode), {
5248
- facts: storageLookup.facts,
5249
- filePath: storageLookup.factsPath,
5250
- patterns: storageLookup.rawSql ?? []
5251
- })
5773
+ ...rawSqlEffects(bodyCalls(definitionNode), rawSqlOf(storageLookup))
5252
5774
  ];
5253
- return [...envReadEffects(definitionNode, module), ...storage];
5775
+ return [...envReadEffects(definitionNode, module, facts), ...storage];
5776
+ }
5777
+ function rawSqlOf(storageLookup) {
5778
+ return {
5779
+ facts: storageLookup.facts,
5780
+ filePath: storageLookup.factsPath,
5781
+ patterns: storageLookup.rawSql ?? [],
5782
+ clients: storageLookup.sqlClients ?? []
5783
+ };
5254
5784
  }
5255
- function recognizedCallIds(definitionNode, storageLookup) {
5785
+ function recognizedCallIds(calls, storageLookup) {
5256
5786
  if (storageLookup === void 0) {
5257
5787
  return /* @__PURE__ */ new Set();
5258
5788
  }
5259
- return storageCallIds(bodyCalls(definitionNode), {
5260
- ...storageLookup,
5261
- filePath: storageLookup.factsPath
5262
- });
5789
+ return /* @__PURE__ */ new Set([
5790
+ ...storageCallIds(calls, {
5791
+ ...storageLookup,
5792
+ filePath: storageLookup.factsPath
5793
+ }),
5794
+ ...rawSqlCallIds(calls, rawSqlOf(storageLookup))
5795
+ ]);
5263
5796
  }
5264
5797
  function bodyContentOf(bodyNode) {
5265
5798
  for (const stmt of bodyStatements(bodyNode)) {
@@ -5599,6 +6132,7 @@ import {
5599
6132
  composeWrappers,
5600
6133
  createCacheLayer,
5601
6134
  createTimer,
6135
+ effectToIR,
5602
6136
  moduleInitStructure,
5603
6137
  noopTimer,
5604
6138
  runDigest,
@@ -5731,11 +6265,14 @@ function resolveCallee2(call, site, ctx, read) {
5731
6265
  const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key);
5732
6266
  return asCallee(outcome, site.owner, ctx);
5733
6267
  }
5734
- function functionNamed(nameKey2, ctx) {
6268
+ function namedOutcomes(keys, ctx) {
6269
+ return calleeOutcomes(ctx.facts, [...new Set(keys)]);
6270
+ }
6271
+ function functionNamed(nameKey2, ctx, read) {
5735
6272
  if (assignedElsewhere(ctx.facts, nameKey2)) {
5736
6273
  return null;
5737
6274
  }
5738
- const outcome = calleeOutcomeOf(ctx.facts, nameKey2);
6275
+ const outcome = read?.get(nameKey2) ?? calleeOutcomeOf(ctx.facts, nameKey2);
5739
6276
  return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
5740
6277
  }
5741
6278
  function assignedElsewhere(facts, nameKey2) {
@@ -5943,6 +6480,7 @@ function reachedFunctions(seeds, options) {
5943
6480
  const stopsByKey = /* @__PURE__ */ new Map();
5944
6481
  const argTargetsByKey = /* @__PURE__ */ new Map();
5945
6482
  const parameterCallsByKey = /* @__PURE__ */ new Map();
6483
+ const callsByKey = /* @__PURE__ */ new Map();
5946
6484
  const passedPositions = /* @__PURE__ */ new Set();
5947
6485
  for (const seed of seeds) {
5948
6486
  seedKeys.add(seed.key);
@@ -5969,12 +6507,21 @@ function reachedFunctions(seeds, options) {
5969
6507
  bodies.flatMap((body) => body.written),
5970
6508
  ctx
5971
6509
  );
6510
+ const passed = namedOutcomes(
6511
+ bodies.flatMap(
6512
+ ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
6513
+ ),
6514
+ ctx
6515
+ );
5972
6516
  for (const { key, source, body, written } of bodies) {
5973
6517
  const scan = scanBody(
5974
6518
  source,
5975
6519
  ctx,
5976
- recognizedCallIds(source.node, options.storageFor(source.file)),
5977
- { body, written, spellings }
6520
+ recognizedCallIds(
6521
+ written.map(({ call }) => call),
6522
+ options.storageFor(source.file)
6523
+ ),
6524
+ { body, written, spellings, passed }
5978
6525
  );
5979
6526
  if (scan.stops.length > 0) {
5980
6527
  stopsByKey.set(key, scan.stops);
@@ -5987,21 +6534,27 @@ function reachedFunctions(seeds, options) {
5987
6534
  for (const position of scan.passedPositions) {
5988
6535
  passedPositions.add(position);
5989
6536
  }
6537
+ const called = [];
5990
6538
  for (const target of scan.followed) {
5991
- const calleeKey = keyOf(target);
6539
+ const calleeKey = keyOf2(target);
5992
6540
  if (!functionByKey.has(calleeKey)) {
5993
6541
  functionByKey.set(calleeKey, target);
5994
6542
  }
5995
6543
  db.add("calls", [key, calleeKey]);
6544
+ called.push(calleeKey);
6545
+ }
6546
+ if (called.length > 0) {
6547
+ callsByKey.set(key, called);
5996
6548
  }
5997
6549
  }
5998
6550
  }
6551
+ const reached = db.facts("reachable").map(([keyAtom]) => String(keyAtom)).filter((key) => !seedKeys.has(key));
6552
+ settleBodyValues(reached, functionByKey, options.facts);
5999
6553
  const summaries = [];
6000
6554
  const summariesByKey = /* @__PURE__ */ new Map();
6001
- for (const [keyAtom] of db.facts("reachable")) {
6002
- const key = String(keyAtom);
6555
+ for (const key of reached) {
6003
6556
  const target = functionByKey.get(key);
6004
- if (seedKeys.has(key) || target === void 0) {
6557
+ if (target === void 0) {
6005
6558
  continue;
6006
6559
  }
6007
6560
  const summary = assembleSummary(libraryUnit(target, options), {
@@ -6026,10 +6579,11 @@ function reachedFunctions(seeds, options) {
6026
6579
  stopsByKey,
6027
6580
  argTargetsByKey,
6028
6581
  parameterCallsByKey,
6582
+ callsByKey,
6029
6583
  passedPositions
6030
6584
  };
6031
6585
  }
6032
- function keyOf(target) {
6586
+ function keyOf2(target) {
6033
6587
  return nodeId(target.file.file, target.node);
6034
6588
  }
6035
6589
  var EMPTY_SCAN = {
@@ -6042,15 +6596,53 @@ var EMPTY_SCAN = {
6042
6596
  };
6043
6597
  function bodyOf(source) {
6044
6598
  const { file, node } = source;
6045
- const body = field(node, "body");
6599
+ const body = isModule(node) ? node : field(node, "body");
6046
6600
  if (body === null) {
6047
6601
  return { body, written: [] };
6048
6602
  }
6049
6603
  return {
6050
6604
  body,
6051
- written: callsWritten(file, body, scopeAt2(file, node), keyOf(source))
6605
+ written: callsWritten(file, body, scopeAt2(file, node), keyOf2(source), {
6606
+ descendsInto: isModule(node) ? runsAtModuleLoad : outsideNestedDef
6607
+ })
6052
6608
  };
6053
6609
  }
6610
+ function outsideNestedDef(node) {
6611
+ return node.type !== "function_definition";
6612
+ }
6613
+ function settleBodyValues(reached, functionByKey, facts) {
6614
+ const byFile = /* @__PURE__ */ new Map();
6615
+ for (const key of reached) {
6616
+ const target = functionByKey.get(key);
6617
+ if (target === void 0) {
6618
+ continue;
6619
+ }
6620
+ const listed = byFile.get(target.file.file) ?? [];
6621
+ for (const node of bodyValueNodes(target.node)) {
6622
+ listed.push(node);
6623
+ }
6624
+ byFile.set(target.file.file, listed);
6625
+ }
6626
+ for (const nodes of byFile.values()) {
6627
+ askWrittenValues(nodes, facts);
6628
+ }
6629
+ }
6630
+ function passedNameKeyOf(file, arg) {
6631
+ if (arg === null || arg.type !== "identifier") {
6632
+ return null;
6633
+ }
6634
+ return readKey(file.file, arg, enclosingFunction(arg));
6635
+ }
6636
+ function passedNameKeys(file, call) {
6637
+ const args = field(call, "arguments");
6638
+ if (args === null) {
6639
+ return [];
6640
+ }
6641
+ return args.namedChildren.flatMap((arg) => {
6642
+ const key = passedNameKeyOf(file, arg);
6643
+ return key === null ? [] : [key];
6644
+ });
6645
+ }
6054
6646
  function scanBody(source, ctx, recognized, read) {
6055
6647
  const followed = [];
6056
6648
  const stops = [];
@@ -6071,17 +6663,15 @@ function scanBody(source, ctx, recognized, read) {
6071
6663
  return;
6072
6664
  }
6073
6665
  args.namedChildren.forEach((arg, position) => {
6074
- if (arg === null || arg.type !== "identifier") {
6666
+ const nameKey2 = passedNameKeyOf(file, arg);
6667
+ if (nameKey2 === null) {
6075
6668
  return;
6076
6669
  }
6077
- const resolved = functionNamed(
6078
- readKey(file.file, arg, enclosingFunction(arg)),
6079
- ctx
6080
- );
6670
+ const resolved = functionNamed(nameKey2, ctx, read.passed);
6081
6671
  if (resolved === null) {
6082
6672
  return;
6083
6673
  }
6084
- const key = keyOf(resolved);
6674
+ const key = keyOf2(resolved);
6085
6675
  if (!seen.has(key)) {
6086
6676
  seen.add(key);
6087
6677
  followed.push(resolved);
@@ -6106,7 +6696,7 @@ function scanBody(source, ctx, recognized, read) {
6106
6696
  recordPassedArgs(
6107
6697
  call,
6108
6698
  callee,
6109
- outcome.kind === "followed" ? keyOf(outcome.target) : null
6699
+ outcome.kind === "followed" ? keyOf2(outcome.target) : null
6110
6700
  );
6111
6701
  if (outcome.kind === "stopped") {
6112
6702
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6123,7 +6713,7 @@ function scanBody(source, ctx, recognized, read) {
6123
6713
  }
6124
6714
  return;
6125
6715
  }
6126
- const key = keyOf(outcome.target);
6716
+ const key = keyOf2(outcome.target);
6127
6717
  if (!seen.has(key)) {
6128
6718
  seen.add(key);
6129
6719
  followed.push(outcome.target);
@@ -6141,7 +6731,7 @@ function scanBody(source, ctx, recognized, read) {
6141
6731
  passedPositions
6142
6732
  };
6143
6733
  }
6144
- function callsWritten(file, body, outer, owner) {
6734
+ function callsWritten(file, body, outer, owner, limit) {
6145
6735
  const found = [];
6146
6736
  walkDescendants3(body, outer, {
6147
6737
  at: (child, scope) => {
@@ -6149,7 +6739,7 @@ function callsWritten(file, body, outer, owner) {
6149
6739
  found.push({ call: child, site: { file, scope, owner } });
6150
6740
  }
6151
6741
  },
6152
- into: (child, scope) => child.type === "function_definition" ? SKIP_CHILDREN2 : file.module.scopeFor.get(child.id) ?? scope
6742
+ into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : SKIP_CHILDREN2
6153
6743
  });
6154
6744
  return found;
6155
6745
  }
@@ -6182,7 +6772,8 @@ function libraryUnit(target, options) {
6182
6772
  const extra = recognizedBodyEffects(
6183
6773
  node,
6184
6774
  file.module,
6185
- options.storageFor(file)
6775
+ options.storageFor(file),
6776
+ options.facts
6186
6777
  );
6187
6778
  const range = rangeOf(node);
6188
6779
  return {
@@ -7763,7 +8354,8 @@ function wrapperUnit(target, declared, options) {
7763
8354
  const extra = recognizedBodyEffects(
7764
8355
  node,
7765
8356
  file.module,
7766
- options.storageFor(file)
8357
+ options.storageFor(file),
8358
+ options.facts
7767
8359
  );
7768
8360
  const delegate = (at) => ({
7769
8361
  terminal: {
@@ -7921,6 +8513,7 @@ function factsForFile(options) {
7921
8513
  files: [{ file: options.file, root: options.root, module: options.module }],
7922
8514
  definitions
7923
8515
  });
8516
+ bindEnvFacts(db, [envFactsIn(options.file, options.root, options.module)]);
7924
8517
  addPackWords(db, packWordsOf(options.packs));
7925
8518
  return db;
7926
8519
  }
@@ -7971,6 +8564,7 @@ async function extractPythonProject(options) {
7971
8564
  );
7972
8565
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
7973
8566
  const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
8567
+ const sqlClients = options.packs.flatMap((pack) => pack.sqlClients ?? []);
7974
8568
  const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
7975
8569
  const discovers = options.packs.some((pack) => pack.discovery.length > 0);
7976
8570
  const buildsReceivers = options.packs.some(
@@ -7978,7 +8572,15 @@ async function extractPythonProject(options) {
7978
8572
  (client) => (client.receiverConstructors ?? []).length > 0
7979
8573
  )
7980
8574
  );
7981
- const needsValues = discovers || mountsRouters || buildsReceivers || storagePatterns.length > 0 || modelQueries.length > 0;
8575
+ const envFacts = bound.map(
8576
+ (boundFile) => envFactsIn(boundFile.file, boundFile.root, boundFile.module)
8577
+ );
8578
+ const readsEnvThroughNames = envFacts.some(
8579
+ (one) => one.sites.length > 0 || one.objects.length > 0
8580
+ );
8581
+ const needsValues = discovers || mountsRouters || buildsReceivers || readsEnvThroughNames || storagePatterns.length > 0 || modelQueries.length > 0 || // A statement written as SQL is a value, and so is the client the
8582
+ // call that hands it over is read off.
8583
+ rawSqlPatterns.length > 0 || sqlClients.length > 0;
7982
8584
  const definitions = /* @__PURE__ */ new Map();
7983
8585
  timer.time("discover", () => {
7984
8586
  for (const { file, root, module: moduleBinding } of bound) {
@@ -7990,6 +8592,7 @@ async function extractPythonProject(options) {
7990
8592
  }
7991
8593
  if (needsValues) {
7992
8594
  bindEvaluator(db, { files: bound, definitions });
8595
+ bindEnvFacts(db, envFacts);
7993
8596
  }
7994
8597
  addPackWords(db, packWordsOf(options.packs));
7995
8598
  });
@@ -8005,13 +8608,14 @@ async function extractPythonProject(options) {
8005
8608
  ...mountsRouters ? { facts: db } : {}
8006
8609
  })
8007
8610
  );
8008
- const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
8611
+ const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 || sqlClients.length > 0 ? {
8009
8612
  facts: db,
8010
8613
  factsPath: file.file,
8011
8614
  patterns: storagePatterns,
8012
8615
  definitionAt: (key) => definitions.get(key),
8013
8616
  couldMatch,
8014
- rawSql: rawSqlPatterns
8617
+ rawSql: rawSqlPatterns,
8618
+ sqlClients
8015
8619
  } : void 0;
8016
8620
  const wrapperIndex = timer.time(
8017
8621
  "discover",
@@ -8046,6 +8650,7 @@ async function extractPythonProject(options) {
8046
8650
  }
8047
8651
  const seeds = [];
8048
8652
  const summariesBySeed = /* @__PURE__ */ new Map();
8653
+ const moduleRoots = [];
8049
8654
  for (const boundFile of bound) {
8050
8655
  const { file, root, module: moduleBinding } = boundFile;
8051
8656
  const displayPath = displayPathOf(file);
@@ -8078,23 +8683,17 @@ async function extractPythonProject(options) {
8078
8683
  }
8079
8684
  const loadTimeReads = timer.time(
8080
8685
  "discover",
8081
- () => envReadEffects(root, moduleBinding)
8686
+ () => envReadEffects(root, moduleBinding, db)
8082
8687
  );
8083
- if (loadTimeReads.length > 0) {
8084
- const summary = timer.time(
8085
- "summarize",
8086
- () => assembleSummary2(
8087
- moduleInitStructure({
8088
- name: path3.basename(displayPath),
8089
- file: displayPath,
8090
- range: rangeOf(root),
8091
- effects: loadTimeReads
8092
- }),
8093
- { gapHandling }
8094
- )
8095
- );
8096
- summary.confidence = { source: "inferred_static", level: "low" };
8097
- summaries.push(summary);
8688
+ const moduleKey = nodeId(file, root);
8689
+ if (!summariesBySeed.has(moduleKey)) {
8690
+ seeds.push({ key: moduleKey, file: boundFile, node: root });
8691
+ moduleRoots.push({
8692
+ boundFile,
8693
+ displayPath,
8694
+ key: moduleKey,
8695
+ loadTimeReads
8696
+ });
8098
8697
  }
8099
8698
  }
8100
8699
  const reached = timer.time(
@@ -8108,16 +8707,19 @@ async function extractPythonProject(options) {
8108
8707
  definitions
8109
8708
  })
8110
8709
  );
8710
+ const placeWhatItReached = (summary, key) => {
8711
+ if (gapHandling !== "silent") {
8712
+ summary.gaps.push(
8713
+ ...(reached.stopsByKey.get(key) ?? []).map(unfollowedCallGap2)
8714
+ );
8715
+ }
8716
+ placeCalls2(summary, reached.targetsByKey.get(key));
8717
+ placeArgTargets2(summary, reached.argTargetsByKey.get(key));
8718
+ placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
8719
+ };
8111
8720
  for (const [key, owners] of summariesBySeed) {
8112
8721
  for (const summary of owners) {
8113
- if (gapHandling !== "silent") {
8114
- summary.gaps.push(
8115
- ...(reached.stopsByKey.get(key) ?? []).map(unfollowedCallGap2)
8116
- );
8117
- }
8118
- placeCalls2(summary, reached.targetsByKey.get(key));
8119
- placeArgTargets2(summary, reached.argTargetsByKey.get(key));
8120
- placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
8722
+ placeWhatItReached(summary, key);
8121
8723
  }
8122
8724
  }
8123
8725
  if (gapHandling !== "silent") {
@@ -8127,6 +8729,30 @@ async function extractPythonProject(options) {
8127
8729
  reached.passedPositions
8128
8730
  );
8129
8731
  }
8732
+ for (const { boundFile, displayPath, key, loadTimeReads } of moduleRoots) {
8733
+ const calledAtLoad = reached.callsByKey.get(key) ?? [];
8734
+ if (loadTimeReads.length === 0 && calledAtLoad.length === 0) {
8735
+ continue;
8736
+ }
8737
+ const summary = timer.time(
8738
+ "summarize",
8739
+ () => assembleSummary2(
8740
+ moduleInitStructure({
8741
+ name: path3.basename(displayPath),
8742
+ file: displayPath,
8743
+ range: rangeOf(boundFile.root),
8744
+ effects: [
8745
+ ...loadTimeReads,
8746
+ ...moduleLoadInvocationEffects(boundFile.root, db).map(effectToIR)
8747
+ ]
8748
+ }),
8749
+ { gapHandling }
8750
+ )
8751
+ );
8752
+ summary.confidence = { source: "inferred_static", level: "low" };
8753
+ placeWhatItReached(summary, key);
8754
+ summaries.push(summary);
8755
+ }
8130
8756
  summaries.push(...reached.summaries);
8131
8757
  const resolvedImports = resolvedImportsOf(db, displayPathOf);
8132
8758
  stampModuleImports(summaries, (file) => resolvedImports.get(file) ?? []);