@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.cjs CHANGED
@@ -97,6 +97,12 @@ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([
97
97
  "lambda",
98
98
  "class_definition"
99
99
  ]);
100
+ function isModule(node) {
101
+ return node.type === "module";
102
+ }
103
+ function runsAtModuleLoad(node) {
104
+ return !LATER_BODY_TYPES.has(node.type) && node.type !== "decorator";
105
+ }
100
106
  function enclosingFunction(node) {
101
107
  let current = node.parent;
102
108
  while (current !== null) {
@@ -784,6 +790,15 @@ var import_resolution = require("@suss/resolution");
784
790
  function resolveCalls(db, callKeys) {
785
791
  (0, import_resolution.askResolution)(db, callKeys);
786
792
  }
793
+ function resolveEnvObjects(db, objectKeys) {
794
+ (0, import_resolution.askResolution)(db, objectKeys, "wantedEnvObject");
795
+ }
796
+ function resolvedFunctions(db, key) {
797
+ return [
798
+ ...db.lookup("wantedResolves", 0, key),
799
+ ...db.lookup("wantedGivesBack", 0, key)
800
+ ].map((row) => String(row[1]));
801
+ }
787
802
  function writtenValueUnder(db, key, site) {
788
803
  if ((0, import_resolution.askResolutionUnder)(db, [[key, site]]) === "abandoned") {
789
804
  return null;
@@ -802,6 +817,13 @@ function writtenValuesOf(db, key) {
802
817
  resolveCalls(db, [key]);
803
818
  return (0, import_resolution.writtenValuesOf)(db, key, (keys) => resolveCalls(db, keys));
804
819
  }
820
+ function settleWrittenValues(db, keys) {
821
+ if (keys.length === 0) {
822
+ return;
823
+ }
824
+ resolveCalls(db, keys);
825
+ (0, import_resolution.writtenValuesByKey)(db, keys, (behind) => resolveCalls(db, behind));
826
+ }
805
827
  function originsOf(db, nameKey2) {
806
828
  (0, import_resolution.askResolution)(db, [nameKey2], "wantedOrigin");
807
829
  return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey2).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
@@ -992,31 +1014,46 @@ function emitCall(emitter, call) {
992
1014
  if (!emitter.insideMethod) {
993
1015
  add(emitter, "callOutsideMethod", callKey);
994
1016
  }
1017
+ for (const argument of callArguments(call)) {
1018
+ if (argument.kind === "keyword") {
1019
+ add(
1020
+ emitter,
1021
+ "callKeywordArg",
1022
+ callKey,
1023
+ argument.name,
1024
+ valueKey(emitter, argument.node)
1025
+ );
1026
+ continue;
1027
+ }
1028
+ add(
1029
+ emitter,
1030
+ "callArg",
1031
+ callKey,
1032
+ String(argument.position),
1033
+ valueKey(emitter, argument.node)
1034
+ );
1035
+ }
1036
+ }
1037
+ function callArguments(call) {
1038
+ const args = field(call, "arguments");
1039
+ if (args === null) {
1040
+ return [];
1041
+ }
1042
+ const written = [];
995
1043
  let position = 0;
996
1044
  for (const argument of children(args)) {
997
1045
  if (argument.type === "keyword_argument") {
998
1046
  const name = field(argument, "name");
999
1047
  const value = field(argument, "value");
1000
1048
  if (name !== null && value !== null) {
1001
- add(
1002
- emitter,
1003
- "callKeywordArg",
1004
- callKey,
1005
- name.text,
1006
- valueKey(emitter, value)
1007
- );
1049
+ written.push({ kind: "keyword", name: name.text, node: value });
1008
1050
  }
1009
1051
  continue;
1010
1052
  }
1011
- add(
1012
- emitter,
1013
- "callArg",
1014
- callKey,
1015
- String(position),
1016
- valueKey(emitter, argument)
1017
- );
1053
+ written.push({ kind: "positional", position, node: argument });
1018
1054
  position += 1;
1019
1055
  }
1056
+ return written;
1020
1057
  }
1021
1058
  function valueKey(emitter, value) {
1022
1059
  if (value.type !== "identifier") {
@@ -1083,9 +1120,51 @@ function walkExpressions(emitter, node, visit) {
1083
1120
  walkExpressions(emitter, child, visit);
1084
1121
  }
1085
1122
  }
1123
+ var WRITTEN_KEY_TYPES = /* @__PURE__ */ new Set(["string", "integer", "concatenated_string"]);
1124
+ function emitKeyedRead(emitter, site, container, key) {
1125
+ if (WRITTEN_KEY_TYPES.has(key.type)) {
1126
+ return;
1127
+ }
1128
+ add(
1129
+ emitter,
1130
+ "readsKeyed",
1131
+ nodeId(emitter.filePath, site),
1132
+ valueKey(emitter, container),
1133
+ valueKey(emitter, key)
1134
+ );
1135
+ }
1136
+ function singleSubscript(node) {
1137
+ const written = fields(node, "subscript");
1138
+ return written.length === 1 ? written[0] ?? null : null;
1139
+ }
1140
+ function mappingGetRead(call) {
1141
+ const callee = field(call, "function");
1142
+ if (callee === null || callee.type !== "attribute") {
1143
+ return null;
1144
+ }
1145
+ const container = field(callee, "object");
1146
+ if (container === null || field(callee, "attribute")?.text !== "get") {
1147
+ return null;
1148
+ }
1149
+ const first = callArguments(call).find(
1150
+ (argument) => argument.kind === "positional" && argument.position === 0
1151
+ );
1152
+ return first === void 0 ? null : { container, key: first.node };
1153
+ }
1086
1154
  function emitExpressionFact(emitter, child) {
1087
1155
  if (child.type === "call") {
1088
1156
  emitCall(emitter, child);
1157
+ const mapping = mappingGetRead(child);
1158
+ if (mapping !== null) {
1159
+ emitKeyedRead(emitter, child, mapping.container, mapping.key);
1160
+ }
1161
+ }
1162
+ if (child.type === "subscript") {
1163
+ const index = singleSubscript(child);
1164
+ const container = field(child, "value");
1165
+ if (index !== null && container !== null) {
1166
+ emitKeyedRead(emitter, child, container, index);
1167
+ }
1089
1168
  }
1090
1169
  if (child.type === "dictionary") {
1091
1170
  emitDictionary(emitter, child);
@@ -1153,6 +1232,9 @@ function emitFunctionFacts(emitter, fn, classKey) {
1153
1232
  return funcKey;
1154
1233
  }
1155
1234
  emitNestedDefinitions(inside, body);
1235
+ if (fn.type === "lambda") {
1236
+ emitExpressionFact(inside, body);
1237
+ }
1156
1238
  let statesReturn = false;
1157
1239
  const stores = /* @__PURE__ */ new Map();
1158
1240
  walkExpressions(inside, body, (child) => {
@@ -2511,6 +2593,10 @@ function writtenNodeOf(node, db) {
2511
2593
  const bound = db === void 0 ? void 0 : projects.get(db);
2512
2594
  return bound?.context.writtenTo(node) ?? null;
2513
2595
  }
2596
+ function resolutionKeyOf(node, db) {
2597
+ const bound = db === void 0 ? void 0 : projects.get(db);
2598
+ return bound?.keyOf(node) ?? null;
2599
+ }
2514
2600
  var NOTHING_BUILT = { type: "noCall" };
2515
2601
  function constructionBehind(node, db) {
2516
2602
  if (db === void 0) {
@@ -2545,8 +2631,17 @@ function askWrittenValues(nodes, db) {
2545
2631
  if (bound === void 0) {
2546
2632
  return;
2547
2633
  }
2548
- const keys = nodes.map((node) => bound.keyOf(node)).filter((key) => key !== null);
2549
- resolveCalls(db, keys);
2634
+ settleWrittenValues(db, resolutionKeysOf(nodes, bound));
2635
+ }
2636
+ function resolutionKeysOf(nodes, bound) {
2637
+ const keys = /* @__PURE__ */ new Set();
2638
+ for (const node of nodes) {
2639
+ const key = bound.keyOf(node);
2640
+ if (key !== null) {
2641
+ keys.add(key);
2642
+ }
2643
+ }
2644
+ return [...keys];
2550
2645
  }
2551
2646
  function evaluatedValue(node, db, site) {
2552
2647
  const evaluator = evaluatorFor(node, db);
@@ -2614,7 +2709,7 @@ function projectOver(db, nodes) {
2614
2709
  filesByRoot.set(entry.root.id, entry);
2615
2710
  rootsByFile.set(entry.file, entry.root);
2616
2711
  }
2617
- const keyOf2 = (node) => {
2712
+ const keyOf3 = (node) => {
2618
2713
  const entry = filesByRoot.get(node.tree.rootNode.id);
2619
2714
  if (entry === void 0) {
2620
2715
  return null;
@@ -2623,7 +2718,7 @@ function projectOver(db, nodes) {
2623
2718
  };
2624
2719
  const context = {
2625
2720
  writtenTo: (node, site) => {
2626
- const key = keyOf2(node);
2721
+ const key = keyOf3(node);
2627
2722
  if (key === null) {
2628
2723
  return null;
2629
2724
  }
@@ -2632,17 +2727,17 @@ function projectOver(db, nodes) {
2632
2727
  },
2633
2728
  callable: (call) => {
2634
2729
  const callee = field(call, "function");
2635
- const key = callee === null ? null : keyOf2(callee);
2730
+ const key = callee === null ? null : keyOf3(callee);
2636
2731
  if (key === null) {
2637
2732
  return null;
2638
2733
  }
2639
2734
  resolveCalls(db, [key]);
2640
- const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2641
- const settled = resolved.length === 1 ? resolved[0] : void 0;
2735
+ const resolved = db.lookup("wantedResolves", 0, key);
2736
+ const settled = resolved.length === 1 ? String(resolved[0]?.[1]) : void 0;
2642
2737
  return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
2643
2738
  }
2644
2739
  };
2645
- return { keyOf: keyOf2, roots: rootsByFile, context };
2740
+ return { keyOf: keyOf3, roots: rootsByFile, context };
2646
2741
  }
2647
2742
 
2648
2743
  // src/decorators.ts
@@ -2857,7 +2952,7 @@ function passesParametersThrough(def, inner) {
2857
2952
  // src/discovery.ts
2858
2953
  var import_behavioral_ir4 = require("@suss/behavioral-ir");
2859
2954
  var import_extractor5 = require("@suss/extractor");
2860
- var import_values10 = require("@suss/values");
2955
+ var import_values13 = require("@suss/values");
2861
2956
 
2862
2957
  // src/clientCalls.ts
2863
2958
  var import_behavioral_ir2 = require("@suss/behavioral-ir");
@@ -3270,6 +3365,18 @@ function bodyCalls(node, found = []) {
3270
3365
  }
3271
3366
  return found;
3272
3367
  }
3368
+ function moduleLoadCalls(node, found = []) {
3369
+ for (const child of node.namedChildren) {
3370
+ if (child === null || !runsAtModuleLoad(child)) {
3371
+ continue;
3372
+ }
3373
+ if (child.type === "call") {
3374
+ found.push(child);
3375
+ }
3376
+ moduleLoadCalls(child, found);
3377
+ }
3378
+ return found;
3379
+ }
3273
3380
  function enclosingStatement(call, body) {
3274
3381
  let current = call;
3275
3382
  while (current !== null && current.parent !== null) {
@@ -3294,15 +3401,18 @@ function argOf(node, facts) {
3294
3401
  }
3295
3402
  return { kind: "identifier", name: node.text };
3296
3403
  }
3297
- function argsOf(call, facts) {
3404
+ function argumentNodes(call) {
3298
3405
  const args = field(call, "arguments");
3299
3406
  if (args === null) {
3300
3407
  return [];
3301
3408
  }
3302
3409
  return args.namedChildren.filter((child) => child !== null).map(
3303
- (child) => child.type === "keyword_argument" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3410
+ (child) => child.type === "keyword_argument" ? field(child, "value") ?? child : child
3304
3411
  );
3305
3412
  }
3413
+ function argsOf(call, facts) {
3414
+ return argumentNodes(call).map((node) => argOf(node, facts));
3415
+ }
3306
3416
  function calleeText(call) {
3307
3417
  return field(call, "function")?.text ?? call.text;
3308
3418
  }
@@ -3311,7 +3421,40 @@ function invocationEffects(definitionNode, facts) {
3311
3421
  if (body === null) {
3312
3422
  return [];
3313
3423
  }
3314
- const calls = bodyCalls(body).sort((a, b) => a.endIndex - b.endIndex);
3424
+ return invocationEffectsIn(body, bodyCalls(body), facts);
3425
+ }
3426
+ function moduleLoadInvocationEffects(moduleNode, facts) {
3427
+ return invocationEffectsIn(moduleNode, moduleLoadCalls(moduleNode), facts);
3428
+ }
3429
+ var ASKED_ABOUT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "call"]);
3430
+ function askedNodesUnder(node, found = []) {
3431
+ if (ASKED_ABOUT_TYPES.has(node.type)) {
3432
+ found.push(node);
3433
+ }
3434
+ for (const child of node.namedChildren) {
3435
+ if (child !== null && !NESTED_DEFINITION_TYPES.has(child.type)) {
3436
+ askedNodesUnder(child, found);
3437
+ }
3438
+ }
3439
+ return found;
3440
+ }
3441
+ function bodyValueNodes(definitionNode) {
3442
+ const body = field(definitionNode, "body");
3443
+ return body === null ? [] : askedNodesUnder(body);
3444
+ }
3445
+ function effectValueNodes(call, gating) {
3446
+ const found = argumentNodes(call).flatMap((node) => askedNodesUnder(node));
3447
+ for (const path6 of gating ?? []) {
3448
+ for (const condition of path6) {
3449
+ if (condition.expression !== null) {
3450
+ askedNodesUnder(condition.expression, found);
3451
+ }
3452
+ }
3453
+ }
3454
+ return found;
3455
+ }
3456
+ function invocationEffectsIn(body, written, facts) {
3457
+ const calls = [...written].sort((a, b) => a.endIndex - b.endIndex);
3315
3458
  if (calls.length === 0) {
3316
3459
  return [];
3317
3460
  }
@@ -3333,10 +3476,17 @@ function invocationEffects(definitionNode, facts) {
3333
3476
  },
3334
3477
  statements
3335
3478
  );
3336
- return calls.map((call) => {
3479
+ const gatingOf = calls.map((call) => {
3337
3480
  const statement = statementOf3.get(call.id);
3481
+ return statement === void 0 ? void 0 : enumerated.byTerminal.get(statement);
3482
+ });
3483
+ askWrittenValues(
3484
+ calls.flatMap((call, index) => effectValueNodes(call, gatingOf[index])),
3485
+ facts
3486
+ );
3487
+ return calls.map((call, index) => {
3338
3488
  const conditions = (0, import_extractor3.sharedGatingConditions)(
3339
- statement === void 0 ? void 0 : enumerated.byTerminal.get(statement),
3489
+ gatingOf[index],
3340
3490
  (condition) => predicateOf(condition, facts)
3341
3491
  );
3342
3492
  return {
@@ -3367,6 +3517,22 @@ function clientCallUnits(root, module2, pack, pattern, options) {
3367
3517
  }
3368
3518
  return units;
3369
3519
  }
3520
+ function clientCallReceivers(root, module2) {
3521
+ const found = [];
3522
+ for (const definition of functionDefinitions(root)) {
3523
+ for (const call of bodyCalls(definition)) {
3524
+ const callee = field(call, "function");
3525
+ if (callee === null || originOf(callee, module2) !== null) {
3526
+ continue;
3527
+ }
3528
+ const object = field(callee, "object");
3529
+ if (object !== null && field(callee, "attribute") !== null) {
3530
+ found.push(object);
3531
+ }
3532
+ }
3533
+ }
3534
+ return found;
3535
+ }
3370
3536
  function functionDefinitions(node, found = []) {
3371
3537
  for (const child of children(node)) {
3372
3538
  if (isFunction(child)) {
@@ -3575,14 +3741,22 @@ var import_behavioral_ir3 = require("@suss/behavioral-ir");
3575
3741
  var import_extractor4 = require("@suss/extractor");
3576
3742
  var PYTHON_ENV_RECOGNITION = "python-env";
3577
3743
  var DEFERRED_BODY_TYPES = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
3578
- function envReadEffects(root, module2) {
3579
- const effects = [];
3744
+ function envReadEffects(root, module2, facts) {
3745
+ const slots = [];
3746
+ const readsHelpers = facts !== void 0 && sitesByDb.has(facts);
3580
3747
  const startScope = module2.scopeFor.get(root.id) ?? module2.moduleScope;
3581
3748
  (0, import_extractor4.walkDescendants)(root, startScope, {
3582
3749
  at: (node, scope) => {
3583
- const read = envReadAt(node, scope);
3584
- if (read !== null) {
3585
- effects.push(configReadEffect(read));
3750
+ const syntax = envReadSyntaxAt(node, scope);
3751
+ if (syntax !== null) {
3752
+ const name = stringLiteralValue(syntax.name);
3753
+ if (name !== null) {
3754
+ slots.push({ read: { name, defaulted: syntax.defaulted } });
3755
+ }
3756
+ return;
3757
+ }
3758
+ if (readsHelpers && node.type === "call") {
3759
+ slots.push({ call: node });
3586
3760
  }
3587
3761
  },
3588
3762
  into: (node, scope) => {
@@ -3592,7 +3766,16 @@ function envReadEffects(root, module2) {
3592
3766
  return module2.scopeFor.get(node.id) ?? scope;
3593
3767
  }
3594
3768
  });
3595
- return effects;
3769
+ const throughHelpers = helperReadsByCall(
3770
+ slots.flatMap((slot) => "call" in slot ? [slot.call] : []),
3771
+ facts
3772
+ );
3773
+ return slots.flatMap((slot) => {
3774
+ if ("read" in slot) {
3775
+ return [configReadEffect(slot.read)];
3776
+ }
3777
+ return throughHelpers.get(slot.call.id) ?? [];
3778
+ });
3596
3779
  }
3597
3780
  function configReadEffect(read) {
3598
3781
  return {
@@ -3610,7 +3793,7 @@ function configReadEffect(read) {
3610
3793
  }
3611
3794
  };
3612
3795
  }
3613
- function envReadAt(node, scope) {
3796
+ function envReadSyntaxAt(node, scope) {
3614
3797
  if (node.type === "subscript") {
3615
3798
  return subscriptRead(node, scope);
3616
3799
  }
@@ -3625,11 +3808,10 @@ function subscriptRead(node, scope) {
3625
3808
  if (value === null || index === null || !isEnviron(value, scope)) {
3626
3809
  return null;
3627
3810
  }
3628
- const name = stringLiteralValue(index);
3629
- if (name === null || isAssignedTo(node)) {
3811
+ if (isAssignedTo(node)) {
3630
3812
  return null;
3631
3813
  }
3632
- return { name, defaulted: isDefaultedAt(node) };
3814
+ return { name: index, defaulted: isDefaultedAt(node) };
3633
3815
  }
3634
3816
  function isAssignedTo(node) {
3635
3817
  const parent = node.parent;
@@ -3643,22 +3825,20 @@ function isAssignedTo(node) {
3643
3825
  }
3644
3826
  function callRead(node, scope) {
3645
3827
  const callee = field(node, "function");
3646
- const args = field(node, "arguments");
3647
- if (callee === null || args === null || !isEnvGetter(callee, scope)) {
3828
+ if (callee === null || !isEnvGetter(callee, scope)) {
3648
3829
  return null;
3649
3830
  }
3650
- const positional = args.namedChildren.filter(
3651
- (arg) => arg !== null && arg.type !== "keyword_argument"
3831
+ const written = callArguments(node);
3832
+ const name = written.find(
3833
+ (argument) => argument.kind === "positional" && argument.position === 0
3652
3834
  );
3653
- const name = positional[0] === void 0 ? null : stringLiteralValue(positional[0]);
3654
- if (name === null) {
3835
+ if (name === void 0) {
3655
3836
  return null;
3656
3837
  }
3657
- const keywords = args.namedChildren.filter(
3658
- (arg) => arg !== null && arg.type === "keyword_argument"
3838
+ const hasDefault = written.some(
3839
+ (argument) => argument.kind === "positional" && argument.position === 1 || argument.kind === "keyword" && argument.name === "default"
3659
3840
  );
3660
- const hasDefault = positional.length > 1 || keywords.some((arg) => field(arg, "name")?.text === "default");
3661
- return { name, defaulted: hasDefault || isDefaultedAt(node) };
3841
+ return { name: name.node, defaulted: hasDefault || isDefaultedAt(node) };
3662
3842
  }
3663
3843
  function isEnvGetter(callee, scope) {
3664
3844
  if (callee.type === "attribute") {
@@ -3715,9 +3895,197 @@ function isDefaultedAt(node) {
3715
3895
  }
3716
3896
  return false;
3717
3897
  }
3898
+ function envFactsIn(filePath, root, module2) {
3899
+ const found = { sites: [], objects: [] };
3900
+ (0, import_extractor4.walkDescendants)(root, module2.moduleScope, {
3901
+ at: (node, scope) => {
3902
+ if (isEnviron(node, scope) && handsOnward(node)) {
3903
+ found.objects.push(keyOf(filePath, node));
3904
+ }
3905
+ const syntax = envReadSyntaxAt(node, scope);
3906
+ if (syntax === null || stringLiteralValue(syntax.name) !== null) {
3907
+ return;
3908
+ }
3909
+ found.sites.push({
3910
+ site: nodeId(filePath, node),
3911
+ nameKey: keyOf(filePath, syntax.name),
3912
+ defaulted: syntax.defaulted,
3913
+ functionKey: functionReadKey(filePath, node, scope)
3914
+ });
3915
+ },
3916
+ into: (node, scope) => module2.scopeFor.get(node.id) ?? scope
3917
+ });
3918
+ return found;
3919
+ }
3920
+ var keyOf = (filePath, node) => readKey(filePath, node, enclosingFunction(node));
3921
+ function functionReadKey(filePath, node, scope) {
3922
+ if (node.type !== "call") {
3923
+ return null;
3924
+ }
3925
+ const callee = field(node, "function");
3926
+ if (callee === null || isEnviron(field(callee, "object") ?? callee, scope)) {
3927
+ return null;
3928
+ }
3929
+ return keyOf(filePath, callee);
3930
+ }
3931
+ function handsOnward(node) {
3932
+ const parent = node.parent;
3933
+ if (parent === null) {
3934
+ return true;
3935
+ }
3936
+ if (parent.type === "subscript" && field(parent, "value")?.id === node.id) {
3937
+ return !writesTheKey(field(parent, "subscript"));
3938
+ }
3939
+ if (parent.type === "attribute" && field(parent, "object")?.id === node.id) {
3940
+ const call = parent.parent;
3941
+ return field(parent, "attribute")?.text === "get" && call !== null && call.type === "call" && !writesTheKey(firstArgumentOf(call));
3942
+ }
3943
+ return true;
3944
+ }
3945
+ var writesTheKey = (key) => key !== null && stringLiteralValue(key) !== null;
3946
+ function firstArgumentOf(call) {
3947
+ const first = callArguments(call).find(
3948
+ (argument) => argument.kind === "positional" && argument.position === 0
3949
+ );
3950
+ return first?.node ?? null;
3951
+ }
3952
+ var sitesByDb = /* @__PURE__ */ new WeakMap();
3953
+ function bindEnvFacts(db, found) {
3954
+ const sites = found.flatMap((one) => one.sites);
3955
+ const objects = found.flatMap((one) => one.objects);
3956
+ if (sites.length === 0 && objects.length === 0) {
3957
+ return;
3958
+ }
3959
+ let index = sitesByDb.get(db);
3960
+ if (index === void 0) {
3961
+ index = { byId: /* @__PURE__ */ new Map(), objects: /* @__PURE__ */ new Set(), sitesByParameter: null };
3962
+ sitesByDb.set(db, index);
3963
+ }
3964
+ for (const object of objects) {
3965
+ db.add("environmentObject", [object]);
3966
+ index.objects.add(object);
3967
+ }
3968
+ for (const site of sites) {
3969
+ index.byId.set(site.site, site);
3970
+ if (site.functionKey === null) {
3971
+ continue;
3972
+ }
3973
+ db.add("environmentObject", [site.functionKey]);
3974
+ db.add("readsKeyed", [site.site, site.functionKey, site.nameKey]);
3975
+ index.objects.add(site.functionKey);
3976
+ }
3977
+ }
3978
+ function sitesEachParameterNames(db, index) {
3979
+ if (index.sitesByParameter !== null) {
3980
+ return index.sitesByParameter;
3981
+ }
3982
+ resolveEnvObjects(db, [...index.objects]);
3983
+ const found = /* @__PURE__ */ new Map();
3984
+ for (const row of db.facts("wantedParamNamesEnv")) {
3985
+ const parameter = String(row[0]);
3986
+ const named = found.get(parameter) ?? [];
3987
+ named.push(String(row[1]));
3988
+ found.set(parameter, named);
3989
+ }
3990
+ index.sitesByParameter = found;
3991
+ return found;
3992
+ }
3993
+ function helperReadsByCall(calls, db) {
3994
+ const found = /* @__PURE__ */ new Map();
3995
+ const index = db === void 0 ? void 0 : sitesByDb.get(db);
3996
+ if (db === void 0 || index === void 0 || calls.length === 0) {
3997
+ return found;
3998
+ }
3999
+ const named = sitesEachParameterNames(db, index);
4000
+ if (named.size === 0) {
4001
+ return found;
4002
+ }
4003
+ const calleeKeys = /* @__PURE__ */ new Map();
4004
+ for (const call of calls) {
4005
+ const callee = field(call, "function");
4006
+ const key = callee === null ? null : resolutionKeyOf(callee, db);
4007
+ if (key !== null) {
4008
+ calleeKeys.set(call.id, key);
4009
+ }
4010
+ }
4011
+ if (calleeKeys.size === 0) {
4012
+ return found;
4013
+ }
4014
+ resolveCalls(db, [...new Set(calleeKeys.values())]);
4015
+ for (const call of calls) {
4016
+ const key = calleeKeys.get(call.id);
4017
+ if (key === void 0) {
4018
+ continue;
4019
+ }
4020
+ const argumentAt2 = argumentsByParameter(db, call, key);
4021
+ if (argumentAt2.size === 0) {
4022
+ continue;
4023
+ }
4024
+ const reads = readsThroughHelper(db, call, argumentAt2, index.byId, named);
4025
+ if (reads.length > 0) {
4026
+ found.set(call.id, reads);
4027
+ }
4028
+ }
4029
+ return found;
4030
+ }
4031
+ function argumentsByParameter(db, call, calleeKey) {
4032
+ const written = callArguments(call);
4033
+ if (written.length === 0) {
4034
+ return /* @__PURE__ */ new Map();
4035
+ }
4036
+ const positional = /* @__PURE__ */ new Map();
4037
+ const byName = /* @__PURE__ */ new Map();
4038
+ for (const argument of written) {
4039
+ if (argument.kind === "keyword") {
4040
+ byName.set(argument.name, argument.node);
4041
+ continue;
4042
+ }
4043
+ positional.set(String(argument.position), argument.node);
4044
+ }
4045
+ const argumentAt2 = /* @__PURE__ */ new Map();
4046
+ for (const callee of resolvedFunctions(db, calleeKey)) {
4047
+ for (const row of db.lookup("paramOf", 0, callee)) {
4048
+ const at = positional.get(String(row[1]));
4049
+ if (at !== void 0) {
4050
+ argumentAt2.set(String(row[2]), at);
4051
+ }
4052
+ }
4053
+ for (const row of db.lookup("paramNamed", 0, callee)) {
4054
+ const at = byName.get(String(row[1]));
4055
+ if (at !== void 0) {
4056
+ argumentAt2.set(String(row[2]), at);
4057
+ }
4058
+ }
4059
+ }
4060
+ return argumentAt2;
4061
+ }
4062
+ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
4063
+ const reads = /* @__PURE__ */ new Map();
4064
+ for (const [parameter, argument] of argumentAt2) {
4065
+ const sites = named.get(parameter);
4066
+ if (sites === void 0) {
4067
+ continue;
4068
+ }
4069
+ const name = stringLiteralValue(argument) ?? stringValueOf(argument, db);
4070
+ if (name === null) {
4071
+ continue;
4072
+ }
4073
+ for (const id of sites) {
4074
+ const defaulted = siteById.get(id)?.defaulted ?? false;
4075
+ reads.set(name, (reads.get(name) ?? true) && defaulted);
4076
+ }
4077
+ }
4078
+ if (reads.size === 0) {
4079
+ return [];
4080
+ }
4081
+ const defaultedAtCall = isDefaultedAt(call);
4082
+ return [...reads].map(
4083
+ ([name, defaulted]) => configReadEffect({ name, defaulted: defaulted || defaultedAtCall })
4084
+ );
4085
+ }
3718
4086
 
3719
4087
  // src/paths/raisedResponses.ts
3720
- var import_values8 = require("@suss/values");
4088
+ var import_values9 = require("@suss/values");
3721
4089
  var NESTED_DEFINITION_TYPES2 = /* @__PURE__ */ new Set(["function_definition", "lambda"]);
3722
4090
  function raisedResponses(body, options) {
3723
4091
  if (body === null || options.calls.length === 0) {
@@ -3811,7 +4179,7 @@ function statusOf(declared, call, facts) {
3811
4179
  if (written === null) {
3812
4180
  return declared.defaultStatusCode === void 0 ? null : { type: "literal", value: declared.defaultStatusCode };
3813
4181
  }
3814
- const value = (0, import_values8.constantOf)(evaluatedValue(written, facts));
4182
+ const value = (0, import_values9.constantOf)(evaluatedValue(written, facts));
3815
4183
  if (typeof value === "number") {
3816
4184
  return { type: "literal", value };
3817
4185
  }
@@ -3924,71 +4292,301 @@ function returnedBodyShape(statement) {
3924
4292
  // src/rawSql.ts
3925
4293
  var import_ir_core = require("@suss/ir-core");
3926
4294
  var import_sql = require("@suss/sql");
3927
- function rawSqlEffects(calls, options) {
3928
- if (options.patterns.length === 0) {
3929
- return [];
4295
+ var import_values11 = require("@suss/values");
4296
+
4297
+ // src/receiverTypes.ts
4298
+ var UNDECLARED_MODULE = "builtins";
4299
+ function annotationTarget(annotation) {
4300
+ if (annotation.type === "type" && annotation.namedChildren[0]) {
4301
+ return annotationTarget(annotation.namedChildren[0]);
3930
4302
  }
3931
- const effects = [];
3932
- for (const call of calls) {
3933
- const pattern = patternFor(call, options);
3934
- if (pattern === void 0) {
3935
- continue;
4303
+ if (annotation.type === "binary_operator") {
4304
+ const named = [field(annotation, "left"), field(annotation, "right")].find(
4305
+ (side) => side !== null && side.type !== "none"
4306
+ );
4307
+ return named === void 0 || named === null ? null : annotationTarget(named);
4308
+ }
4309
+ if (annotation.type === "generic_type") {
4310
+ const outer = annotation.namedChildren[0];
4311
+ const first = genericTypeArgs(annotation)[0];
4312
+ if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4313
+ return annotationTarget(first);
3936
4314
  }
3937
- const statement = statementIn(call, options.facts);
3938
- if (statement === null) {
4315
+ return outer ?? null;
4316
+ }
4317
+ return annotation;
4318
+ }
4319
+ function typeNameOf(annotation) {
4320
+ const target = annotationTarget(annotation);
4321
+ if (target === null) {
4322
+ return null;
4323
+ }
4324
+ return target.type === "identifier" ? target.text : stringLiteralValue(target);
4325
+ }
4326
+ function firstInBody(node, read) {
4327
+ const here = read(node);
4328
+ if (here !== null) {
4329
+ return here;
4330
+ }
4331
+ for (const child of children(node)) {
4332
+ if (child.type === "function_definition" || child.type === "lambda") {
3939
4333
  continue;
3940
4334
  }
3941
- for (const access of (0, import_sql.readSqlAccess)(statement, {
3942
- dialect: pattern.storageSystem
3943
- })) {
3944
- effects.push({
3945
- type: "interaction",
3946
- binding: (0, import_ir_core.storageBinding)({
3947
- recognition: `python-${pattern.module}`,
3948
- storageSystem: pattern.storageSystem,
3949
- scope: "default",
3950
- container: access.table
3951
- }),
3952
- callee: field(call, "function")?.text ?? "",
3953
- interaction: {
3954
- class: "storage-access",
3955
- kind: access.kind,
3956
- fields: access.fields,
3957
- ...access.selector.length > 0 ? { selector: access.selector } : {},
3958
- operation: field(call, "function")?.text ?? ""
3959
- }
3960
- });
4335
+ const found = firstInBody(child, read);
4336
+ if (found !== null) {
4337
+ return found;
3961
4338
  }
3962
4339
  }
3963
- return effects;
4340
+ return null;
3964
4341
  }
3965
- function patternFor(call, options) {
4342
+ function statedBy(statement, name) {
4343
+ if (statement.type === "assignment") {
4344
+ const annotation = field(statement, "type");
4345
+ return field(statement, "left")?.text === name ? annotation : null;
4346
+ }
4347
+ if (statement.type === "as_pattern") {
4348
+ const alias = field(statement, "alias");
4349
+ const value = statement.namedChildren[0] ?? null;
4350
+ const callee = value?.type === "call" ? field(value, "function") : null;
4351
+ return alias?.text === name ? callee : null;
4352
+ }
4353
+ return null;
4354
+ }
4355
+ function statedTypeNode(name, from) {
4356
+ const fn = enclosingFunction(from);
4357
+ if (fn === null) {
4358
+ return null;
4359
+ }
4360
+ const params = field(fn, "parameters");
4361
+ for (const param of params === null ? [] : children(params)) {
4362
+ const info = parameterNameAndType(param);
4363
+ if (info?.name === name && info.typeNode !== null) {
4364
+ return info.typeNode;
4365
+ }
4366
+ }
4367
+ const body = field(fn, "body");
4368
+ return body === null ? null : firstInBody(body, (statement) => statedBy(statement, name));
4369
+ }
4370
+ function statedTypeName(name, from) {
4371
+ const stated = statedTypeNode(name, from);
4372
+ return stated === null ? null : typeNameOf(stated);
4373
+ }
4374
+ function statedOrigins(stated, options) {
4375
+ const name = typeNameOf(stated);
4376
+ const byRule = name === null ? [] : originsOf(options.facts, `${options.filePath}#${name}`);
4377
+ const target = annotationTarget(stated);
4378
+ const imported = target === null ? null : originOf(target, moduleOf(target));
4379
+ if (imported === null || imported.module === UNDECLARED_MODULE) {
4380
+ return byRule;
4381
+ }
4382
+ return [...byRule, imported];
4383
+ }
4384
+ function receiverTypeOrigins(receiver, options) {
4385
+ const stated = statedTypeNode(receiver.text, receiver);
4386
+ if (stated !== null) {
4387
+ return statedOrigins(stated, options);
4388
+ }
4389
+ const built = constructionBehind(receiver, options.facts);
4390
+ if (built.type === "oneCall") {
4391
+ return [built.construction.origin];
4392
+ }
4393
+ const key = nameKeyIn(
4394
+ options.filePath,
4395
+ enclosingFunction(receiver),
4396
+ receiver.text
4397
+ );
4398
+ return subjectConstructions(options.facts, [key]).get(key)?.origins ?? [];
4399
+ }
4400
+
4401
+ // src/rawSql.ts
4402
+ function rawSqlEffects(calls, options) {
4403
+ return matchesIn(calls, options).flatMap(
4404
+ (match) => match.accesses.flatMap((access) => effectFor(match, access))
4405
+ );
4406
+ }
4407
+ function rawSqlCallIds(calls, options) {
4408
+ return new Set(matchesIn(calls, options).map((match) => match.call.id));
4409
+ }
4410
+ function matchesIn(calls, options) {
4411
+ if (options.patterns.length === 0 && (options.clients ?? []).length === 0) {
4412
+ return [];
4413
+ }
4414
+ const matched = [];
4415
+ for (const call of calls) {
4416
+ const one = importedFunctionMatch(call, options) ?? clientMatch(call, options);
4417
+ if (one !== null) {
4418
+ matched.push(one);
4419
+ }
4420
+ }
4421
+ return matched;
4422
+ }
4423
+ function effectFor(match, access) {
4424
+ const operation = field(match.call, "function")?.text ?? "";
4425
+ return [
4426
+ {
4427
+ type: "interaction",
4428
+ binding: (0, import_ir_core.storageBinding)({
4429
+ recognition: match.recognition,
4430
+ storageSystem: match.storageSystem,
4431
+ scope: access.qualifier[access.qualifier.length - 1] ?? NO_GROUP,
4432
+ container: access.table
4433
+ }),
4434
+ callee: operation,
4435
+ interaction: {
4436
+ class: "storage-access",
4437
+ kind: access.kind,
4438
+ fields: access.fields,
4439
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
4440
+ operation
4441
+ }
4442
+ }
4443
+ ];
4444
+ }
4445
+ var NO_GROUP = "default";
4446
+ function importedFunctionMatch(call, options) {
3966
4447
  const callee = field(call, "function");
3967
4448
  if (callee === null || callee.type !== "identifier") {
3968
- return void 0;
4449
+ return null;
3969
4450
  }
3970
4451
  const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
3971
4452
  if (from === void 0) {
3972
- return void 0;
4453
+ return null;
3973
4454
  }
3974
4455
  const module2 = String(from[1]);
3975
- return options.patterns.find(
3976
- (pattern) => pattern.module === module2 && pattern.functions.includes(callee.text)
4456
+ const pattern = options.patterns.find(
4457
+ (candidate) => candidate.module === module2 && candidate.functions.includes(callee.text)
3977
4458
  );
3978
- }
3979
- function statementIn(call, facts) {
4459
+ if (pattern === void 0) {
4460
+ return null;
4461
+ }
3980
4462
  const args = field(call, "arguments");
3981
4463
  const first = args?.namedChildren.find((child) => child !== null) ?? null;
3982
- if (first === null) {
4464
+ const statement = first === null ? null : statementAt(first, void 0, options.facts);
4465
+ if (statement === null) {
3983
4466
  return null;
3984
4467
  }
3985
- const parts = literalParts(evaluatedValue(first, facts));
4468
+ return {
4469
+ call,
4470
+ recognition: `python-${pattern.module}`,
4471
+ storageSystem: pattern.storageSystem,
4472
+ accesses: (0, import_sql.readSqlAccess)(statement, { dialect: pattern.storageSystem })
4473
+ };
4474
+ }
4475
+ function clientMatch(call, options) {
4476
+ const callee = field(call, "function");
4477
+ if (callee === null || callee.type !== "attribute") {
4478
+ return null;
4479
+ }
4480
+ const method = field(callee, "attribute")?.text ?? "";
4481
+ const receiver = field(callee, "object");
4482
+ if (receiver === null) {
4483
+ return null;
4484
+ }
4485
+ for (const pattern of options.clients ?? []) {
4486
+ const accesses = accessesOf(call, method, pattern, options);
4487
+ if (accesses === null || !isClientOf(receiver, pattern, options)) {
4488
+ continue;
4489
+ }
4490
+ return {
4491
+ call,
4492
+ recognition: `python-${pattern.module}`,
4493
+ storageSystem: pattern.storageSystem,
4494
+ accesses
4495
+ };
4496
+ }
4497
+ return null;
4498
+ }
4499
+ function accessesOf(call, method, pattern, options) {
4500
+ const dialect = pattern.dialect ?? pattern.storageSystem;
4501
+ for (const takes of pattern.statements ?? []) {
4502
+ if (takes.method !== method) {
4503
+ continue;
4504
+ }
4505
+ const written = argumentNode(call, takes);
4506
+ const statement = written === null ? null : statementAt(written, takes.path, options.facts);
4507
+ return statement === null ? [] : (0, import_sql.readSqlAccess)(statement, { dialect });
4508
+ }
4509
+ for (const takes of pattern.tables ?? []) {
4510
+ if (takes.method !== method) {
4511
+ continue;
4512
+ }
4513
+ const written = argumentNode(call, takes);
4514
+ const name = written === null ? null : stringValueOf(written, options.facts);
4515
+ const split = name === null ? null : (0, import_sql.splitQualifiedTable)(name);
4516
+ return split === null ? [] : [{ ...split, kind: takes.kind, fields: [], selector: [] }];
4517
+ }
4518
+ return null;
4519
+ }
4520
+ function isClientOf(receiver, pattern, options) {
4521
+ return receiverOrigins(receiver, options).some(
4522
+ (origin) => origin.module === pattern.module && pattern.clientTypes.includes(origin.name)
4523
+ );
4524
+ }
4525
+ function receiverOrigins(receiver, options) {
4526
+ if (receiver.type === "identifier") {
4527
+ return receiverTypeOrigins(receiver, options);
4528
+ }
4529
+ return receiver.type === "call" ? handedBack(receiver, options) : [];
4530
+ }
4531
+ function handedBack(call, options) {
4532
+ const callee = field(call, "function");
4533
+ if (callee === null || callee.type !== "attribute") {
4534
+ return [];
4535
+ }
4536
+ const method = field(callee, "attribute")?.text ?? "";
4537
+ const inner = field(callee, "object");
4538
+ if (inner === null) {
4539
+ return [];
4540
+ }
4541
+ const found = [];
4542
+ for (const pattern of options.clients ?? []) {
4543
+ const handoff = (pattern.handsBack ?? []).find(
4544
+ (one) => one.method === method
4545
+ );
4546
+ if (handoff !== void 0 && isClientOf(inner, pattern, options)) {
4547
+ found.push({ module: handoff.module, name: handoff.name });
4548
+ }
4549
+ }
4550
+ return found;
4551
+ }
4552
+ function argumentNode(call, at) {
4553
+ const args = field(call, "arguments");
4554
+ const written = args === null ? [] : children(args);
4555
+ const named = written.find(
4556
+ (one) => one.type === "keyword_argument" && at.keyword !== void 0 && field(one, "name")?.text === at.keyword
4557
+ );
4558
+ const value = named === void 0 ? null : field(named, "value");
4559
+ if (value !== null) {
4560
+ return value;
4561
+ }
4562
+ if (at.argument === void 0) {
4563
+ return null;
4564
+ }
4565
+ return written.filter((one) => one.type !== "keyword_argument")[at.argument] ?? null;
4566
+ }
4567
+ function statementAt(node, path6, facts) {
4568
+ const reached = valueAt(evaluatedValue(node, facts), path6 ?? []);
4569
+ const parts = reached === null ? null : literalParts(reached);
3986
4570
  if (parts === null) {
3987
4571
  return null;
3988
4572
  }
3989
4573
  const statement = (0, import_sql.sqlFromParts)(parts);
3990
4574
  return statement.trim() === "" ? null : statement;
3991
4575
  }
4576
+ function valueAt(value, path6) {
4577
+ let reached = value;
4578
+ for (const key of path6) {
4579
+ if (reached.kind !== "record") {
4580
+ return null;
4581
+ }
4582
+ const item = reached.fields.get(key);
4583
+ if (item === void 0) {
4584
+ return null;
4585
+ }
4586
+ reached = (0, import_values11.force)(item.value);
4587
+ }
4588
+ return reached;
4589
+ }
3992
4590
  function literalParts(value) {
3993
4591
  if (value.kind !== "string") {
3994
4592
  return null;
@@ -4086,78 +4684,7 @@ function receiverName(chain) {
4086
4684
  return null;
4087
4685
  }
4088
4686
  const object = field(callee, "object");
4089
- return object !== null && object.type === "identifier" ? object.text : null;
4090
- }
4091
- function typeNameOf(annotation) {
4092
- if (annotation.type === "type" && annotation.namedChildren[0]) {
4093
- return typeNameOf(annotation.namedChildren[0]);
4094
- }
4095
- if (annotation.type === "identifier") {
4096
- return annotation.text;
4097
- }
4098
- const quoted = stringLiteralValue(annotation);
4099
- if (quoted !== null) {
4100
- return quoted;
4101
- }
4102
- if (annotation.type === "binary_operator") {
4103
- const named = [field(annotation, "left"), field(annotation, "right")].find(
4104
- (side) => side !== null && side.type !== "none"
4105
- );
4106
- return named === void 0 || named === null ? null : typeNameOf(named);
4107
- }
4108
- if (annotation.type === "generic_type") {
4109
- const outer = annotation.namedChildren[0];
4110
- const first = genericTypeArgs(annotation)[0];
4111
- if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4112
- return typeNameOf(first);
4113
- }
4114
- return outer?.type === "identifier" ? outer.text : null;
4115
- }
4116
- return null;
4117
- }
4118
- function statedTypeName(name, from) {
4119
- const fn = enclosingFunction(from);
4120
- if (fn === null) {
4121
- return null;
4122
- }
4123
- const params = field(fn, "parameters");
4124
- for (const param of params === null ? [] : children(params)) {
4125
- const info = parameterNameAndType(param);
4126
- if (info?.name === name && info.typeNode !== null) {
4127
- return typeNameOf(info.typeNode);
4128
- }
4129
- }
4130
- const body = field(fn, "body");
4131
- return body === null ? null : firstInBody(body, (statement) => statedBy(statement, name));
4132
- }
4133
- function firstInBody(node, read) {
4134
- const here = read(node);
4135
- if (here !== null) {
4136
- return here;
4137
- }
4138
- for (const child of children(node)) {
4139
- if (child.type === "function_definition" || child.type === "lambda") {
4140
- continue;
4141
- }
4142
- const found = firstInBody(child, read);
4143
- if (found !== null) {
4144
- return found;
4145
- }
4146
- }
4147
- return null;
4148
- }
4149
- function statedBy(statement, name) {
4150
- if (statement.type === "assignment") {
4151
- const annotation = field(statement, "type");
4152
- return field(statement, "left")?.text === name && annotation !== null ? typeNameOf(annotation) : null;
4153
- }
4154
- if (statement.type === "as_pattern") {
4155
- const alias = field(statement, "alias");
4156
- const value = statement.namedChildren[0] ?? null;
4157
- const callee = value?.type === "call" ? field(value, "function") : null;
4158
- return alias?.text === name && callee?.type === "identifier" ? callee.text : null;
4159
- }
4160
- return null;
4687
+ return object !== null && object.type === "identifier" ? object : null;
4161
4688
  }
4162
4689
  function builtType(call, options) {
4163
4690
  const root = rootOf(call);
@@ -4205,7 +4732,7 @@ function typedReceiverPattern(options, chain) {
4205
4732
  if (receiver === null) {
4206
4733
  return void 0;
4207
4734
  }
4208
- for (const origin of receiverTypeOrigins(options, receiver, chain.root)) {
4735
+ for (const origin of receiverTypeOrigins(receiver, options)) {
4209
4736
  const pattern = queryTypePattern(options, origin.module, origin.name);
4210
4737
  if (pattern !== void 0) {
4211
4738
  return pattern;
@@ -4213,14 +4740,6 @@ function typedReceiverPattern(options, chain) {
4213
4740
  }
4214
4741
  return void 0;
4215
4742
  }
4216
- function receiverTypeOrigins(options, name, from) {
4217
- const stated = statedTypeName(name, from);
4218
- if (stated !== null) {
4219
- return originsOf(options.facts, `${options.filePath}#${stated}`);
4220
- }
4221
- const key = nameKeyIn(options.filePath, enclosingFunction(from), name);
4222
- return subjectConstructions(options.facts, [key]).get(key)?.origins ?? [];
4223
- }
4224
4743
  function queryTypePattern(options, module2, name) {
4225
4744
  return options.patterns.find(
4226
4745
  (pattern) => module2 === pattern.module && pattern.queryTypes.includes(name)
@@ -4342,7 +4861,7 @@ function classBehind(typeName, options) {
4342
4861
  function isClassName(name) {
4343
4862
  return /^[A-Z][A-Za-z0-9_]*$/.test(name) && name !== name.toUpperCase();
4344
4863
  }
4345
- function effectFor(pattern, chain, operation, options) {
4864
+ function effectFor2(pattern, chain, operation, options) {
4346
4865
  const valueMethods = pattern.valueMethods ?? [];
4347
4866
  const picked = selectorOf(chain, valueMethods);
4348
4867
  return {
@@ -4452,7 +4971,7 @@ function storageCallIds(calls, options) {
4452
4971
  }
4453
4972
  function storageEffects(calls, options) {
4454
4973
  return matchedChains(calls, options).flatMap(
4455
- ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation, options)]
4974
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor2(pattern, chain, operation, options)]
4456
4975
  );
4457
4976
  }
4458
4977
 
@@ -4487,8 +5006,10 @@ function decoratedStatements(root, module2, facts) {
4487
5006
  }));
4488
5007
  }
4489
5008
  function discoverUnits(root, module2, options) {
5009
+ const decorated = decoratedStatements(root, module2, options.facts);
5010
+ askWrittenValues(clientCallReceivers(root, module2), options.facts);
4490
5011
  return [
4491
- ...decoratedStatements(root, module2, options.facts).flatMap(
5012
+ ...decorated.flatMap(
4492
5013
  (decoratedStatement) => decoratedUnits(decoratedStatement, module2, options)
4493
5014
  ),
4494
5015
  ...clientUnits(root, module2, options)
@@ -4584,7 +5105,7 @@ function readPathArgument(classification, options) {
4584
5105
  const first = classification.args[0];
4585
5106
  if (first !== void 0) {
4586
5107
  const value = evaluatedValue(first.node, options.facts);
4587
- const path6 = (0, import_values10.literalOf)(value) ?? (0, import_values10.pathOf)(value);
5108
+ const path6 = (0, import_values13.literalOf)(value) ?? (0, import_values13.pathOf)(value);
4588
5109
  if (path6 !== void 0) {
4589
5110
  return (0, import_extractor5.writtenReading)(path6, classification.range);
4590
5111
  }
@@ -4831,11 +5352,11 @@ function statusFromReturnedValue(node, facts) {
4831
5352
  return null;
4832
5353
  }
4833
5354
  const value = evaluatedValue(node, facts);
4834
- const number = (0, import_values10.constantOf)(value);
5355
+ const number = (0, import_values13.constantOf)(value);
4835
5356
  if (typeof number === "number") {
4836
5357
  return Number.isInteger(number) ? number : null;
4837
5358
  }
4838
- const text2 = (0, import_values10.literalOf)(value);
5359
+ const text2 = (0, import_values13.literalOf)(value);
4839
5360
  if (text2 === null) {
4840
5361
  return null;
4841
5362
  }
@@ -5178,7 +5699,12 @@ function buildRouteUnit(options) {
5178
5699
  () => readReturnAnnotation(definitionNode, enclosingScope, ctx)
5179
5700
  );
5180
5701
  const effects = invocationEffects(definitionNode, options.facts);
5181
- const extra = recognizedBodyEffects(definitionNode, module2, storageLookup);
5702
+ const extra = recognizedBodyEffects(
5703
+ definitionNode,
5704
+ module2,
5705
+ storageLookup,
5706
+ options.facts
5707
+ );
5182
5708
  const perTerminal = branchesPerTerminal({
5183
5709
  definitionNode,
5184
5710
  effects,
@@ -5263,28 +5789,35 @@ function unreadRoleReadings(parameters, range) {
5263
5789
  )
5264
5790
  ];
5265
5791
  }
5266
- function recognizedBodyEffects(definitionNode, module2, storageLookup) {
5792
+ function recognizedBodyEffects(definitionNode, module2, storageLookup, facts) {
5267
5793
  const storage = storageLookup === void 0 ? [] : [
5268
5794
  ...storageEffects(bodyCalls(definitionNode), {
5269
5795
  ...storageLookup,
5270
5796
  filePath: storageLookup.factsPath
5271
5797
  }),
5272
- ...rawSqlEffects(bodyCalls(definitionNode), {
5273
- facts: storageLookup.facts,
5274
- filePath: storageLookup.factsPath,
5275
- patterns: storageLookup.rawSql ?? []
5276
- })
5798
+ ...rawSqlEffects(bodyCalls(definitionNode), rawSqlOf(storageLookup))
5277
5799
  ];
5278
- return [...envReadEffects(definitionNode, module2), ...storage];
5800
+ return [...envReadEffects(definitionNode, module2, facts), ...storage];
5801
+ }
5802
+ function rawSqlOf(storageLookup) {
5803
+ return {
5804
+ facts: storageLookup.facts,
5805
+ filePath: storageLookup.factsPath,
5806
+ patterns: storageLookup.rawSql ?? [],
5807
+ clients: storageLookup.sqlClients ?? []
5808
+ };
5279
5809
  }
5280
- function recognizedCallIds(definitionNode, storageLookup) {
5810
+ function recognizedCallIds(calls, storageLookup) {
5281
5811
  if (storageLookup === void 0) {
5282
5812
  return /* @__PURE__ */ new Set();
5283
5813
  }
5284
- return storageCallIds(bodyCalls(definitionNode), {
5285
- ...storageLookup,
5286
- filePath: storageLookup.factsPath
5287
- });
5814
+ return /* @__PURE__ */ new Set([
5815
+ ...storageCallIds(calls, {
5816
+ ...storageLookup,
5817
+ filePath: storageLookup.factsPath
5818
+ }),
5819
+ ...rawSqlCallIds(calls, rawSqlOf(storageLookup))
5820
+ ]);
5288
5821
  }
5289
5822
  function bodyContentOf(bodyNode) {
5290
5823
  for (const stmt of bodyStatements(bodyNode)) {
@@ -5717,11 +6250,14 @@ function resolveCallee2(call, site, ctx, read) {
5717
6250
  const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution5.calleeOutcomeOf)(ctx.facts, spelling.key);
5718
6251
  return asCallee(outcome, site.owner, ctx);
5719
6252
  }
5720
- function functionNamed(nameKey2, ctx) {
6253
+ function namedOutcomes(keys, ctx) {
6254
+ return (0, import_resolution5.calleeOutcomes)(ctx.facts, [...new Set(keys)]);
6255
+ }
6256
+ function functionNamed(nameKey2, ctx, read) {
5721
6257
  if (assignedElsewhere(ctx.facts, nameKey2)) {
5722
6258
  return null;
5723
6259
  }
5724
- const outcome = (0, import_resolution5.calleeOutcomeOf)(ctx.facts, nameKey2);
6260
+ const outcome = read?.get(nameKey2) ?? (0, import_resolution5.calleeOutcomeOf)(ctx.facts, nameKey2);
5725
6261
  return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
5726
6262
  }
5727
6263
  function assignedElsewhere(facts, nameKey2) {
@@ -5929,6 +6465,7 @@ function reachedFunctions(seeds, options) {
5929
6465
  const stopsByKey = /* @__PURE__ */ new Map();
5930
6466
  const argTargetsByKey = /* @__PURE__ */ new Map();
5931
6467
  const parameterCallsByKey = /* @__PURE__ */ new Map();
6468
+ const callsByKey = /* @__PURE__ */ new Map();
5932
6469
  const passedPositions = /* @__PURE__ */ new Set();
5933
6470
  for (const seed of seeds) {
5934
6471
  seedKeys.add(seed.key);
@@ -5955,12 +6492,21 @@ function reachedFunctions(seeds, options) {
5955
6492
  bodies.flatMap((body) => body.written),
5956
6493
  ctx
5957
6494
  );
6495
+ const passed = namedOutcomes(
6496
+ bodies.flatMap(
6497
+ ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
6498
+ ),
6499
+ ctx
6500
+ );
5958
6501
  for (const { key, source, body, written } of bodies) {
5959
6502
  const scan = scanBody(
5960
6503
  source,
5961
6504
  ctx,
5962
- recognizedCallIds(source.node, options.storageFor(source.file)),
5963
- { body, written, spellings }
6505
+ recognizedCallIds(
6506
+ written.map(({ call }) => call),
6507
+ options.storageFor(source.file)
6508
+ ),
6509
+ { body, written, spellings, passed }
5964
6510
  );
5965
6511
  if (scan.stops.length > 0) {
5966
6512
  stopsByKey.set(key, scan.stops);
@@ -5973,21 +6519,27 @@ function reachedFunctions(seeds, options) {
5973
6519
  for (const position of scan.passedPositions) {
5974
6520
  passedPositions.add(position);
5975
6521
  }
6522
+ const called = [];
5976
6523
  for (const target of scan.followed) {
5977
- const calleeKey = keyOf(target);
6524
+ const calleeKey = keyOf2(target);
5978
6525
  if (!functionByKey.has(calleeKey)) {
5979
6526
  functionByKey.set(calleeKey, target);
5980
6527
  }
5981
6528
  db.add("calls", [key, calleeKey]);
6529
+ called.push(calleeKey);
6530
+ }
6531
+ if (called.length > 0) {
6532
+ callsByKey.set(key, called);
5982
6533
  }
5983
6534
  }
5984
6535
  }
6536
+ const reached = db.facts("reachable").map(([keyAtom]) => String(keyAtom)).filter((key) => !seedKeys.has(key));
6537
+ settleBodyValues(reached, functionByKey, options.facts);
5985
6538
  const summaries = [];
5986
6539
  const summariesByKey = /* @__PURE__ */ new Map();
5987
- for (const [keyAtom] of db.facts("reachable")) {
5988
- const key = String(keyAtom);
6540
+ for (const key of reached) {
5989
6541
  const target = functionByKey.get(key);
5990
- if (seedKeys.has(key) || target === void 0) {
6542
+ if (target === void 0) {
5991
6543
  continue;
5992
6544
  }
5993
6545
  const summary = (0, import_extractor7.assembleSummary)(libraryUnit(target, options), {
@@ -6012,10 +6564,11 @@ function reachedFunctions(seeds, options) {
6012
6564
  stopsByKey,
6013
6565
  argTargetsByKey,
6014
6566
  parameterCallsByKey,
6567
+ callsByKey,
6015
6568
  passedPositions
6016
6569
  };
6017
6570
  }
6018
- function keyOf(target) {
6571
+ function keyOf2(target) {
6019
6572
  return nodeId(target.file.file, target.node);
6020
6573
  }
6021
6574
  var EMPTY_SCAN = {
@@ -6028,15 +6581,53 @@ var EMPTY_SCAN = {
6028
6581
  };
6029
6582
  function bodyOf(source) {
6030
6583
  const { file, node } = source;
6031
- const body = field(node, "body");
6584
+ const body = isModule(node) ? node : field(node, "body");
6032
6585
  if (body === null) {
6033
6586
  return { body, written: [] };
6034
6587
  }
6035
6588
  return {
6036
6589
  body,
6037
- written: callsWritten(file, body, scopeAt2(file, node), keyOf(source))
6590
+ written: callsWritten(file, body, scopeAt2(file, node), keyOf2(source), {
6591
+ descendsInto: isModule(node) ? runsAtModuleLoad : outsideNestedDef
6592
+ })
6038
6593
  };
6039
6594
  }
6595
+ function outsideNestedDef(node) {
6596
+ return node.type !== "function_definition";
6597
+ }
6598
+ function settleBodyValues(reached, functionByKey, facts) {
6599
+ const byFile = /* @__PURE__ */ new Map();
6600
+ for (const key of reached) {
6601
+ const target = functionByKey.get(key);
6602
+ if (target === void 0) {
6603
+ continue;
6604
+ }
6605
+ const listed = byFile.get(target.file.file) ?? [];
6606
+ for (const node of bodyValueNodes(target.node)) {
6607
+ listed.push(node);
6608
+ }
6609
+ byFile.set(target.file.file, listed);
6610
+ }
6611
+ for (const nodes of byFile.values()) {
6612
+ askWrittenValues(nodes, facts);
6613
+ }
6614
+ }
6615
+ function passedNameKeyOf(file, arg) {
6616
+ if (arg === null || arg.type !== "identifier") {
6617
+ return null;
6618
+ }
6619
+ return readKey(file.file, arg, enclosingFunction(arg));
6620
+ }
6621
+ function passedNameKeys(file, call) {
6622
+ const args = field(call, "arguments");
6623
+ if (args === null) {
6624
+ return [];
6625
+ }
6626
+ return args.namedChildren.flatMap((arg) => {
6627
+ const key = passedNameKeyOf(file, arg);
6628
+ return key === null ? [] : [key];
6629
+ });
6630
+ }
6040
6631
  function scanBody(source, ctx, recognized, read) {
6041
6632
  const followed = [];
6042
6633
  const stops = [];
@@ -6057,17 +6648,15 @@ function scanBody(source, ctx, recognized, read) {
6057
6648
  return;
6058
6649
  }
6059
6650
  args.namedChildren.forEach((arg, position) => {
6060
- if (arg === null || arg.type !== "identifier") {
6651
+ const nameKey2 = passedNameKeyOf(file, arg);
6652
+ if (nameKey2 === null) {
6061
6653
  return;
6062
6654
  }
6063
- const resolved = functionNamed(
6064
- readKey(file.file, arg, enclosingFunction(arg)),
6065
- ctx
6066
- );
6655
+ const resolved = functionNamed(nameKey2, ctx, read.passed);
6067
6656
  if (resolved === null) {
6068
6657
  return;
6069
6658
  }
6070
- const key = keyOf(resolved);
6659
+ const key = keyOf2(resolved);
6071
6660
  if (!seen.has(key)) {
6072
6661
  seen.add(key);
6073
6662
  followed.push(resolved);
@@ -6092,7 +6681,7 @@ function scanBody(source, ctx, recognized, read) {
6092
6681
  recordPassedArgs(
6093
6682
  call,
6094
6683
  callee,
6095
- outcome.kind === "followed" ? keyOf(outcome.target) : null
6684
+ outcome.kind === "followed" ? keyOf2(outcome.target) : null
6096
6685
  );
6097
6686
  if (outcome.kind === "stopped") {
6098
6687
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6109,7 +6698,7 @@ function scanBody(source, ctx, recognized, read) {
6109
6698
  }
6110
6699
  return;
6111
6700
  }
6112
- const key = keyOf(outcome.target);
6701
+ const key = keyOf2(outcome.target);
6113
6702
  if (!seen.has(key)) {
6114
6703
  seen.add(key);
6115
6704
  followed.push(outcome.target);
@@ -6127,7 +6716,7 @@ function scanBody(source, ctx, recognized, read) {
6127
6716
  passedPositions
6128
6717
  };
6129
6718
  }
6130
- function callsWritten(file, body, outer, owner) {
6719
+ function callsWritten(file, body, outer, owner, limit) {
6131
6720
  const found = [];
6132
6721
  (0, import_extractor7.walkDescendants)(body, outer, {
6133
6722
  at: (child, scope) => {
@@ -6135,7 +6724,7 @@ function callsWritten(file, body, outer, owner) {
6135
6724
  found.push({ call: child, site: { file, scope, owner } });
6136
6725
  }
6137
6726
  },
6138
- into: (child, scope) => child.type === "function_definition" ? import_extractor7.SKIP_CHILDREN : file.module.scopeFor.get(child.id) ?? scope
6727
+ into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : import_extractor7.SKIP_CHILDREN
6139
6728
  });
6140
6729
  return found;
6141
6730
  }
@@ -6168,7 +6757,8 @@ function libraryUnit(target, options) {
6168
6757
  const extra = recognizedBodyEffects(
6169
6758
  node,
6170
6759
  file.module,
6171
- options.storageFor(file)
6760
+ options.storageFor(file),
6761
+ options.facts
6172
6762
  );
6173
6763
  const range = rangeOf(node);
6174
6764
  return {
@@ -7749,7 +8339,8 @@ function wrapperUnit(target, declared, options) {
7749
8339
  const extra = recognizedBodyEffects(
7750
8340
  node,
7751
8341
  file.module,
7752
- options.storageFor(file)
8342
+ options.storageFor(file),
8343
+ options.facts
7753
8344
  );
7754
8345
  const delegate = (at) => ({
7755
8346
  terminal: {
@@ -7907,6 +8498,7 @@ function factsForFile(options) {
7907
8498
  files: [{ file: options.file, root: options.root, module: options.module }],
7908
8499
  definitions
7909
8500
  });
8501
+ bindEnvFacts(db, [envFactsIn(options.file, options.root, options.module)]);
7910
8502
  (0, import_resolution7.addPackWords)(db, packWordsOf(options.packs));
7911
8503
  return db;
7912
8504
  }
@@ -7957,6 +8549,7 @@ async function extractPythonProject(options) {
7957
8549
  );
7958
8550
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
7959
8551
  const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
8552
+ const sqlClients = options.packs.flatMap((pack) => pack.sqlClients ?? []);
7960
8553
  const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
7961
8554
  const discovers = options.packs.some((pack) => pack.discovery.length > 0);
7962
8555
  const buildsReceivers = options.packs.some(
@@ -7964,7 +8557,15 @@ async function extractPythonProject(options) {
7964
8557
  (client) => (client.receiverConstructors ?? []).length > 0
7965
8558
  )
7966
8559
  );
7967
- const needsValues = discovers || mountsRouters || buildsReceivers || storagePatterns.length > 0 || modelQueries.length > 0;
8560
+ const envFacts = bound.map(
8561
+ (boundFile) => envFactsIn(boundFile.file, boundFile.root, boundFile.module)
8562
+ );
8563
+ const readsEnvThroughNames = envFacts.some(
8564
+ (one) => one.sites.length > 0 || one.objects.length > 0
8565
+ );
8566
+ 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
8567
+ // call that hands it over is read off.
8568
+ rawSqlPatterns.length > 0 || sqlClients.length > 0;
7968
8569
  const definitions = /* @__PURE__ */ new Map();
7969
8570
  timer.time("discover", () => {
7970
8571
  for (const { file, root, module: moduleBinding } of bound) {
@@ -7976,6 +8577,7 @@ async function extractPythonProject(options) {
7976
8577
  }
7977
8578
  if (needsValues) {
7978
8579
  bindEvaluator(db, { files: bound, definitions });
8580
+ bindEnvFacts(db, envFacts);
7979
8581
  }
7980
8582
  (0, import_resolution7.addPackWords)(db, packWordsOf(options.packs));
7981
8583
  });
@@ -7991,13 +8593,14 @@ async function extractPythonProject(options) {
7991
8593
  ...mountsRouters ? { facts: db } : {}
7992
8594
  })
7993
8595
  );
7994
- const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
8596
+ const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 || sqlClients.length > 0 ? {
7995
8597
  facts: db,
7996
8598
  factsPath: file.file,
7997
8599
  patterns: storagePatterns,
7998
8600
  definitionAt: (key) => definitions.get(key),
7999
8601
  couldMatch,
8000
- rawSql: rawSqlPatterns
8602
+ rawSql: rawSqlPatterns,
8603
+ sqlClients
8001
8604
  } : void 0;
8002
8605
  const wrapperIndex = timer.time(
8003
8606
  "discover",
@@ -8032,6 +8635,7 @@ async function extractPythonProject(options) {
8032
8635
  }
8033
8636
  const seeds = [];
8034
8637
  const summariesBySeed = /* @__PURE__ */ new Map();
8638
+ const moduleRoots = [];
8035
8639
  for (const boundFile of bound) {
8036
8640
  const { file, root, module: moduleBinding } = boundFile;
8037
8641
  const displayPath = displayPathOf(file);
@@ -8064,23 +8668,17 @@ async function extractPythonProject(options) {
8064
8668
  }
8065
8669
  const loadTimeReads = timer.time(
8066
8670
  "discover",
8067
- () => envReadEffects(root, moduleBinding)
8671
+ () => envReadEffects(root, moduleBinding, db)
8068
8672
  );
8069
- if (loadTimeReads.length > 0) {
8070
- const summary = timer.time(
8071
- "summarize",
8072
- () => (0, import_extractor10.assembleSummary)(
8073
- (0, import_extractor10.moduleInitStructure)({
8074
- name: import_node_path3.default.basename(displayPath),
8075
- file: displayPath,
8076
- range: rangeOf(root),
8077
- effects: loadTimeReads
8078
- }),
8079
- { gapHandling }
8080
- )
8081
- );
8082
- summary.confidence = { source: "inferred_static", level: "low" };
8083
- summaries.push(summary);
8673
+ const moduleKey = nodeId(file, root);
8674
+ if (!summariesBySeed.has(moduleKey)) {
8675
+ seeds.push({ key: moduleKey, file: boundFile, node: root });
8676
+ moduleRoots.push({
8677
+ boundFile,
8678
+ displayPath,
8679
+ key: moduleKey,
8680
+ loadTimeReads
8681
+ });
8084
8682
  }
8085
8683
  }
8086
8684
  const reached = timer.time(
@@ -8094,16 +8692,19 @@ async function extractPythonProject(options) {
8094
8692
  definitions
8095
8693
  })
8096
8694
  );
8695
+ const placeWhatItReached = (summary, key) => {
8696
+ if (gapHandling !== "silent") {
8697
+ summary.gaps.push(
8698
+ ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir6.unfollowedCallGap)
8699
+ );
8700
+ }
8701
+ (0, import_behavioral_ir6.placeCalls)(summary, reached.targetsByKey.get(key));
8702
+ (0, import_behavioral_ir6.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
8703
+ (0, import_behavioral_ir6.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
8704
+ };
8097
8705
  for (const [key, owners] of summariesBySeed) {
8098
8706
  for (const summary of owners) {
8099
- if (gapHandling !== "silent") {
8100
- summary.gaps.push(
8101
- ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir6.unfollowedCallGap)
8102
- );
8103
- }
8104
- (0, import_behavioral_ir6.placeCalls)(summary, reached.targetsByKey.get(key));
8105
- (0, import_behavioral_ir6.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
8106
- (0, import_behavioral_ir6.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
8707
+ placeWhatItReached(summary, key);
8107
8708
  }
8108
8709
  }
8109
8710
  if (gapHandling !== "silent") {
@@ -8113,6 +8714,30 @@ async function extractPythonProject(options) {
8113
8714
  reached.passedPositions
8114
8715
  );
8115
8716
  }
8717
+ for (const { boundFile, displayPath, key, loadTimeReads } of moduleRoots) {
8718
+ const calledAtLoad = reached.callsByKey.get(key) ?? [];
8719
+ if (loadTimeReads.length === 0 && calledAtLoad.length === 0) {
8720
+ continue;
8721
+ }
8722
+ const summary = timer.time(
8723
+ "summarize",
8724
+ () => (0, import_extractor10.assembleSummary)(
8725
+ (0, import_extractor10.moduleInitStructure)({
8726
+ name: import_node_path3.default.basename(displayPath),
8727
+ file: displayPath,
8728
+ range: rangeOf(boundFile.root),
8729
+ effects: [
8730
+ ...loadTimeReads,
8731
+ ...moduleLoadInvocationEffects(boundFile.root, db).map(import_extractor10.effectToIR)
8732
+ ]
8733
+ }),
8734
+ { gapHandling }
8735
+ )
8736
+ );
8737
+ summary.confidence = { source: "inferred_static", level: "low" };
8738
+ placeWhatItReached(summary, key);
8739
+ summaries.push(summary);
8740
+ }
8116
8741
  summaries.push(...reached.summaries);
8117
8742
  const resolvedImports = resolvedImportsOf(db, displayPathOf);
8118
8743
  (0, import_extractor10.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);