@suss/adapter-python 0.32.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,8 +790,14 @@ var import_resolution = require("@suss/resolution");
784
790
  function resolveCalls(db, callKeys) {
785
791
  (0, import_resolution.askResolution)(db, callKeys);
786
792
  }
787
- function resolveEnvSites(db, siteIds) {
788
- (0, import_resolution.askResolution)(db, siteIds, "wantedEnvSite");
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]));
789
801
  }
790
802
  function writtenValueUnder(db, key, site) {
791
803
  if ((0, import_resolution.askResolutionUnder)(db, [[key, site]]) === "abandoned") {
@@ -805,6 +817,13 @@ function writtenValuesOf(db, key) {
805
817
  resolveCalls(db, [key]);
806
818
  return (0, import_resolution.writtenValuesOf)(db, key, (keys) => resolveCalls(db, keys));
807
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
+ }
808
827
  function originsOf(db, nameKey2) {
809
828
  (0, import_resolution.askResolution)(db, [nameKey2], "wantedOrigin");
810
829
  return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey2).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
@@ -1101,9 +1120,51 @@ function walkExpressions(emitter, node, visit) {
1101
1120
  walkExpressions(emitter, child, visit);
1102
1121
  }
1103
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
+ }
1104
1154
  function emitExpressionFact(emitter, child) {
1105
1155
  if (child.type === "call") {
1106
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
+ }
1107
1168
  }
1108
1169
  if (child.type === "dictionary") {
1109
1170
  emitDictionary(emitter, child);
@@ -1171,6 +1232,9 @@ function emitFunctionFacts(emitter, fn, classKey) {
1171
1232
  return funcKey;
1172
1233
  }
1173
1234
  emitNestedDefinitions(inside, body);
1235
+ if (fn.type === "lambda") {
1236
+ emitExpressionFact(inside, body);
1237
+ }
1174
1238
  let statesReturn = false;
1175
1239
  const stores = /* @__PURE__ */ new Map();
1176
1240
  walkExpressions(inside, body, (child) => {
@@ -2567,8 +2631,17 @@ function askWrittenValues(nodes, db) {
2567
2631
  if (bound === void 0) {
2568
2632
  return;
2569
2633
  }
2570
- const keys = nodes.map((node) => bound.keyOf(node)).filter((key) => key !== null);
2571
- 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];
2572
2645
  }
2573
2646
  function evaluatedValue(node, db, site) {
2574
2647
  const evaluator = evaluatorFor(node, db);
@@ -2636,7 +2709,7 @@ function projectOver(db, nodes) {
2636
2709
  filesByRoot.set(entry.root.id, entry);
2637
2710
  rootsByFile.set(entry.file, entry.root);
2638
2711
  }
2639
- const keyOf2 = (node) => {
2712
+ const keyOf3 = (node) => {
2640
2713
  const entry = filesByRoot.get(node.tree.rootNode.id);
2641
2714
  if (entry === void 0) {
2642
2715
  return null;
@@ -2645,7 +2718,7 @@ function projectOver(db, nodes) {
2645
2718
  };
2646
2719
  const context = {
2647
2720
  writtenTo: (node, site) => {
2648
- const key = keyOf2(node);
2721
+ const key = keyOf3(node);
2649
2722
  if (key === null) {
2650
2723
  return null;
2651
2724
  }
@@ -2654,17 +2727,17 @@ function projectOver(db, nodes) {
2654
2727
  },
2655
2728
  callable: (call) => {
2656
2729
  const callee = field(call, "function");
2657
- const key = callee === null ? null : keyOf2(callee);
2730
+ const key = callee === null ? null : keyOf3(callee);
2658
2731
  if (key === null) {
2659
2732
  return null;
2660
2733
  }
2661
2734
  resolveCalls(db, [key]);
2662
- const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2663
- 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;
2664
2737
  return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
2665
2738
  }
2666
2739
  };
2667
- return { keyOf: keyOf2, roots: rootsByFile, context };
2740
+ return { keyOf: keyOf3, roots: rootsByFile, context };
2668
2741
  }
2669
2742
 
2670
2743
  // src/decorators.ts
@@ -2879,7 +2952,7 @@ function passesParametersThrough(def, inner) {
2879
2952
  // src/discovery.ts
2880
2953
  var import_behavioral_ir4 = require("@suss/behavioral-ir");
2881
2954
  var import_extractor5 = require("@suss/extractor");
2882
- var import_values11 = require("@suss/values");
2955
+ var import_values13 = require("@suss/values");
2883
2956
 
2884
2957
  // src/clientCalls.ts
2885
2958
  var import_behavioral_ir2 = require("@suss/behavioral-ir");
@@ -3292,6 +3365,18 @@ function bodyCalls(node, found = []) {
3292
3365
  }
3293
3366
  return found;
3294
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
+ }
3295
3380
  function enclosingStatement(call, body) {
3296
3381
  let current = call;
3297
3382
  while (current !== null && current.parent !== null) {
@@ -3316,15 +3401,18 @@ function argOf(node, facts) {
3316
3401
  }
3317
3402
  return { kind: "identifier", name: node.text };
3318
3403
  }
3319
- function argsOf(call, facts) {
3404
+ function argumentNodes(call) {
3320
3405
  const args = field(call, "arguments");
3321
3406
  if (args === null) {
3322
3407
  return [];
3323
3408
  }
3324
3409
  return args.namedChildren.filter((child) => child !== null).map(
3325
- (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
3326
3411
  );
3327
3412
  }
3413
+ function argsOf(call, facts) {
3414
+ return argumentNodes(call).map((node) => argOf(node, facts));
3415
+ }
3328
3416
  function calleeText(call) {
3329
3417
  return field(call, "function")?.text ?? call.text;
3330
3418
  }
@@ -3333,7 +3421,40 @@ function invocationEffects(definitionNode, facts) {
3333
3421
  if (body === null) {
3334
3422
  return [];
3335
3423
  }
3336
- 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);
3337
3458
  if (calls.length === 0) {
3338
3459
  return [];
3339
3460
  }
@@ -3355,10 +3476,17 @@ function invocationEffects(definitionNode, facts) {
3355
3476
  },
3356
3477
  statements
3357
3478
  );
3358
- return calls.map((call) => {
3479
+ const gatingOf = calls.map((call) => {
3359
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) => {
3360
3488
  const conditions = (0, import_extractor3.sharedGatingConditions)(
3361
- statement === void 0 ? void 0 : enumerated.byTerminal.get(statement),
3489
+ gatingOf[index],
3362
3490
  (condition) => predicateOf(condition, facts)
3363
3491
  );
3364
3492
  return {
@@ -3389,6 +3517,22 @@ function clientCallUnits(root, module2, pack, pattern, options) {
3389
3517
  }
3390
3518
  return units;
3391
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
+ }
3392
3536
  function functionDefinitions(node, found = []) {
3393
3537
  for (const child of children(node)) {
3394
3538
  if (isFunction(child)) {
@@ -3751,44 +3895,91 @@ function isDefaultedAt(node) {
3751
3895
  }
3752
3896
  return false;
3753
3897
  }
3754
- function envNameSites(filePath, root, module2) {
3755
- const sites = [];
3898
+ function envFactsIn(filePath, root, module2) {
3899
+ const found = { sites: [], objects: [] };
3756
3900
  (0, import_extractor4.walkDescendants)(root, module2.moduleScope, {
3757
3901
  at: (node, scope) => {
3902
+ if (isEnviron(node, scope) && handsOnward(node)) {
3903
+ found.objects.push(keyOf(filePath, node));
3904
+ }
3758
3905
  const syntax = envReadSyntaxAt(node, scope);
3759
3906
  if (syntax === null || stringLiteralValue(syntax.name) !== null) {
3760
3907
  return;
3761
3908
  }
3762
- sites.push({
3909
+ found.sites.push({
3763
3910
  site: nodeId(filePath, node),
3764
- nameKey: readKey(filePath, syntax.name, enclosingFunction(syntax.name)),
3765
- defaulted: syntax.defaulted
3911
+ nameKey: keyOf(filePath, syntax.name),
3912
+ defaulted: syntax.defaulted,
3913
+ functionKey: functionReadKey(filePath, node, scope)
3766
3914
  });
3767
3915
  },
3768
3916
  into: (node, scope) => module2.scopeFor.get(node.id) ?? scope
3769
3917
  });
3770
- return sites;
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;
3771
3951
  }
3772
3952
  var sitesByDb = /* @__PURE__ */ new WeakMap();
3773
- function bindEnvNameSites(db, sites) {
3774
- if (sites.length === 0) {
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) {
3775
3957
  return;
3776
3958
  }
3777
3959
  let index = sitesByDb.get(db);
3778
3960
  if (index === void 0) {
3779
- index = { byId: /* @__PURE__ */ new Map(), sitesByParameter: null };
3961
+ index = { byId: /* @__PURE__ */ new Map(), objects: /* @__PURE__ */ new Set(), sitesByParameter: null };
3780
3962
  sitesByDb.set(db, index);
3781
3963
  }
3964
+ for (const object of objects) {
3965
+ db.add("environmentObject", [object]);
3966
+ index.objects.add(object);
3967
+ }
3782
3968
  for (const site of sites) {
3783
- db.add("readsEnvNamed", [site.site, site.nameKey]);
3784
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);
3785
3976
  }
3786
3977
  }
3787
3978
  function sitesEachParameterNames(db, index) {
3788
3979
  if (index.sitesByParameter !== null) {
3789
3980
  return index.sitesByParameter;
3790
3981
  }
3791
- resolveEnvSites(db, [...index.byId.keys()]);
3982
+ resolveEnvObjects(db, [...index.objects]);
3792
3983
  const found = /* @__PURE__ */ new Map();
3793
3984
  for (const row of db.facts("wantedParamNamesEnv")) {
3794
3985
  const parameter = String(row[0]);
@@ -3852,8 +4043,7 @@ function argumentsByParameter(db, call, calleeKey) {
3852
4043
  positional.set(String(argument.position), argument.node);
3853
4044
  }
3854
4045
  const argumentAt2 = /* @__PURE__ */ new Map();
3855
- for (const resolved of db.lookup("wantedResolves", 0, calleeKey)) {
3856
- const callee = String(resolved[1]);
4046
+ for (const callee of resolvedFunctions(db, calleeKey)) {
3857
4047
  for (const row of db.lookup("paramOf", 0, callee)) {
3858
4048
  const at = positional.get(String(row[1]));
3859
4049
  if (at !== void 0) {
@@ -3881,10 +4071,8 @@ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
3881
4071
  continue;
3882
4072
  }
3883
4073
  for (const id of sites) {
3884
- const site = siteById.get(id);
3885
- if (site !== void 0) {
3886
- reads.set(name, (reads.get(name) ?? true) && site.defaulted);
3887
- }
4074
+ const defaulted = siteById.get(id)?.defaulted ?? false;
4075
+ reads.set(name, (reads.get(name) ?? true) && defaulted);
3888
4076
  }
3889
4077
  }
3890
4078
  if (reads.size === 0) {
@@ -4104,71 +4292,301 @@ function returnedBodyShape(statement) {
4104
4292
  // src/rawSql.ts
4105
4293
  var import_ir_core = require("@suss/ir-core");
4106
4294
  var import_sql = require("@suss/sql");
4107
- function rawSqlEffects(calls, options) {
4108
- if (options.patterns.length === 0) {
4109
- 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]);
4110
4302
  }
4111
- const effects = [];
4112
- for (const call of calls) {
4113
- const pattern = patternFor(call, options);
4114
- if (pattern === void 0) {
4115
- 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);
4116
4314
  }
4117
- const statement = statementIn(call, options.facts);
4118
- 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") {
4119
4333
  continue;
4120
4334
  }
4121
- for (const access of (0, import_sql.readSqlAccess)(statement, {
4122
- dialect: pattern.storageSystem
4123
- })) {
4124
- effects.push({
4125
- type: "interaction",
4126
- binding: (0, import_ir_core.storageBinding)({
4127
- recognition: `python-${pattern.module}`,
4128
- storageSystem: pattern.storageSystem,
4129
- scope: "default",
4130
- container: access.table
4131
- }),
4132
- callee: field(call, "function")?.text ?? "",
4133
- interaction: {
4134
- class: "storage-access",
4135
- kind: access.kind,
4136
- fields: access.fields,
4137
- ...access.selector.length > 0 ? { selector: access.selector } : {},
4138
- operation: field(call, "function")?.text ?? ""
4139
- }
4140
- });
4335
+ const found = firstInBody(child, read);
4336
+ if (found !== null) {
4337
+ return found;
4141
4338
  }
4142
4339
  }
4143
- return effects;
4340
+ return null;
4341
+ }
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
+ ];
4144
4444
  }
4145
- function patternFor(call, options) {
4445
+ var NO_GROUP = "default";
4446
+ function importedFunctionMatch(call, options) {
4146
4447
  const callee = field(call, "function");
4147
4448
  if (callee === null || callee.type !== "identifier") {
4148
- return void 0;
4449
+ return null;
4149
4450
  }
4150
4451
  const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
4151
4452
  if (from === void 0) {
4152
- return void 0;
4453
+ return null;
4153
4454
  }
4154
4455
  const module2 = String(from[1]);
4155
- return options.patterns.find(
4156
- (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)
4157
4458
  );
4158
- }
4159
- function statementIn(call, facts) {
4459
+ if (pattern === void 0) {
4460
+ return null;
4461
+ }
4160
4462
  const args = field(call, "arguments");
4161
4463
  const first = args?.namedChildren.find((child) => child !== null) ?? null;
4162
- if (first === null) {
4464
+ const statement = first === null ? null : statementAt(first, void 0, options.facts);
4465
+ if (statement === null) {
4466
+ return null;
4467
+ }
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) {
4163
4483
  return null;
4164
4484
  }
4165
- const parts = literalParts(evaluatedValue(first, facts));
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);
4166
4570
  if (parts === null) {
4167
4571
  return null;
4168
4572
  }
4169
4573
  const statement = (0, import_sql.sqlFromParts)(parts);
4170
4574
  return statement.trim() === "" ? null : statement;
4171
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
+ }
4172
4590
  function literalParts(value) {
4173
4591
  if (value.kind !== "string") {
4174
4592
  return null;
@@ -4266,78 +4684,7 @@ function receiverName(chain) {
4266
4684
  return null;
4267
4685
  }
4268
4686
  const object = field(callee, "object");
4269
- return object !== null && object.type === "identifier" ? object.text : null;
4270
- }
4271
- function typeNameOf(annotation) {
4272
- if (annotation.type === "type" && annotation.namedChildren[0]) {
4273
- return typeNameOf(annotation.namedChildren[0]);
4274
- }
4275
- if (annotation.type === "identifier") {
4276
- return annotation.text;
4277
- }
4278
- const quoted = stringLiteralValue(annotation);
4279
- if (quoted !== null) {
4280
- return quoted;
4281
- }
4282
- if (annotation.type === "binary_operator") {
4283
- const named = [field(annotation, "left"), field(annotation, "right")].find(
4284
- (side) => side !== null && side.type !== "none"
4285
- );
4286
- return named === void 0 || named === null ? null : typeNameOf(named);
4287
- }
4288
- if (annotation.type === "generic_type") {
4289
- const outer = annotation.namedChildren[0];
4290
- const first = genericTypeArgs(annotation)[0];
4291
- if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4292
- return typeNameOf(first);
4293
- }
4294
- return outer?.type === "identifier" ? outer.text : null;
4295
- }
4296
- return null;
4297
- }
4298
- function statedTypeName(name, from) {
4299
- const fn = enclosingFunction(from);
4300
- if (fn === null) {
4301
- return null;
4302
- }
4303
- const params = field(fn, "parameters");
4304
- for (const param of params === null ? [] : children(params)) {
4305
- const info = parameterNameAndType(param);
4306
- if (info?.name === name && info.typeNode !== null) {
4307
- return typeNameOf(info.typeNode);
4308
- }
4309
- }
4310
- const body = field(fn, "body");
4311
- return body === null ? null : firstInBody(body, (statement) => statedBy(statement, name));
4312
- }
4313
- function firstInBody(node, read) {
4314
- const here = read(node);
4315
- if (here !== null) {
4316
- return here;
4317
- }
4318
- for (const child of children(node)) {
4319
- if (child.type === "function_definition" || child.type === "lambda") {
4320
- continue;
4321
- }
4322
- const found = firstInBody(child, read);
4323
- if (found !== null) {
4324
- return found;
4325
- }
4326
- }
4327
- return null;
4328
- }
4329
- function statedBy(statement, name) {
4330
- if (statement.type === "assignment") {
4331
- const annotation = field(statement, "type");
4332
- return field(statement, "left")?.text === name && annotation !== null ? typeNameOf(annotation) : null;
4333
- }
4334
- if (statement.type === "as_pattern") {
4335
- const alias = field(statement, "alias");
4336
- const value = statement.namedChildren[0] ?? null;
4337
- const callee = value?.type === "call" ? field(value, "function") : null;
4338
- return alias?.text === name && callee?.type === "identifier" ? callee.text : null;
4339
- }
4340
- return null;
4687
+ return object !== null && object.type === "identifier" ? object : null;
4341
4688
  }
4342
4689
  function builtType(call, options) {
4343
4690
  const root = rootOf(call);
@@ -4385,7 +4732,7 @@ function typedReceiverPattern(options, chain) {
4385
4732
  if (receiver === null) {
4386
4733
  return void 0;
4387
4734
  }
4388
- for (const origin of receiverTypeOrigins(options, receiver, chain.root)) {
4735
+ for (const origin of receiverTypeOrigins(receiver, options)) {
4389
4736
  const pattern = queryTypePattern(options, origin.module, origin.name);
4390
4737
  if (pattern !== void 0) {
4391
4738
  return pattern;
@@ -4393,14 +4740,6 @@ function typedReceiverPattern(options, chain) {
4393
4740
  }
4394
4741
  return void 0;
4395
4742
  }
4396
- function receiverTypeOrigins(options, name, from) {
4397
- const stated = statedTypeName(name, from);
4398
- if (stated !== null) {
4399
- return originsOf(options.facts, `${options.filePath}#${stated}`);
4400
- }
4401
- const key = nameKeyIn(options.filePath, enclosingFunction(from), name);
4402
- return subjectConstructions(options.facts, [key]).get(key)?.origins ?? [];
4403
- }
4404
4743
  function queryTypePattern(options, module2, name) {
4405
4744
  return options.patterns.find(
4406
4745
  (pattern) => module2 === pattern.module && pattern.queryTypes.includes(name)
@@ -4522,7 +4861,7 @@ function classBehind(typeName, options) {
4522
4861
  function isClassName(name) {
4523
4862
  return /^[A-Z][A-Za-z0-9_]*$/.test(name) && name !== name.toUpperCase();
4524
4863
  }
4525
- function effectFor(pattern, chain, operation, options) {
4864
+ function effectFor2(pattern, chain, operation, options) {
4526
4865
  const valueMethods = pattern.valueMethods ?? [];
4527
4866
  const picked = selectorOf(chain, valueMethods);
4528
4867
  return {
@@ -4632,7 +4971,7 @@ function storageCallIds(calls, options) {
4632
4971
  }
4633
4972
  function storageEffects(calls, options) {
4634
4973
  return matchedChains(calls, options).flatMap(
4635
- ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation, options)]
4974
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor2(pattern, chain, operation, options)]
4636
4975
  );
4637
4976
  }
4638
4977
 
@@ -4667,8 +5006,10 @@ function decoratedStatements(root, module2, facts) {
4667
5006
  }));
4668
5007
  }
4669
5008
  function discoverUnits(root, module2, options) {
5009
+ const decorated = decoratedStatements(root, module2, options.facts);
5010
+ askWrittenValues(clientCallReceivers(root, module2), options.facts);
4670
5011
  return [
4671
- ...decoratedStatements(root, module2, options.facts).flatMap(
5012
+ ...decorated.flatMap(
4672
5013
  (decoratedStatement) => decoratedUnits(decoratedStatement, module2, options)
4673
5014
  ),
4674
5015
  ...clientUnits(root, module2, options)
@@ -4764,7 +5105,7 @@ function readPathArgument(classification, options) {
4764
5105
  const first = classification.args[0];
4765
5106
  if (first !== void 0) {
4766
5107
  const value = evaluatedValue(first.node, options.facts);
4767
- const path6 = (0, import_values11.literalOf)(value) ?? (0, import_values11.pathOf)(value);
5108
+ const path6 = (0, import_values13.literalOf)(value) ?? (0, import_values13.pathOf)(value);
4768
5109
  if (path6 !== void 0) {
4769
5110
  return (0, import_extractor5.writtenReading)(path6, classification.range);
4770
5111
  }
@@ -5011,11 +5352,11 @@ function statusFromReturnedValue(node, facts) {
5011
5352
  return null;
5012
5353
  }
5013
5354
  const value = evaluatedValue(node, facts);
5014
- const number = (0, import_values11.constantOf)(value);
5355
+ const number = (0, import_values13.constantOf)(value);
5015
5356
  if (typeof number === "number") {
5016
5357
  return Number.isInteger(number) ? number : null;
5017
5358
  }
5018
- const text2 = (0, import_values11.literalOf)(value);
5359
+ const text2 = (0, import_values13.literalOf)(value);
5019
5360
  if (text2 === null) {
5020
5361
  return null;
5021
5362
  }
@@ -5454,22 +5795,29 @@ function recognizedBodyEffects(definitionNode, module2, storageLookup, facts) {
5454
5795
  ...storageLookup,
5455
5796
  filePath: storageLookup.factsPath
5456
5797
  }),
5457
- ...rawSqlEffects(bodyCalls(definitionNode), {
5458
- facts: storageLookup.facts,
5459
- filePath: storageLookup.factsPath,
5460
- patterns: storageLookup.rawSql ?? []
5461
- })
5798
+ ...rawSqlEffects(bodyCalls(definitionNode), rawSqlOf(storageLookup))
5462
5799
  ];
5463
5800
  return [...envReadEffects(definitionNode, module2, facts), ...storage];
5464
5801
  }
5465
- function recognizedCallIds(definitionNode, storageLookup) {
5802
+ function rawSqlOf(storageLookup) {
5803
+ return {
5804
+ facts: storageLookup.facts,
5805
+ filePath: storageLookup.factsPath,
5806
+ patterns: storageLookup.rawSql ?? [],
5807
+ clients: storageLookup.sqlClients ?? []
5808
+ };
5809
+ }
5810
+ function recognizedCallIds(calls, storageLookup) {
5466
5811
  if (storageLookup === void 0) {
5467
5812
  return /* @__PURE__ */ new Set();
5468
5813
  }
5469
- return storageCallIds(bodyCalls(definitionNode), {
5470
- ...storageLookup,
5471
- filePath: storageLookup.factsPath
5472
- });
5814
+ return /* @__PURE__ */ new Set([
5815
+ ...storageCallIds(calls, {
5816
+ ...storageLookup,
5817
+ filePath: storageLookup.factsPath
5818
+ }),
5819
+ ...rawSqlCallIds(calls, rawSqlOf(storageLookup))
5820
+ ]);
5473
5821
  }
5474
5822
  function bodyContentOf(bodyNode) {
5475
5823
  for (const stmt of bodyStatements(bodyNode)) {
@@ -5902,11 +6250,14 @@ function resolveCallee2(call, site, ctx, read) {
5902
6250
  const outcome = read?.outcomes.get(spelling.key) ?? (0, import_resolution5.calleeOutcomeOf)(ctx.facts, spelling.key);
5903
6251
  return asCallee(outcome, site.owner, ctx);
5904
6252
  }
5905
- 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) {
5906
6257
  if (assignedElsewhere(ctx.facts, nameKey2)) {
5907
6258
  return null;
5908
6259
  }
5909
- const outcome = (0, import_resolution5.calleeOutcomeOf)(ctx.facts, nameKey2);
6260
+ const outcome = read?.get(nameKey2) ?? (0, import_resolution5.calleeOutcomeOf)(ctx.facts, nameKey2);
5910
6261
  return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
5911
6262
  }
5912
6263
  function assignedElsewhere(facts, nameKey2) {
@@ -6114,6 +6465,7 @@ function reachedFunctions(seeds, options) {
6114
6465
  const stopsByKey = /* @__PURE__ */ new Map();
6115
6466
  const argTargetsByKey = /* @__PURE__ */ new Map();
6116
6467
  const parameterCallsByKey = /* @__PURE__ */ new Map();
6468
+ const callsByKey = /* @__PURE__ */ new Map();
6117
6469
  const passedPositions = /* @__PURE__ */ new Set();
6118
6470
  for (const seed of seeds) {
6119
6471
  seedKeys.add(seed.key);
@@ -6140,12 +6492,21 @@ function reachedFunctions(seeds, options) {
6140
6492
  bodies.flatMap((body) => body.written),
6141
6493
  ctx
6142
6494
  );
6495
+ const passed = namedOutcomes(
6496
+ bodies.flatMap(
6497
+ ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
6498
+ ),
6499
+ ctx
6500
+ );
6143
6501
  for (const { key, source, body, written } of bodies) {
6144
6502
  const scan = scanBody(
6145
6503
  source,
6146
6504
  ctx,
6147
- recognizedCallIds(source.node, options.storageFor(source.file)),
6148
- { body, written, spellings }
6505
+ recognizedCallIds(
6506
+ written.map(({ call }) => call),
6507
+ options.storageFor(source.file)
6508
+ ),
6509
+ { body, written, spellings, passed }
6149
6510
  );
6150
6511
  if (scan.stops.length > 0) {
6151
6512
  stopsByKey.set(key, scan.stops);
@@ -6158,21 +6519,27 @@ function reachedFunctions(seeds, options) {
6158
6519
  for (const position of scan.passedPositions) {
6159
6520
  passedPositions.add(position);
6160
6521
  }
6522
+ const called = [];
6161
6523
  for (const target of scan.followed) {
6162
- const calleeKey = keyOf(target);
6524
+ const calleeKey = keyOf2(target);
6163
6525
  if (!functionByKey.has(calleeKey)) {
6164
6526
  functionByKey.set(calleeKey, target);
6165
6527
  }
6166
6528
  db.add("calls", [key, calleeKey]);
6529
+ called.push(calleeKey);
6530
+ }
6531
+ if (called.length > 0) {
6532
+ callsByKey.set(key, called);
6167
6533
  }
6168
6534
  }
6169
6535
  }
6536
+ const reached = db.facts("reachable").map(([keyAtom]) => String(keyAtom)).filter((key) => !seedKeys.has(key));
6537
+ settleBodyValues(reached, functionByKey, options.facts);
6170
6538
  const summaries = [];
6171
6539
  const summariesByKey = /* @__PURE__ */ new Map();
6172
- for (const [keyAtom] of db.facts("reachable")) {
6173
- const key = String(keyAtom);
6540
+ for (const key of reached) {
6174
6541
  const target = functionByKey.get(key);
6175
- if (seedKeys.has(key) || target === void 0) {
6542
+ if (target === void 0) {
6176
6543
  continue;
6177
6544
  }
6178
6545
  const summary = (0, import_extractor7.assembleSummary)(libraryUnit(target, options), {
@@ -6197,10 +6564,11 @@ function reachedFunctions(seeds, options) {
6197
6564
  stopsByKey,
6198
6565
  argTargetsByKey,
6199
6566
  parameterCallsByKey,
6567
+ callsByKey,
6200
6568
  passedPositions
6201
6569
  };
6202
6570
  }
6203
- function keyOf(target) {
6571
+ function keyOf2(target) {
6204
6572
  return nodeId(target.file.file, target.node);
6205
6573
  }
6206
6574
  var EMPTY_SCAN = {
@@ -6213,15 +6581,53 @@ var EMPTY_SCAN = {
6213
6581
  };
6214
6582
  function bodyOf(source) {
6215
6583
  const { file, node } = source;
6216
- const body = field(node, "body");
6584
+ const body = isModule(node) ? node : field(node, "body");
6217
6585
  if (body === null) {
6218
6586
  return { body, written: [] };
6219
6587
  }
6220
6588
  return {
6221
6589
  body,
6222
- 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
+ })
6223
6593
  };
6224
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
+ }
6225
6631
  function scanBody(source, ctx, recognized, read) {
6226
6632
  const followed = [];
6227
6633
  const stops = [];
@@ -6242,17 +6648,15 @@ function scanBody(source, ctx, recognized, read) {
6242
6648
  return;
6243
6649
  }
6244
6650
  args.namedChildren.forEach((arg, position) => {
6245
- if (arg === null || arg.type !== "identifier") {
6651
+ const nameKey2 = passedNameKeyOf(file, arg);
6652
+ if (nameKey2 === null) {
6246
6653
  return;
6247
6654
  }
6248
- const resolved = functionNamed(
6249
- readKey(file.file, arg, enclosingFunction(arg)),
6250
- ctx
6251
- );
6655
+ const resolved = functionNamed(nameKey2, ctx, read.passed);
6252
6656
  if (resolved === null) {
6253
6657
  return;
6254
6658
  }
6255
- const key = keyOf(resolved);
6659
+ const key = keyOf2(resolved);
6256
6660
  if (!seen.has(key)) {
6257
6661
  seen.add(key);
6258
6662
  followed.push(resolved);
@@ -6277,7 +6681,7 @@ function scanBody(source, ctx, recognized, read) {
6277
6681
  recordPassedArgs(
6278
6682
  call,
6279
6683
  callee,
6280
- outcome.kind === "followed" ? keyOf(outcome.target) : null
6684
+ outcome.kind === "followed" ? keyOf2(outcome.target) : null
6281
6685
  );
6282
6686
  if (outcome.kind === "stopped") {
6283
6687
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6294,7 +6698,7 @@ function scanBody(source, ctx, recognized, read) {
6294
6698
  }
6295
6699
  return;
6296
6700
  }
6297
- const key = keyOf(outcome.target);
6701
+ const key = keyOf2(outcome.target);
6298
6702
  if (!seen.has(key)) {
6299
6703
  seen.add(key);
6300
6704
  followed.push(outcome.target);
@@ -6312,7 +6716,7 @@ function scanBody(source, ctx, recognized, read) {
6312
6716
  passedPositions
6313
6717
  };
6314
6718
  }
6315
- function callsWritten(file, body, outer, owner) {
6719
+ function callsWritten(file, body, outer, owner, limit) {
6316
6720
  const found = [];
6317
6721
  (0, import_extractor7.walkDescendants)(body, outer, {
6318
6722
  at: (child, scope) => {
@@ -6320,7 +6724,7 @@ function callsWritten(file, body, outer, owner) {
6320
6724
  found.push({ call: child, site: { file, scope, owner } });
6321
6725
  }
6322
6726
  },
6323
- 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
6324
6728
  });
6325
6729
  return found;
6326
6730
  }
@@ -8094,10 +8498,7 @@ function factsForFile(options) {
8094
8498
  files: [{ file: options.file, root: options.root, module: options.module }],
8095
8499
  definitions
8096
8500
  });
8097
- bindEnvNameSites(
8098
- db,
8099
- envNameSites(options.file, options.root, options.module)
8100
- );
8501
+ bindEnvFacts(db, [envFactsIn(options.file, options.root, options.module)]);
8101
8502
  (0, import_resolution7.addPackWords)(db, packWordsOf(options.packs));
8102
8503
  return db;
8103
8504
  }
@@ -8148,6 +8549,7 @@ async function extractPythonProject(options) {
8148
8549
  );
8149
8550
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
8150
8551
  const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
8552
+ const sqlClients = options.packs.flatMap((pack) => pack.sqlClients ?? []);
8151
8553
  const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
8152
8554
  const discovers = options.packs.some((pack) => pack.discovery.length > 0);
8153
8555
  const buildsReceivers = options.packs.some(
@@ -8155,10 +8557,15 @@ async function extractPythonProject(options) {
8155
8557
  (client) => (client.receiverConstructors ?? []).length > 0
8156
8558
  )
8157
8559
  );
8158
- const envSites = bound.flatMap(
8159
- (boundFile) => envNameSites(boundFile.file, boundFile.root, boundFile.module)
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
8160
8565
  );
8161
- const needsValues = discovers || mountsRouters || buildsReceivers || envSites.length > 0 || storagePatterns.length > 0 || modelQueries.length > 0;
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;
8162
8569
  const definitions = /* @__PURE__ */ new Map();
8163
8570
  timer.time("discover", () => {
8164
8571
  for (const { file, root, module: moduleBinding } of bound) {
@@ -8170,7 +8577,7 @@ async function extractPythonProject(options) {
8170
8577
  }
8171
8578
  if (needsValues) {
8172
8579
  bindEvaluator(db, { files: bound, definitions });
8173
- bindEnvNameSites(db, envSites);
8580
+ bindEnvFacts(db, envFacts);
8174
8581
  }
8175
8582
  (0, import_resolution7.addPackWords)(db, packWordsOf(options.packs));
8176
8583
  });
@@ -8186,13 +8593,14 @@ async function extractPythonProject(options) {
8186
8593
  ...mountsRouters ? { facts: db } : {}
8187
8594
  })
8188
8595
  );
8189
- const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
8596
+ const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 || sqlClients.length > 0 ? {
8190
8597
  facts: db,
8191
8598
  factsPath: file.file,
8192
8599
  patterns: storagePatterns,
8193
8600
  definitionAt: (key) => definitions.get(key),
8194
8601
  couldMatch,
8195
- rawSql: rawSqlPatterns
8602
+ rawSql: rawSqlPatterns,
8603
+ sqlClients
8196
8604
  } : void 0;
8197
8605
  const wrapperIndex = timer.time(
8198
8606
  "discover",
@@ -8227,6 +8635,7 @@ async function extractPythonProject(options) {
8227
8635
  }
8228
8636
  const seeds = [];
8229
8637
  const summariesBySeed = /* @__PURE__ */ new Map();
8638
+ const moduleRoots = [];
8230
8639
  for (const boundFile of bound) {
8231
8640
  const { file, root, module: moduleBinding } = boundFile;
8232
8641
  const displayPath = displayPathOf(file);
@@ -8261,21 +8670,15 @@ async function extractPythonProject(options) {
8261
8670
  "discover",
8262
8671
  () => envReadEffects(root, moduleBinding, db)
8263
8672
  );
8264
- if (loadTimeReads.length > 0) {
8265
- const summary = timer.time(
8266
- "summarize",
8267
- () => (0, import_extractor10.assembleSummary)(
8268
- (0, import_extractor10.moduleInitStructure)({
8269
- name: import_node_path3.default.basename(displayPath),
8270
- file: displayPath,
8271
- range: rangeOf(root),
8272
- effects: loadTimeReads
8273
- }),
8274
- { gapHandling }
8275
- )
8276
- );
8277
- summary.confidence = { source: "inferred_static", level: "low" };
8278
- 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
+ });
8279
8682
  }
8280
8683
  }
8281
8684
  const reached = timer.time(
@@ -8289,16 +8692,19 @@ async function extractPythonProject(options) {
8289
8692
  definitions
8290
8693
  })
8291
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
+ };
8292
8705
  for (const [key, owners] of summariesBySeed) {
8293
8706
  for (const summary of owners) {
8294
- if (gapHandling !== "silent") {
8295
- summary.gaps.push(
8296
- ...(reached.stopsByKey.get(key) ?? []).map(import_behavioral_ir6.unfollowedCallGap)
8297
- );
8298
- }
8299
- (0, import_behavioral_ir6.placeCalls)(summary, reached.targetsByKey.get(key));
8300
- (0, import_behavioral_ir6.placeArgTargets)(summary, reached.argTargetsByKey.get(key));
8301
- (0, import_behavioral_ir6.placeCalleeParameters)(summary, reached.parameterCallsByKey.get(key));
8707
+ placeWhatItReached(summary, key);
8302
8708
  }
8303
8709
  }
8304
8710
  if (gapHandling !== "silent") {
@@ -8308,6 +8714,30 @@ async function extractPythonProject(options) {
8308
8714
  reached.passedPositions
8309
8715
  );
8310
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
+ }
8311
8741
  summaries.push(...reached.summaries);
8312
8742
  const resolvedImports = resolvedImportsOf(db, displayPathOf);
8313
8743
  (0, import_extractor10.stampModuleImports)(summaries, (file) => resolvedImports.get(file) ?? []);