@suss/adapter-python 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -29,6 +29,12 @@ var LATER_BODY_TYPES = /* @__PURE__ */ new Set([
29
29
  "lambda",
30
30
  "class_definition"
31
31
  ]);
32
+ function isModule(node) {
33
+ return node.type === "module";
34
+ }
35
+ function runsAtModuleLoad(node) {
36
+ return !LATER_BODY_TYPES.has(node.type) && node.type !== "decorator";
37
+ }
32
38
  function enclosingFunction(node) {
33
39
  let current = node.parent;
34
40
  while (current !== null) {
@@ -724,8 +730,14 @@ import {
724
730
  function resolveCalls(db, callKeys) {
725
731
  askResolution(db, callKeys);
726
732
  }
727
- function resolveEnvSites(db, siteIds) {
728
- askResolution(db, siteIds, "wantedEnvSite");
733
+ function resolveEnvObjects(db, objectKeys) {
734
+ askResolution(db, objectKeys, "wantedEnvObject");
735
+ }
736
+ function resolvedFunctions(db, key) {
737
+ return [
738
+ ...db.lookup("wantedResolves", 0, key),
739
+ ...db.lookup("wantedGivesBack", 0, key)
740
+ ].map((row) => String(row[1]));
729
741
  }
730
742
  function writtenValueUnder(db, key, site) {
731
743
  if (askResolutionUnder(db, [[key, site]]) === "abandoned") {
@@ -745,6 +757,13 @@ function writtenValuesOf(db, key) {
745
757
  resolveCalls(db, [key]);
746
758
  return sharedWrittenValuesOf(db, key, (keys) => resolveCalls(db, keys));
747
759
  }
760
+ function settleWrittenValues(db, keys) {
761
+ if (keys.length === 0) {
762
+ return;
763
+ }
764
+ resolveCalls(db, keys);
765
+ writtenValuesByKey(db, keys, (behind) => resolveCalls(db, behind));
766
+ }
748
767
  function originsOf(db, nameKey2) {
749
768
  askResolution(db, [nameKey2], "wantedOrigin");
750
769
  return db.facts("wantedComesFrom").filter((row) => String(row[0]) === nameKey2).map((row) => ({ module: String(row[1]), name: String(row[2]) }));
@@ -1045,9 +1064,51 @@ function walkExpressions(emitter, node, visit) {
1045
1064
  walkExpressions(emitter, child, visit);
1046
1065
  }
1047
1066
  }
1067
+ var WRITTEN_KEY_TYPES = /* @__PURE__ */ new Set(["string", "integer", "concatenated_string"]);
1068
+ function emitKeyedRead(emitter, site, container, key) {
1069
+ if (WRITTEN_KEY_TYPES.has(key.type)) {
1070
+ return;
1071
+ }
1072
+ add(
1073
+ emitter,
1074
+ "readsKeyed",
1075
+ nodeId(emitter.filePath, site),
1076
+ valueKey(emitter, container),
1077
+ valueKey(emitter, key)
1078
+ );
1079
+ }
1080
+ function singleSubscript(node) {
1081
+ const written = fields(node, "subscript");
1082
+ return written.length === 1 ? written[0] ?? null : null;
1083
+ }
1084
+ function mappingGetRead(call) {
1085
+ const callee = field(call, "function");
1086
+ if (callee === null || callee.type !== "attribute") {
1087
+ return null;
1088
+ }
1089
+ const container = field(callee, "object");
1090
+ if (container === null || field(callee, "attribute")?.text !== "get") {
1091
+ return null;
1092
+ }
1093
+ const first = callArguments(call).find(
1094
+ (argument) => argument.kind === "positional" && argument.position === 0
1095
+ );
1096
+ return first === void 0 ? null : { container, key: first.node };
1097
+ }
1048
1098
  function emitExpressionFact(emitter, child) {
1049
1099
  if (child.type === "call") {
1050
1100
  emitCall(emitter, child);
1101
+ const mapping = mappingGetRead(child);
1102
+ if (mapping !== null) {
1103
+ emitKeyedRead(emitter, child, mapping.container, mapping.key);
1104
+ }
1105
+ }
1106
+ if (child.type === "subscript") {
1107
+ const index = singleSubscript(child);
1108
+ const container = field(child, "value");
1109
+ if (index !== null && container !== null) {
1110
+ emitKeyedRead(emitter, child, container, index);
1111
+ }
1051
1112
  }
1052
1113
  if (child.type === "dictionary") {
1053
1114
  emitDictionary(emitter, child);
@@ -1115,6 +1176,9 @@ function emitFunctionFacts(emitter, fn, classKey) {
1115
1176
  return funcKey;
1116
1177
  }
1117
1178
  emitNestedDefinitions(inside, body);
1179
+ if (fn.type === "lambda") {
1180
+ emitExpressionFact(inside, body);
1181
+ }
1118
1182
  let statesReturn = false;
1119
1183
  const stores = /* @__PURE__ */ new Map();
1120
1184
  walkExpressions(inside, body, (child) => {
@@ -2532,8 +2596,17 @@ function askWrittenValues(nodes, db) {
2532
2596
  if (bound === void 0) {
2533
2597
  return;
2534
2598
  }
2535
- const keys = nodes.map((node) => bound.keyOf(node)).filter((key) => key !== null);
2536
- resolveCalls(db, keys);
2599
+ settleWrittenValues(db, resolutionKeysOf(nodes, bound));
2600
+ }
2601
+ function resolutionKeysOf(nodes, bound) {
2602
+ const keys = /* @__PURE__ */ new Set();
2603
+ for (const node of nodes) {
2604
+ const key = bound.keyOf(node);
2605
+ if (key !== null) {
2606
+ keys.add(key);
2607
+ }
2608
+ }
2609
+ return [...keys];
2537
2610
  }
2538
2611
  function evaluatedValue(node, db, site) {
2539
2612
  const evaluator = evaluatorFor(node, db);
@@ -2601,7 +2674,7 @@ function projectOver(db, nodes) {
2601
2674
  filesByRoot.set(entry.root.id, entry);
2602
2675
  rootsByFile.set(entry.file, entry.root);
2603
2676
  }
2604
- const keyOf2 = (node) => {
2677
+ const keyOf3 = (node) => {
2605
2678
  const entry = filesByRoot.get(node.tree.rootNode.id);
2606
2679
  if (entry === void 0) {
2607
2680
  return null;
@@ -2610,7 +2683,7 @@ function projectOver(db, nodes) {
2610
2683
  };
2611
2684
  const context = {
2612
2685
  writtenTo: (node, site) => {
2613
- const key = keyOf2(node);
2686
+ const key = keyOf3(node);
2614
2687
  if (key === null) {
2615
2688
  return null;
2616
2689
  }
@@ -2619,17 +2692,17 @@ function projectOver(db, nodes) {
2619
2692
  },
2620
2693
  callable: (call) => {
2621
2694
  const callee = field(call, "function");
2622
- const key = callee === null ? null : keyOf2(callee);
2695
+ const key = callee === null ? null : keyOf3(callee);
2623
2696
  if (key === null) {
2624
2697
  return null;
2625
2698
  }
2626
2699
  resolveCalls(db, [key]);
2627
- const resolved = db.facts("wantedResolves").filter((row) => String(row[0]) === key).map((row) => String(row[1]));
2628
- const settled = resolved.length === 1 ? resolved[0] : void 0;
2700
+ const resolved = db.lookup("wantedResolves", 0, key);
2701
+ const settled = resolved.length === 1 ? String(resolved[0]?.[1]) : void 0;
2629
2702
  return settled === void 0 ? null : nodes.definitions.get(settled) ?? null;
2630
2703
  }
2631
2704
  };
2632
- return { keyOf: keyOf2, roots: rootsByFile, context };
2705
+ return { keyOf: keyOf3, roots: rootsByFile, context };
2633
2706
  }
2634
2707
 
2635
2708
  // src/decorators.ts
@@ -3267,6 +3340,18 @@ function bodyCalls(node, found = []) {
3267
3340
  }
3268
3341
  return found;
3269
3342
  }
3343
+ function moduleLoadCalls(node, found = []) {
3344
+ for (const child of node.namedChildren) {
3345
+ if (child === null || !runsAtModuleLoad(child)) {
3346
+ continue;
3347
+ }
3348
+ if (child.type === "call") {
3349
+ found.push(child);
3350
+ }
3351
+ moduleLoadCalls(child, found);
3352
+ }
3353
+ return found;
3354
+ }
3270
3355
  function enclosingStatement(call, body) {
3271
3356
  let current = call;
3272
3357
  while (current !== null && current.parent !== null) {
@@ -3291,15 +3376,18 @@ function argOf(node, facts) {
3291
3376
  }
3292
3377
  return { kind: "identifier", name: node.text };
3293
3378
  }
3294
- function argsOf(call, facts) {
3379
+ function argumentNodes(call) {
3295
3380
  const args = field(call, "arguments");
3296
3381
  if (args === null) {
3297
3382
  return [];
3298
3383
  }
3299
3384
  return args.namedChildren.filter((child) => child !== null).map(
3300
- (child) => child.type === "keyword_argument" ? argOf(field(child, "value") ?? child, facts) : argOf(child, facts)
3385
+ (child) => child.type === "keyword_argument" ? field(child, "value") ?? child : child
3301
3386
  );
3302
3387
  }
3388
+ function argsOf(call, facts) {
3389
+ return argumentNodes(call).map((node) => argOf(node, facts));
3390
+ }
3303
3391
  function calleeText(call) {
3304
3392
  return field(call, "function")?.text ?? call.text;
3305
3393
  }
@@ -3308,7 +3396,40 @@ function invocationEffects(definitionNode, facts) {
3308
3396
  if (body === null) {
3309
3397
  return [];
3310
3398
  }
3311
- const calls = bodyCalls(body).sort((a, b) => a.endIndex - b.endIndex);
3399
+ return invocationEffectsIn(body, bodyCalls(body), facts);
3400
+ }
3401
+ function moduleLoadInvocationEffects(moduleNode, facts) {
3402
+ return invocationEffectsIn(moduleNode, moduleLoadCalls(moduleNode), facts);
3403
+ }
3404
+ var ASKED_ABOUT_TYPES = /* @__PURE__ */ new Set(["identifier", "attribute", "call"]);
3405
+ function askedNodesUnder(node, found = []) {
3406
+ if (ASKED_ABOUT_TYPES.has(node.type)) {
3407
+ found.push(node);
3408
+ }
3409
+ for (const child of node.namedChildren) {
3410
+ if (child !== null && !NESTED_DEFINITION_TYPES.has(child.type)) {
3411
+ askedNodesUnder(child, found);
3412
+ }
3413
+ }
3414
+ return found;
3415
+ }
3416
+ function bodyValueNodes(definitionNode) {
3417
+ const body = field(definitionNode, "body");
3418
+ return body === null ? [] : askedNodesUnder(body);
3419
+ }
3420
+ function effectValueNodes(call, gating) {
3421
+ const found = argumentNodes(call).flatMap((node) => askedNodesUnder(node));
3422
+ for (const path6 of gating ?? []) {
3423
+ for (const condition of path6) {
3424
+ if (condition.expression !== null) {
3425
+ askedNodesUnder(condition.expression, found);
3426
+ }
3427
+ }
3428
+ }
3429
+ return found;
3430
+ }
3431
+ function invocationEffectsIn(body, written, facts) {
3432
+ const calls = [...written].sort((a, b) => a.endIndex - b.endIndex);
3312
3433
  if (calls.length === 0) {
3313
3434
  return [];
3314
3435
  }
@@ -3330,10 +3451,17 @@ function invocationEffects(definitionNode, facts) {
3330
3451
  },
3331
3452
  statements
3332
3453
  );
3333
- return calls.map((call) => {
3454
+ const gatingOf = calls.map((call) => {
3334
3455
  const statement = statementOf3.get(call.id);
3456
+ return statement === void 0 ? void 0 : enumerated.byTerminal.get(statement);
3457
+ });
3458
+ askWrittenValues(
3459
+ calls.flatMap((call, index) => effectValueNodes(call, gatingOf[index])),
3460
+ facts
3461
+ );
3462
+ return calls.map((call, index) => {
3335
3463
  const conditions = sharedGatingConditions(
3336
- statement === void 0 ? void 0 : enumerated.byTerminal.get(statement),
3464
+ gatingOf[index],
3337
3465
  (condition) => predicateOf(condition, facts)
3338
3466
  );
3339
3467
  return {
@@ -3364,6 +3492,22 @@ function clientCallUnits(root, module, pack, pattern, options) {
3364
3492
  }
3365
3493
  return units;
3366
3494
  }
3495
+ function clientCallReceivers(root, module) {
3496
+ const found = [];
3497
+ for (const definition of functionDefinitions(root)) {
3498
+ for (const call of bodyCalls(definition)) {
3499
+ const callee = field(call, "function");
3500
+ if (callee === null || originOf(callee, module) !== null) {
3501
+ continue;
3502
+ }
3503
+ const object = field(callee, "object");
3504
+ if (object !== null && field(callee, "attribute") !== null) {
3505
+ found.push(object);
3506
+ }
3507
+ }
3508
+ }
3509
+ return found;
3510
+ }
3367
3511
  function functionDefinitions(node, found = []) {
3368
3512
  for (const child of children(node)) {
3369
3513
  if (isFunction(child)) {
@@ -3726,44 +3870,91 @@ function isDefaultedAt(node) {
3726
3870
  }
3727
3871
  return false;
3728
3872
  }
3729
- function envNameSites(filePath, root, module) {
3730
- const sites = [];
3873
+ function envFactsIn(filePath, root, module) {
3874
+ const found = { sites: [], objects: [] };
3731
3875
  walkDescendants(root, module.moduleScope, {
3732
3876
  at: (node, scope) => {
3877
+ if (isEnviron(node, scope) && handsOnward(node)) {
3878
+ found.objects.push(keyOf(filePath, node));
3879
+ }
3733
3880
  const syntax = envReadSyntaxAt(node, scope);
3734
3881
  if (syntax === null || stringLiteralValue(syntax.name) !== null) {
3735
3882
  return;
3736
3883
  }
3737
- sites.push({
3884
+ found.sites.push({
3738
3885
  site: nodeId(filePath, node),
3739
- nameKey: readKey(filePath, syntax.name, enclosingFunction(syntax.name)),
3740
- defaulted: syntax.defaulted
3886
+ nameKey: keyOf(filePath, syntax.name),
3887
+ defaulted: syntax.defaulted,
3888
+ functionKey: functionReadKey(filePath, node, scope)
3741
3889
  });
3742
3890
  },
3743
3891
  into: (node, scope) => module.scopeFor.get(node.id) ?? scope
3744
3892
  });
3745
- return sites;
3893
+ return found;
3894
+ }
3895
+ var keyOf = (filePath, node) => readKey(filePath, node, enclosingFunction(node));
3896
+ function functionReadKey(filePath, node, scope) {
3897
+ if (node.type !== "call") {
3898
+ return null;
3899
+ }
3900
+ const callee = field(node, "function");
3901
+ if (callee === null || isEnviron(field(callee, "object") ?? callee, scope)) {
3902
+ return null;
3903
+ }
3904
+ return keyOf(filePath, callee);
3905
+ }
3906
+ function handsOnward(node) {
3907
+ const parent = node.parent;
3908
+ if (parent === null) {
3909
+ return true;
3910
+ }
3911
+ if (parent.type === "subscript" && field(parent, "value")?.id === node.id) {
3912
+ return !writesTheKey(field(parent, "subscript"));
3913
+ }
3914
+ if (parent.type === "attribute" && field(parent, "object")?.id === node.id) {
3915
+ const call = parent.parent;
3916
+ return field(parent, "attribute")?.text === "get" && call !== null && call.type === "call" && !writesTheKey(firstArgumentOf(call));
3917
+ }
3918
+ return true;
3919
+ }
3920
+ var writesTheKey = (key) => key !== null && stringLiteralValue(key) !== null;
3921
+ function firstArgumentOf(call) {
3922
+ const first = callArguments(call).find(
3923
+ (argument) => argument.kind === "positional" && argument.position === 0
3924
+ );
3925
+ return first?.node ?? null;
3746
3926
  }
3747
3927
  var sitesByDb = /* @__PURE__ */ new WeakMap();
3748
- function bindEnvNameSites(db, sites) {
3749
- if (sites.length === 0) {
3928
+ function bindEnvFacts(db, found) {
3929
+ const sites = found.flatMap((one) => one.sites);
3930
+ const objects = found.flatMap((one) => one.objects);
3931
+ if (sites.length === 0 && objects.length === 0) {
3750
3932
  return;
3751
3933
  }
3752
3934
  let index = sitesByDb.get(db);
3753
3935
  if (index === void 0) {
3754
- index = { byId: /* @__PURE__ */ new Map(), sitesByParameter: null };
3936
+ index = { byId: /* @__PURE__ */ new Map(), objects: /* @__PURE__ */ new Set(), sitesByParameter: null };
3755
3937
  sitesByDb.set(db, index);
3756
3938
  }
3939
+ for (const object of objects) {
3940
+ db.add("environmentObject", [object]);
3941
+ index.objects.add(object);
3942
+ }
3757
3943
  for (const site of sites) {
3758
- db.add("readsEnvNamed", [site.site, site.nameKey]);
3759
3944
  index.byId.set(site.site, site);
3945
+ if (site.functionKey === null) {
3946
+ continue;
3947
+ }
3948
+ db.add("environmentObject", [site.functionKey]);
3949
+ db.add("readsKeyed", [site.site, site.functionKey, site.nameKey]);
3950
+ index.objects.add(site.functionKey);
3760
3951
  }
3761
3952
  }
3762
3953
  function sitesEachParameterNames(db, index) {
3763
3954
  if (index.sitesByParameter !== null) {
3764
3955
  return index.sitesByParameter;
3765
3956
  }
3766
- resolveEnvSites(db, [...index.byId.keys()]);
3957
+ resolveEnvObjects(db, [...index.objects]);
3767
3958
  const found = /* @__PURE__ */ new Map();
3768
3959
  for (const row of db.facts("wantedParamNamesEnv")) {
3769
3960
  const parameter = String(row[0]);
@@ -3827,8 +4018,7 @@ function argumentsByParameter(db, call, calleeKey) {
3827
4018
  positional.set(String(argument.position), argument.node);
3828
4019
  }
3829
4020
  const argumentAt2 = /* @__PURE__ */ new Map();
3830
- for (const resolved of db.lookup("wantedResolves", 0, calleeKey)) {
3831
- const callee = String(resolved[1]);
4021
+ for (const callee of resolvedFunctions(db, calleeKey)) {
3832
4022
  for (const row of db.lookup("paramOf", 0, callee)) {
3833
4023
  const at = positional.get(String(row[1]));
3834
4024
  if (at !== void 0) {
@@ -3856,10 +4046,8 @@ function readsThroughHelper(db, call, argumentAt2, siteById, named) {
3856
4046
  continue;
3857
4047
  }
3858
4048
  for (const id of sites) {
3859
- const site = siteById.get(id);
3860
- if (site !== void 0) {
3861
- reads.set(name, (reads.get(name) ?? true) && site.defaulted);
3862
- }
4049
+ const defaulted = siteById.get(id)?.defaulted ?? false;
4050
+ reads.set(name, (reads.get(name) ?? true) && defaulted);
3863
4051
  }
3864
4052
  }
3865
4053
  if (reads.size === 0) {
@@ -4078,72 +4266,302 @@ function returnedBodyShape(statement) {
4078
4266
 
4079
4267
  // src/rawSql.ts
4080
4268
  import { storageBinding } from "@suss/ir-core";
4081
- import { readSqlAccess, sqlFromParts } from "@suss/sql";
4082
- function rawSqlEffects(calls, options) {
4083
- if (options.patterns.length === 0) {
4084
- return [];
4269
+ import { readSqlAccess, splitQualifiedTable, sqlFromParts } from "@suss/sql";
4270
+ import { force as force2 } from "@suss/values";
4271
+
4272
+ // src/receiverTypes.ts
4273
+ var UNDECLARED_MODULE = "builtins";
4274
+ function annotationTarget(annotation) {
4275
+ if (annotation.type === "type" && annotation.namedChildren[0]) {
4276
+ return annotationTarget(annotation.namedChildren[0]);
4085
4277
  }
4086
- const effects = [];
4087
- for (const call of calls) {
4088
- const pattern = patternFor(call, options);
4089
- if (pattern === void 0) {
4090
- continue;
4278
+ if (annotation.type === "binary_operator") {
4279
+ const named = [field(annotation, "left"), field(annotation, "right")].find(
4280
+ (side) => side !== null && side.type !== "none"
4281
+ );
4282
+ return named === void 0 || named === null ? null : annotationTarget(named);
4283
+ }
4284
+ if (annotation.type === "generic_type") {
4285
+ const outer = annotation.namedChildren[0];
4286
+ const first = genericTypeArgs(annotation)[0];
4287
+ if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4288
+ return annotationTarget(first);
4091
4289
  }
4092
- const statement = statementIn(call, options.facts);
4093
- if (statement === null) {
4290
+ return outer ?? null;
4291
+ }
4292
+ return annotation;
4293
+ }
4294
+ function typeNameOf(annotation) {
4295
+ const target = annotationTarget(annotation);
4296
+ if (target === null) {
4297
+ return null;
4298
+ }
4299
+ return target.type === "identifier" ? target.text : stringLiteralValue(target);
4300
+ }
4301
+ function firstInBody(node, read) {
4302
+ const here = read(node);
4303
+ if (here !== null) {
4304
+ return here;
4305
+ }
4306
+ for (const child of children(node)) {
4307
+ if (child.type === "function_definition" || child.type === "lambda") {
4094
4308
  continue;
4095
4309
  }
4096
- for (const access of readSqlAccess(statement, {
4097
- dialect: pattern.storageSystem
4098
- })) {
4099
- effects.push({
4100
- type: "interaction",
4101
- binding: storageBinding({
4102
- recognition: `python-${pattern.module}`,
4103
- storageSystem: pattern.storageSystem,
4104
- scope: "default",
4105
- container: access.table
4106
- }),
4107
- callee: field(call, "function")?.text ?? "",
4108
- interaction: {
4109
- class: "storage-access",
4110
- kind: access.kind,
4111
- fields: access.fields,
4112
- ...access.selector.length > 0 ? { selector: access.selector } : {},
4113
- operation: field(call, "function")?.text ?? ""
4114
- }
4115
- });
4310
+ const found = firstInBody(child, read);
4311
+ if (found !== null) {
4312
+ return found;
4313
+ }
4314
+ }
4315
+ return null;
4316
+ }
4317
+ function statedBy(statement, name) {
4318
+ if (statement.type === "assignment") {
4319
+ const annotation = field(statement, "type");
4320
+ return field(statement, "left")?.text === name ? annotation : null;
4321
+ }
4322
+ if (statement.type === "as_pattern") {
4323
+ const alias = field(statement, "alias");
4324
+ const value = statement.namedChildren[0] ?? null;
4325
+ const callee = value?.type === "call" ? field(value, "function") : null;
4326
+ return alias?.text === name ? callee : null;
4327
+ }
4328
+ return null;
4329
+ }
4330
+ function statedTypeNode(name, from) {
4331
+ const fn = enclosingFunction(from);
4332
+ if (fn === null) {
4333
+ return null;
4334
+ }
4335
+ const params = field(fn, "parameters");
4336
+ for (const param of params === null ? [] : children(params)) {
4337
+ const info = parameterNameAndType(param);
4338
+ if (info?.name === name && info.typeNode !== null) {
4339
+ return info.typeNode;
4116
4340
  }
4117
4341
  }
4118
- return effects;
4342
+ const body = field(fn, "body");
4343
+ return body === null ? null : firstInBody(body, (statement) => statedBy(statement, name));
4344
+ }
4345
+ function statedTypeName(name, from) {
4346
+ const stated = statedTypeNode(name, from);
4347
+ return stated === null ? null : typeNameOf(stated);
4348
+ }
4349
+ function statedOrigins(stated, options) {
4350
+ const name = typeNameOf(stated);
4351
+ const byRule = name === null ? [] : originsOf(options.facts, `${options.filePath}#${name}`);
4352
+ const target = annotationTarget(stated);
4353
+ const imported = target === null ? null : originOf(target, moduleOf(target));
4354
+ if (imported === null || imported.module === UNDECLARED_MODULE) {
4355
+ return byRule;
4356
+ }
4357
+ return [...byRule, imported];
4358
+ }
4359
+ function receiverTypeOrigins(receiver, options) {
4360
+ const stated = statedTypeNode(receiver.text, receiver);
4361
+ if (stated !== null) {
4362
+ return statedOrigins(stated, options);
4363
+ }
4364
+ const built = constructionBehind(receiver, options.facts);
4365
+ if (built.type === "oneCall") {
4366
+ return [built.construction.origin];
4367
+ }
4368
+ const key = nameKeyIn(
4369
+ options.filePath,
4370
+ enclosingFunction(receiver),
4371
+ receiver.text
4372
+ );
4373
+ return subjectConstructions(options.facts, [key]).get(key)?.origins ?? [];
4374
+ }
4375
+
4376
+ // src/rawSql.ts
4377
+ function rawSqlEffects(calls, options) {
4378
+ return matchesIn(calls, options).flatMap(
4379
+ (match) => match.accesses.flatMap((access) => effectFor(match, access))
4380
+ );
4381
+ }
4382
+ function rawSqlCallIds(calls, options) {
4383
+ return new Set(matchesIn(calls, options).map((match) => match.call.id));
4384
+ }
4385
+ function matchesIn(calls, options) {
4386
+ if (options.patterns.length === 0 && (options.clients ?? []).length === 0) {
4387
+ return [];
4388
+ }
4389
+ const matched = [];
4390
+ for (const call of calls) {
4391
+ const one = importedFunctionMatch(call, options) ?? clientMatch(call, options);
4392
+ if (one !== null) {
4393
+ matched.push(one);
4394
+ }
4395
+ }
4396
+ return matched;
4397
+ }
4398
+ function effectFor(match, access) {
4399
+ const operation = field(match.call, "function")?.text ?? "";
4400
+ return [
4401
+ {
4402
+ type: "interaction",
4403
+ binding: storageBinding({
4404
+ recognition: match.recognition,
4405
+ storageSystem: match.storageSystem,
4406
+ scope: access.qualifier[access.qualifier.length - 1] ?? NO_GROUP,
4407
+ container: access.table
4408
+ }),
4409
+ callee: operation,
4410
+ interaction: {
4411
+ class: "storage-access",
4412
+ kind: access.kind,
4413
+ fields: access.fields,
4414
+ ...access.selector.length > 0 ? { selector: access.selector } : {},
4415
+ operation
4416
+ }
4417
+ }
4418
+ ];
4119
4419
  }
4120
- function patternFor(call, options) {
4420
+ var NO_GROUP = "default";
4421
+ function importedFunctionMatch(call, options) {
4121
4422
  const callee = field(call, "function");
4122
4423
  if (callee === null || callee.type !== "identifier") {
4123
- return void 0;
4424
+ return null;
4124
4425
  }
4125
4426
  const from = options.facts.facts("pyImportedName").find((row) => String(row[0]) === `${options.filePath}#${callee.text}`);
4126
4427
  if (from === void 0) {
4127
- return void 0;
4428
+ return null;
4128
4429
  }
4129
4430
  const module = String(from[1]);
4130
- return options.patterns.find(
4131
- (pattern) => pattern.module === module && pattern.functions.includes(callee.text)
4431
+ const pattern = options.patterns.find(
4432
+ (candidate) => candidate.module === module && candidate.functions.includes(callee.text)
4132
4433
  );
4133
- }
4134
- function statementIn(call, facts) {
4434
+ if (pattern === void 0) {
4435
+ return null;
4436
+ }
4135
4437
  const args = field(call, "arguments");
4136
4438
  const first = args?.namedChildren.find((child) => child !== null) ?? null;
4137
- if (first === null) {
4439
+ const statement = first === null ? null : statementAt(first, void 0, options.facts);
4440
+ if (statement === null) {
4441
+ return null;
4442
+ }
4443
+ return {
4444
+ call,
4445
+ recognition: `python-${pattern.module}`,
4446
+ storageSystem: pattern.storageSystem,
4447
+ accesses: readSqlAccess(statement, { dialect: pattern.storageSystem })
4448
+ };
4449
+ }
4450
+ function clientMatch(call, options) {
4451
+ const callee = field(call, "function");
4452
+ if (callee === null || callee.type !== "attribute") {
4138
4453
  return null;
4139
4454
  }
4140
- const parts = literalParts(evaluatedValue(first, facts));
4455
+ const method = field(callee, "attribute")?.text ?? "";
4456
+ const receiver = field(callee, "object");
4457
+ if (receiver === null) {
4458
+ return null;
4459
+ }
4460
+ for (const pattern of options.clients ?? []) {
4461
+ const accesses = accessesOf(call, method, pattern, options);
4462
+ if (accesses === null || !isClientOf(receiver, pattern, options)) {
4463
+ continue;
4464
+ }
4465
+ return {
4466
+ call,
4467
+ recognition: `python-${pattern.module}`,
4468
+ storageSystem: pattern.storageSystem,
4469
+ accesses
4470
+ };
4471
+ }
4472
+ return null;
4473
+ }
4474
+ function accessesOf(call, method, pattern, options) {
4475
+ const dialect = pattern.dialect ?? pattern.storageSystem;
4476
+ for (const takes of pattern.statements ?? []) {
4477
+ if (takes.method !== method) {
4478
+ continue;
4479
+ }
4480
+ const written = argumentNode(call, takes);
4481
+ const statement = written === null ? null : statementAt(written, takes.path, options.facts);
4482
+ return statement === null ? [] : readSqlAccess(statement, { dialect });
4483
+ }
4484
+ for (const takes of pattern.tables ?? []) {
4485
+ if (takes.method !== method) {
4486
+ continue;
4487
+ }
4488
+ const written = argumentNode(call, takes);
4489
+ const name = written === null ? null : stringValueOf(written, options.facts);
4490
+ const split = name === null ? null : splitQualifiedTable(name);
4491
+ return split === null ? [] : [{ ...split, kind: takes.kind, fields: [], selector: [] }];
4492
+ }
4493
+ return null;
4494
+ }
4495
+ function isClientOf(receiver, pattern, options) {
4496
+ return receiverOrigins(receiver, options).some(
4497
+ (origin) => origin.module === pattern.module && pattern.clientTypes.includes(origin.name)
4498
+ );
4499
+ }
4500
+ function receiverOrigins(receiver, options) {
4501
+ if (receiver.type === "identifier") {
4502
+ return receiverTypeOrigins(receiver, options);
4503
+ }
4504
+ return receiver.type === "call" ? handedBack(receiver, options) : [];
4505
+ }
4506
+ function handedBack(call, options) {
4507
+ const callee = field(call, "function");
4508
+ if (callee === null || callee.type !== "attribute") {
4509
+ return [];
4510
+ }
4511
+ const method = field(callee, "attribute")?.text ?? "";
4512
+ const inner = field(callee, "object");
4513
+ if (inner === null) {
4514
+ return [];
4515
+ }
4516
+ const found = [];
4517
+ for (const pattern of options.clients ?? []) {
4518
+ const handoff = (pattern.handsBack ?? []).find(
4519
+ (one) => one.method === method
4520
+ );
4521
+ if (handoff !== void 0 && isClientOf(inner, pattern, options)) {
4522
+ found.push({ module: handoff.module, name: handoff.name });
4523
+ }
4524
+ }
4525
+ return found;
4526
+ }
4527
+ function argumentNode(call, at) {
4528
+ const args = field(call, "arguments");
4529
+ const written = args === null ? [] : children(args);
4530
+ const named = written.find(
4531
+ (one) => one.type === "keyword_argument" && at.keyword !== void 0 && field(one, "name")?.text === at.keyword
4532
+ );
4533
+ const value = named === void 0 ? null : field(named, "value");
4534
+ if (value !== null) {
4535
+ return value;
4536
+ }
4537
+ if (at.argument === void 0) {
4538
+ return null;
4539
+ }
4540
+ return written.filter((one) => one.type !== "keyword_argument")[at.argument] ?? null;
4541
+ }
4542
+ function statementAt(node, path6, facts) {
4543
+ const reached = valueAt(evaluatedValue(node, facts), path6 ?? []);
4544
+ const parts = reached === null ? null : literalParts(reached);
4141
4545
  if (parts === null) {
4142
4546
  return null;
4143
4547
  }
4144
4548
  const statement = sqlFromParts(parts);
4145
4549
  return statement.trim() === "" ? null : statement;
4146
4550
  }
4551
+ function valueAt(value, path6) {
4552
+ let reached = value;
4553
+ for (const key of path6) {
4554
+ if (reached.kind !== "record") {
4555
+ return null;
4556
+ }
4557
+ const item = reached.fields.get(key);
4558
+ if (item === void 0) {
4559
+ return null;
4560
+ }
4561
+ reached = force2(item.value);
4562
+ }
4563
+ return reached;
4564
+ }
4147
4565
  function literalParts(value) {
4148
4566
  if (value.kind !== "string") {
4149
4567
  return null;
@@ -4241,78 +4659,7 @@ function receiverName(chain) {
4241
4659
  return null;
4242
4660
  }
4243
4661
  const object = field(callee, "object");
4244
- return object !== null && object.type === "identifier" ? object.text : null;
4245
- }
4246
- function typeNameOf(annotation) {
4247
- if (annotation.type === "type" && annotation.namedChildren[0]) {
4248
- return typeNameOf(annotation.namedChildren[0]);
4249
- }
4250
- if (annotation.type === "identifier") {
4251
- return annotation.text;
4252
- }
4253
- const quoted = stringLiteralValue(annotation);
4254
- if (quoted !== null) {
4255
- return quoted;
4256
- }
4257
- if (annotation.type === "binary_operator") {
4258
- const named = [field(annotation, "left"), field(annotation, "right")].find(
4259
- (side) => side !== null && side.type !== "none"
4260
- );
4261
- return named === void 0 || named === null ? null : typeNameOf(named);
4262
- }
4263
- if (annotation.type === "generic_type") {
4264
- const outer = annotation.namedChildren[0];
4265
- const first = genericTypeArgs(annotation)[0];
4266
- if ((outer?.text === "Annotated" || outer?.text === "Optional") && first !== void 0) {
4267
- return typeNameOf(first);
4268
- }
4269
- return outer?.type === "identifier" ? outer.text : null;
4270
- }
4271
- return null;
4272
- }
4273
- function statedTypeName(name, from) {
4274
- const fn = enclosingFunction(from);
4275
- if (fn === null) {
4276
- return null;
4277
- }
4278
- const params = field(fn, "parameters");
4279
- for (const param of params === null ? [] : children(params)) {
4280
- const info = parameterNameAndType(param);
4281
- if (info?.name === name && info.typeNode !== null) {
4282
- return typeNameOf(info.typeNode);
4283
- }
4284
- }
4285
- const body = field(fn, "body");
4286
- return body === null ? null : firstInBody(body, (statement) => statedBy(statement, name));
4287
- }
4288
- function firstInBody(node, read) {
4289
- const here = read(node);
4290
- if (here !== null) {
4291
- return here;
4292
- }
4293
- for (const child of children(node)) {
4294
- if (child.type === "function_definition" || child.type === "lambda") {
4295
- continue;
4296
- }
4297
- const found = firstInBody(child, read);
4298
- if (found !== null) {
4299
- return found;
4300
- }
4301
- }
4302
- return null;
4303
- }
4304
- function statedBy(statement, name) {
4305
- if (statement.type === "assignment") {
4306
- const annotation = field(statement, "type");
4307
- return field(statement, "left")?.text === name && annotation !== null ? typeNameOf(annotation) : null;
4308
- }
4309
- if (statement.type === "as_pattern") {
4310
- const alias = field(statement, "alias");
4311
- const value = statement.namedChildren[0] ?? null;
4312
- const callee = value?.type === "call" ? field(value, "function") : null;
4313
- return alias?.text === name && callee?.type === "identifier" ? callee.text : null;
4314
- }
4315
- return null;
4662
+ return object !== null && object.type === "identifier" ? object : null;
4316
4663
  }
4317
4664
  function builtType(call, options) {
4318
4665
  const root = rootOf(call);
@@ -4360,7 +4707,7 @@ function typedReceiverPattern(options, chain) {
4360
4707
  if (receiver === null) {
4361
4708
  return void 0;
4362
4709
  }
4363
- for (const origin of receiverTypeOrigins(options, receiver, chain.root)) {
4710
+ for (const origin of receiverTypeOrigins(receiver, options)) {
4364
4711
  const pattern = queryTypePattern(options, origin.module, origin.name);
4365
4712
  if (pattern !== void 0) {
4366
4713
  return pattern;
@@ -4368,14 +4715,6 @@ function typedReceiverPattern(options, chain) {
4368
4715
  }
4369
4716
  return void 0;
4370
4717
  }
4371
- function receiverTypeOrigins(options, name, from) {
4372
- const stated = statedTypeName(name, from);
4373
- if (stated !== null) {
4374
- return originsOf(options.facts, `${options.filePath}#${stated}`);
4375
- }
4376
- const key = nameKeyIn(options.filePath, enclosingFunction(from), name);
4377
- return subjectConstructions(options.facts, [key]).get(key)?.origins ?? [];
4378
- }
4379
4718
  function queryTypePattern(options, module, name) {
4380
4719
  return options.patterns.find(
4381
4720
  (pattern) => module === pattern.module && pattern.queryTypes.includes(name)
@@ -4497,7 +4836,7 @@ function classBehind(typeName, options) {
4497
4836
  function isClassName(name) {
4498
4837
  return /^[A-Z][A-Za-z0-9_]*$/.test(name) && name !== name.toUpperCase();
4499
4838
  }
4500
- function effectFor(pattern, chain, operation, options) {
4839
+ function effectFor2(pattern, chain, operation, options) {
4501
4840
  const valueMethods = pattern.valueMethods ?? [];
4502
4841
  const picked = selectorOf(chain, valueMethods);
4503
4842
  return {
@@ -4607,7 +4946,7 @@ function storageCallIds(calls, options) {
4607
4946
  }
4608
4947
  function storageEffects(calls, options) {
4609
4948
  return matchedChains(calls, options).flatMap(
4610
- ({ chain, pattern, operation }) => operation === null ? [] : [effectFor(pattern, chain, operation, options)]
4949
+ ({ chain, pattern, operation }) => operation === null ? [] : [effectFor2(pattern, chain, operation, options)]
4611
4950
  );
4612
4951
  }
4613
4952
 
@@ -4642,8 +4981,10 @@ function decoratedStatements(root, module, facts) {
4642
4981
  }));
4643
4982
  }
4644
4983
  function discoverUnits(root, module, options) {
4984
+ const decorated = decoratedStatements(root, module, options.facts);
4985
+ askWrittenValues(clientCallReceivers(root, module), options.facts);
4645
4986
  return [
4646
- ...decoratedStatements(root, module, options.facts).flatMap(
4987
+ ...decorated.flatMap(
4647
4988
  (decoratedStatement) => decoratedUnits(decoratedStatement, module, options)
4648
4989
  ),
4649
4990
  ...clientUnits(root, module, options)
@@ -5429,22 +5770,29 @@ function recognizedBodyEffects(definitionNode, module, storageLookup, facts) {
5429
5770
  ...storageLookup,
5430
5771
  filePath: storageLookup.factsPath
5431
5772
  }),
5432
- ...rawSqlEffects(bodyCalls(definitionNode), {
5433
- facts: storageLookup.facts,
5434
- filePath: storageLookup.factsPath,
5435
- patterns: storageLookup.rawSql ?? []
5436
- })
5773
+ ...rawSqlEffects(bodyCalls(definitionNode), rawSqlOf(storageLookup))
5437
5774
  ];
5438
5775
  return [...envReadEffects(definitionNode, module, facts), ...storage];
5439
5776
  }
5440
- function recognizedCallIds(definitionNode, storageLookup) {
5777
+ function rawSqlOf(storageLookup) {
5778
+ return {
5779
+ facts: storageLookup.facts,
5780
+ filePath: storageLookup.factsPath,
5781
+ patterns: storageLookup.rawSql ?? [],
5782
+ clients: storageLookup.sqlClients ?? []
5783
+ };
5784
+ }
5785
+ function recognizedCallIds(calls, storageLookup) {
5441
5786
  if (storageLookup === void 0) {
5442
5787
  return /* @__PURE__ */ new Set();
5443
5788
  }
5444
- return storageCallIds(bodyCalls(definitionNode), {
5445
- ...storageLookup,
5446
- filePath: storageLookup.factsPath
5447
- });
5789
+ return /* @__PURE__ */ new Set([
5790
+ ...storageCallIds(calls, {
5791
+ ...storageLookup,
5792
+ filePath: storageLookup.factsPath
5793
+ }),
5794
+ ...rawSqlCallIds(calls, rawSqlOf(storageLookup))
5795
+ ]);
5448
5796
  }
5449
5797
  function bodyContentOf(bodyNode) {
5450
5798
  for (const stmt of bodyStatements(bodyNode)) {
@@ -5784,6 +6132,7 @@ import {
5784
6132
  composeWrappers,
5785
6133
  createCacheLayer,
5786
6134
  createTimer,
6135
+ effectToIR,
5787
6136
  moduleInitStructure,
5788
6137
  noopTimer,
5789
6138
  runDigest,
@@ -5916,11 +6265,14 @@ function resolveCallee2(call, site, ctx, read) {
5916
6265
  const outcome = read?.outcomes.get(spelling.key) ?? calleeOutcomeOf(ctx.facts, spelling.key);
5917
6266
  return asCallee(outcome, site.owner, ctx);
5918
6267
  }
5919
- function functionNamed(nameKey2, ctx) {
6268
+ function namedOutcomes(keys, ctx) {
6269
+ return calleeOutcomes(ctx.facts, [...new Set(keys)]);
6270
+ }
6271
+ function functionNamed(nameKey2, ctx, read) {
5920
6272
  if (assignedElsewhere(ctx.facts, nameKey2)) {
5921
6273
  return null;
5922
6274
  }
5923
- const outcome = calleeOutcomeOf(ctx.facts, nameKey2);
6275
+ const outcome = read?.get(nameKey2) ?? calleeOutcomeOf(ctx.facts, nameKey2);
5924
6276
  return outcome.kind === "function" ? functionAt(outcome.key, ctx) : null;
5925
6277
  }
5926
6278
  function assignedElsewhere(facts, nameKey2) {
@@ -6128,6 +6480,7 @@ function reachedFunctions(seeds, options) {
6128
6480
  const stopsByKey = /* @__PURE__ */ new Map();
6129
6481
  const argTargetsByKey = /* @__PURE__ */ new Map();
6130
6482
  const parameterCallsByKey = /* @__PURE__ */ new Map();
6483
+ const callsByKey = /* @__PURE__ */ new Map();
6131
6484
  const passedPositions = /* @__PURE__ */ new Set();
6132
6485
  for (const seed of seeds) {
6133
6486
  seedKeys.add(seed.key);
@@ -6154,12 +6507,21 @@ function reachedFunctions(seeds, options) {
6154
6507
  bodies.flatMap((body) => body.written),
6155
6508
  ctx
6156
6509
  );
6510
+ const passed = namedOutcomes(
6511
+ bodies.flatMap(
6512
+ ({ source, written }) => written.flatMap(({ call }) => passedNameKeys(source.file, call))
6513
+ ),
6514
+ ctx
6515
+ );
6157
6516
  for (const { key, source, body, written } of bodies) {
6158
6517
  const scan = scanBody(
6159
6518
  source,
6160
6519
  ctx,
6161
- recognizedCallIds(source.node, options.storageFor(source.file)),
6162
- { body, written, spellings }
6520
+ recognizedCallIds(
6521
+ written.map(({ call }) => call),
6522
+ options.storageFor(source.file)
6523
+ ),
6524
+ { body, written, spellings, passed }
6163
6525
  );
6164
6526
  if (scan.stops.length > 0) {
6165
6527
  stopsByKey.set(key, scan.stops);
@@ -6172,21 +6534,27 @@ function reachedFunctions(seeds, options) {
6172
6534
  for (const position of scan.passedPositions) {
6173
6535
  passedPositions.add(position);
6174
6536
  }
6537
+ const called = [];
6175
6538
  for (const target of scan.followed) {
6176
- const calleeKey = keyOf(target);
6539
+ const calleeKey = keyOf2(target);
6177
6540
  if (!functionByKey.has(calleeKey)) {
6178
6541
  functionByKey.set(calleeKey, target);
6179
6542
  }
6180
6543
  db.add("calls", [key, calleeKey]);
6544
+ called.push(calleeKey);
6545
+ }
6546
+ if (called.length > 0) {
6547
+ callsByKey.set(key, called);
6181
6548
  }
6182
6549
  }
6183
6550
  }
6551
+ const reached = db.facts("reachable").map(([keyAtom]) => String(keyAtom)).filter((key) => !seedKeys.has(key));
6552
+ settleBodyValues(reached, functionByKey, options.facts);
6184
6553
  const summaries = [];
6185
6554
  const summariesByKey = /* @__PURE__ */ new Map();
6186
- for (const [keyAtom] of db.facts("reachable")) {
6187
- const key = String(keyAtom);
6555
+ for (const key of reached) {
6188
6556
  const target = functionByKey.get(key);
6189
- if (seedKeys.has(key) || target === void 0) {
6557
+ if (target === void 0) {
6190
6558
  continue;
6191
6559
  }
6192
6560
  const summary = assembleSummary(libraryUnit(target, options), {
@@ -6211,10 +6579,11 @@ function reachedFunctions(seeds, options) {
6211
6579
  stopsByKey,
6212
6580
  argTargetsByKey,
6213
6581
  parameterCallsByKey,
6582
+ callsByKey,
6214
6583
  passedPositions
6215
6584
  };
6216
6585
  }
6217
- function keyOf(target) {
6586
+ function keyOf2(target) {
6218
6587
  return nodeId(target.file.file, target.node);
6219
6588
  }
6220
6589
  var EMPTY_SCAN = {
@@ -6227,15 +6596,53 @@ var EMPTY_SCAN = {
6227
6596
  };
6228
6597
  function bodyOf(source) {
6229
6598
  const { file, node } = source;
6230
- const body = field(node, "body");
6599
+ const body = isModule(node) ? node : field(node, "body");
6231
6600
  if (body === null) {
6232
6601
  return { body, written: [] };
6233
6602
  }
6234
6603
  return {
6235
6604
  body,
6236
- written: callsWritten(file, body, scopeAt2(file, node), keyOf(source))
6605
+ written: callsWritten(file, body, scopeAt2(file, node), keyOf2(source), {
6606
+ descendsInto: isModule(node) ? runsAtModuleLoad : outsideNestedDef
6607
+ })
6237
6608
  };
6238
6609
  }
6610
+ function outsideNestedDef(node) {
6611
+ return node.type !== "function_definition";
6612
+ }
6613
+ function settleBodyValues(reached, functionByKey, facts) {
6614
+ const byFile = /* @__PURE__ */ new Map();
6615
+ for (const key of reached) {
6616
+ const target = functionByKey.get(key);
6617
+ if (target === void 0) {
6618
+ continue;
6619
+ }
6620
+ const listed = byFile.get(target.file.file) ?? [];
6621
+ for (const node of bodyValueNodes(target.node)) {
6622
+ listed.push(node);
6623
+ }
6624
+ byFile.set(target.file.file, listed);
6625
+ }
6626
+ for (const nodes of byFile.values()) {
6627
+ askWrittenValues(nodes, facts);
6628
+ }
6629
+ }
6630
+ function passedNameKeyOf(file, arg) {
6631
+ if (arg === null || arg.type !== "identifier") {
6632
+ return null;
6633
+ }
6634
+ return readKey(file.file, arg, enclosingFunction(arg));
6635
+ }
6636
+ function passedNameKeys(file, call) {
6637
+ const args = field(call, "arguments");
6638
+ if (args === null) {
6639
+ return [];
6640
+ }
6641
+ return args.namedChildren.flatMap((arg) => {
6642
+ const key = passedNameKeyOf(file, arg);
6643
+ return key === null ? [] : [key];
6644
+ });
6645
+ }
6239
6646
  function scanBody(source, ctx, recognized, read) {
6240
6647
  const followed = [];
6241
6648
  const stops = [];
@@ -6256,17 +6663,15 @@ function scanBody(source, ctx, recognized, read) {
6256
6663
  return;
6257
6664
  }
6258
6665
  args.namedChildren.forEach((arg, position) => {
6259
- if (arg === null || arg.type !== "identifier") {
6666
+ const nameKey2 = passedNameKeyOf(file, arg);
6667
+ if (nameKey2 === null) {
6260
6668
  return;
6261
6669
  }
6262
- const resolved = functionNamed(
6263
- readKey(file.file, arg, enclosingFunction(arg)),
6264
- ctx
6265
- );
6670
+ const resolved = functionNamed(nameKey2, ctx, read.passed);
6266
6671
  if (resolved === null) {
6267
6672
  return;
6268
6673
  }
6269
- const key = keyOf(resolved);
6674
+ const key = keyOf2(resolved);
6270
6675
  if (!seen.has(key)) {
6271
6676
  seen.add(key);
6272
6677
  followed.push(resolved);
@@ -6291,7 +6696,7 @@ function scanBody(source, ctx, recognized, read) {
6291
6696
  recordPassedArgs(
6292
6697
  call,
6293
6698
  callee,
6294
- outcome.kind === "followed" ? keyOf(outcome.target) : null
6699
+ outcome.kind === "followed" ? keyOf2(outcome.target) : null
6295
6700
  );
6296
6701
  if (outcome.kind === "stopped") {
6297
6702
  const stopKey = `${outcome.reason}:${callee}`;
@@ -6308,7 +6713,7 @@ function scanBody(source, ctx, recognized, read) {
6308
6713
  }
6309
6714
  return;
6310
6715
  }
6311
- const key = keyOf(outcome.target);
6716
+ const key = keyOf2(outcome.target);
6312
6717
  if (!seen.has(key)) {
6313
6718
  seen.add(key);
6314
6719
  followed.push(outcome.target);
@@ -6326,7 +6731,7 @@ function scanBody(source, ctx, recognized, read) {
6326
6731
  passedPositions
6327
6732
  };
6328
6733
  }
6329
- function callsWritten(file, body, outer, owner) {
6734
+ function callsWritten(file, body, outer, owner, limit) {
6330
6735
  const found = [];
6331
6736
  walkDescendants3(body, outer, {
6332
6737
  at: (child, scope) => {
@@ -6334,7 +6739,7 @@ function callsWritten(file, body, outer, owner) {
6334
6739
  found.push({ call: child, site: { file, scope, owner } });
6335
6740
  }
6336
6741
  },
6337
- into: (child, scope) => child.type === "function_definition" ? SKIP_CHILDREN2 : file.module.scopeFor.get(child.id) ?? scope
6742
+ into: (child, scope) => limit.descendsInto(child) ? file.module.scopeFor.get(child.id) ?? scope : SKIP_CHILDREN2
6338
6743
  });
6339
6744
  return found;
6340
6745
  }
@@ -8108,10 +8513,7 @@ function factsForFile(options) {
8108
8513
  files: [{ file: options.file, root: options.root, module: options.module }],
8109
8514
  definitions
8110
8515
  });
8111
- bindEnvNameSites(
8112
- db,
8113
- envNameSites(options.file, options.root, options.module)
8114
- );
8516
+ bindEnvFacts(db, [envFactsIn(options.file, options.root, options.module)]);
8115
8517
  addPackWords(db, packWordsOf(options.packs));
8116
8518
  return db;
8117
8519
  }
@@ -8162,6 +8564,7 @@ async function extractPythonProject(options) {
8162
8564
  );
8163
8565
  const storagePatterns = options.packs.flatMap((pack) => pack.storage ?? []);
8164
8566
  const rawSqlPatterns = options.packs.flatMap((pack) => pack.rawSql ?? []);
8567
+ const sqlClients = options.packs.flatMap((pack) => pack.sqlClients ?? []);
8165
8568
  const modelQueries = options.packs.flatMap((pack) => pack.models ?? []);
8166
8569
  const discovers = options.packs.some((pack) => pack.discovery.length > 0);
8167
8570
  const buildsReceivers = options.packs.some(
@@ -8169,10 +8572,15 @@ async function extractPythonProject(options) {
8169
8572
  (client) => (client.receiverConstructors ?? []).length > 0
8170
8573
  )
8171
8574
  );
8172
- const envSites = bound.flatMap(
8173
- (boundFile) => envNameSites(boundFile.file, boundFile.root, boundFile.module)
8575
+ const envFacts = bound.map(
8576
+ (boundFile) => envFactsIn(boundFile.file, boundFile.root, boundFile.module)
8577
+ );
8578
+ const readsEnvThroughNames = envFacts.some(
8579
+ (one) => one.sites.length > 0 || one.objects.length > 0
8174
8580
  );
8175
- const needsValues = discovers || mountsRouters || buildsReceivers || envSites.length > 0 || storagePatterns.length > 0 || modelQueries.length > 0;
8581
+ const needsValues = discovers || mountsRouters || buildsReceivers || readsEnvThroughNames || storagePatterns.length > 0 || modelQueries.length > 0 || // A statement written as SQL is a value, and so is the client the
8582
+ // call that hands it over is read off.
8583
+ rawSqlPatterns.length > 0 || sqlClients.length > 0;
8176
8584
  const definitions = /* @__PURE__ */ new Map();
8177
8585
  timer.time("discover", () => {
8178
8586
  for (const { file, root, module: moduleBinding } of bound) {
@@ -8184,7 +8592,7 @@ async function extractPythonProject(options) {
8184
8592
  }
8185
8593
  if (needsValues) {
8186
8594
  bindEvaluator(db, { files: bound, definitions });
8187
- bindEnvNameSites(db, envSites);
8595
+ bindEnvFacts(db, envFacts);
8188
8596
  }
8189
8597
  addPackWords(db, packWordsOf(options.packs));
8190
8598
  });
@@ -8200,13 +8608,14 @@ async function extractPythonProject(options) {
8200
8608
  ...mountsRouters ? { facts: db } : {}
8201
8609
  })
8202
8610
  );
8203
- const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 ? {
8611
+ const storageFor = (file) => storagePatterns.length > 0 || rawSqlPatterns.length > 0 || sqlClients.length > 0 ? {
8204
8612
  facts: db,
8205
8613
  factsPath: file.file,
8206
8614
  patterns: storagePatterns,
8207
8615
  definitionAt: (key) => definitions.get(key),
8208
8616
  couldMatch,
8209
- rawSql: rawSqlPatterns
8617
+ rawSql: rawSqlPatterns,
8618
+ sqlClients
8210
8619
  } : void 0;
8211
8620
  const wrapperIndex = timer.time(
8212
8621
  "discover",
@@ -8241,6 +8650,7 @@ async function extractPythonProject(options) {
8241
8650
  }
8242
8651
  const seeds = [];
8243
8652
  const summariesBySeed = /* @__PURE__ */ new Map();
8653
+ const moduleRoots = [];
8244
8654
  for (const boundFile of bound) {
8245
8655
  const { file, root, module: moduleBinding } = boundFile;
8246
8656
  const displayPath = displayPathOf(file);
@@ -8275,21 +8685,15 @@ async function extractPythonProject(options) {
8275
8685
  "discover",
8276
8686
  () => envReadEffects(root, moduleBinding, db)
8277
8687
  );
8278
- if (loadTimeReads.length > 0) {
8279
- const summary = timer.time(
8280
- "summarize",
8281
- () => assembleSummary2(
8282
- moduleInitStructure({
8283
- name: path3.basename(displayPath),
8284
- file: displayPath,
8285
- range: rangeOf(root),
8286
- effects: loadTimeReads
8287
- }),
8288
- { gapHandling }
8289
- )
8290
- );
8291
- summary.confidence = { source: "inferred_static", level: "low" };
8292
- summaries.push(summary);
8688
+ const moduleKey = nodeId(file, root);
8689
+ if (!summariesBySeed.has(moduleKey)) {
8690
+ seeds.push({ key: moduleKey, file: boundFile, node: root });
8691
+ moduleRoots.push({
8692
+ boundFile,
8693
+ displayPath,
8694
+ key: moduleKey,
8695
+ loadTimeReads
8696
+ });
8293
8697
  }
8294
8698
  }
8295
8699
  const reached = timer.time(
@@ -8303,16 +8707,19 @@ async function extractPythonProject(options) {
8303
8707
  definitions
8304
8708
  })
8305
8709
  );
8710
+ const placeWhatItReached = (summary, key) => {
8711
+ if (gapHandling !== "silent") {
8712
+ summary.gaps.push(
8713
+ ...(reached.stopsByKey.get(key) ?? []).map(unfollowedCallGap2)
8714
+ );
8715
+ }
8716
+ placeCalls2(summary, reached.targetsByKey.get(key));
8717
+ placeArgTargets2(summary, reached.argTargetsByKey.get(key));
8718
+ placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
8719
+ };
8306
8720
  for (const [key, owners] of summariesBySeed) {
8307
8721
  for (const summary of owners) {
8308
- if (gapHandling !== "silent") {
8309
- summary.gaps.push(
8310
- ...(reached.stopsByKey.get(key) ?? []).map(unfollowedCallGap2)
8311
- );
8312
- }
8313
- placeCalls2(summary, reached.targetsByKey.get(key));
8314
- placeArgTargets2(summary, reached.argTargetsByKey.get(key));
8315
- placeCalleeParameters2(summary, reached.parameterCallsByKey.get(key));
8722
+ placeWhatItReached(summary, key);
8316
8723
  }
8317
8724
  }
8318
8725
  if (gapHandling !== "silent") {
@@ -8322,6 +8729,30 @@ async function extractPythonProject(options) {
8322
8729
  reached.passedPositions
8323
8730
  );
8324
8731
  }
8732
+ for (const { boundFile, displayPath, key, loadTimeReads } of moduleRoots) {
8733
+ const calledAtLoad = reached.callsByKey.get(key) ?? [];
8734
+ if (loadTimeReads.length === 0 && calledAtLoad.length === 0) {
8735
+ continue;
8736
+ }
8737
+ const summary = timer.time(
8738
+ "summarize",
8739
+ () => assembleSummary2(
8740
+ moduleInitStructure({
8741
+ name: path3.basename(displayPath),
8742
+ file: displayPath,
8743
+ range: rangeOf(boundFile.root),
8744
+ effects: [
8745
+ ...loadTimeReads,
8746
+ ...moduleLoadInvocationEffects(boundFile.root, db).map(effectToIR)
8747
+ ]
8748
+ }),
8749
+ { gapHandling }
8750
+ )
8751
+ );
8752
+ summary.confidence = { source: "inferred_static", level: "low" };
8753
+ placeWhatItReached(summary, key);
8754
+ summaries.push(summary);
8755
+ }
8325
8756
  summaries.push(...reached.summaries);
8326
8757
  const resolvedImports = resolvedImportsOf(db, displayPathOf);
8327
8758
  stampModuleImports(summaries, (file) => resolvedImports.get(file) ?? []);