@suss/resolution 0.7.2 → 0.9.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/README.md CHANGED
@@ -57,14 +57,42 @@ demand nothing generates.
57
57
 
58
58
  Node identity is the adapter's business. The rules only join on it.
59
59
 
60
+ ## One relation of steps
61
+
62
+ Every construct states its hops once, in one relation:
63
+
64
+ ```
65
+ stepsTo(x, y, kind) following x leads to y in one hop
66
+ reaches(x, z, kind) the closure of those hops
67
+ ```
68
+
69
+ A value step goes to the value x is written as: a name to its
70
+ declaration, an import to what the module exports, a parameter to what a
71
+ call passes it, a property read to what the object contains under that
72
+ name, a construction to the class, a transparent wrapper to the argument
73
+ it wraps. A result step goes the other way, from a call to what the
74
+ function it invokes returns. A walk counts as a result walk once it has
75
+ run a call anywhere along it, which is what keeps a factory call out of
76
+ the answers that stop at a value.
77
+
78
+ A language with a hop of its own states it as a step too. JavaScript's
79
+ `.bind` and Ruby's `Const.new` are each one rule, and every question
80
+ below picks them up without being told.
81
+
82
+ Each question is that one closure with its own stopping condition. So
83
+ adding a construct is one step and every question gets it, and adding a
84
+ question is a stopping condition and no steps at all.
85
+
60
86
  ## What comes out
61
87
 
62
88
  ```
63
89
  comesTo(x, z) following x arrives at the value z
64
90
  resolves(x, z) comesTo narrowed to functions
91
+ givesBack(x, z) following x arrives at a call that returns z
65
92
  isWrittenAs(x, z) x is written as the expression z
66
93
  comesFrom(x, m, n) following x arrives at m's export n
67
94
  callsInto(f, m, n) calling f ends up calling m's n
95
+ paramAt(r, p, z) the call r puts z in the parameter p
68
96
  ```
69
97
 
70
98
  `resolves` is the question most callers ask. `comesTo` is the one
@@ -72,6 +100,21 @@ underneath it, and it can come back with an object, because a chain has
72
100
  to pass through objects for `routes.list` to reach whatever `list`
73
101
  contains.
74
102
 
103
+ `givesBack` is the other direction of function application. `comesTo`
104
+ says what a value comes down to, and it stops at a call on purpose: a
105
+ factory call is usually the wrapper itself, so resolving it to the
106
+ function it returned would fight the unwrapping answer. That leaves
107
+ nobody able to ask what a call returned, which is the whole question
108
+ when a factory builds a dependency. `givesBack` asks it, over the same
109
+ steps and with the same stopping condition, of the walks that ran a
110
+ call.
111
+
112
+ Both directions run at once without interfering, because they answer
113
+ different questions about the same call. `const dao = makeDao()` comes
114
+ to nothing and gives back the class `makeDao` constructed, so
115
+ `dao.findByCustomer` finds the method that class declares while
116
+ `withAuth(handler)` still comes to `handler`.
117
+
75
118
  `isWrittenAs` follows the same names to the expression a value is
76
119
  written as, whatever kind of expression that turns out to be. A GraphQL
77
120
  document is neither a function nor an object, so `comesTo` never
@@ -80,8 +123,14 @@ reaches one.
80
123
  `comesFrom` covers the direction `comesTo` cannot. Every `comesTo` chain
81
124
  ends at something written out in the source suss is reading, so a name
82
125
  for a library's own function ends nowhere, because the library's body is
83
- not here. `comesFrom` follows the same aliases, imports and barrels, and
84
- comes back with the module plus the name that module exports.
126
+ not here. `comesFrom` walks the same steps and stops at the import
127
+ instead, so it comes back with the module plus the name that module
128
+ exports.
129
+
130
+ `paramAt` is the one question that keeps the call it went through.
131
+ `comesTo` merges call sites, so a function called from two places leaves
132
+ its parameter with two values and a caller wanting one gets nothing.
133
+ `paramAt` says which call put which value there.
85
134
 
86
135
  `callsInto` puts that together with the calls a function makes. A
87
136
  project writes its own decorator that calls `Resolver()` and applies
@@ -91,19 +140,24 @@ normal rather than ambiguous, because a wrapper that combines two
91
140
  library decorators applies both of them, so a caller asks whether the
92
141
  one it cares about is among them.
93
142
 
94
- One relation exists for the rules' own use rather than for callers:
143
+ Two relations exist for the rules' own use rather than for callers:
95
144
 
96
145
  ```
97
146
  objectOf(x, obj) x stands for the object literal obj
147
+ invokes(r, f) the call r runs the function f
98
148
  ```
99
149
 
100
150
  An object arrives two ways, through a name or as what a factory call
101
- returns. `objectOf` gives that step a name, so the rule for
102
- `routes.list` and the rule for `make(body).handle` are the same rule. A
103
- factory call gets an `objectOf` answer without getting a `comesTo`
104
- answer, because a factory call is usually the wrapper itself, and
105
- answering with the function it returns would fight the unwrapping
106
- answer.
151
+ gives back. `objectOf` gives that step a name, so the rule for
152
+ `routes.list` and the rule for `make(body).handle` are the same rule.
153
+ This is where the two directions meet: a factory call gets an
154
+ `objectOf` answer without getting a `comesTo` answer.
155
+
156
+ `invokes` is the callee half of `givesBack`, kept apart so the case
157
+ where the callee is itself a call, `daoBuilder()()`, is one rule rather
158
+ than a copy of every other. It differs from `callsFunction`, which
159
+ starts from the function because a caller asking for call sites has the
160
+ function in hand.
107
161
 
108
162
  ## Why rules and not a walker
109
163
 
@@ -119,6 +173,15 @@ resolved 11 handlers and missed most of what the template declared. The
119
173
  fix was one condition in fact extraction, and the existing rules found
120
174
  the rest without any change.
121
175
 
176
+ ## Changing a rule
177
+
178
+ Every rule here composes with every other one, so a change meant for one
179
+ construct lands on chains nobody had in mind. `@suss/resolution-fuzz`
180
+ generates four thousand fact bases, runs these rules over each, and
181
+ compares what came out against a committed baseline, which is what turns
182
+ a change into a diff a reviewer reads. Run `npm run resolution:baseline`
183
+ to accept one, and commit the rewritten file with the rule.
184
+
122
185
  ## What is not modelled
123
186
 
124
187
  **A handler passed as one property of a config.** `make({ body:
package/dist/index.cjs CHANGED
@@ -24,6 +24,8 @@ __export(index_exports, {
24
24
  FACT_CONTRACT_CASES: () => FACT_CONTRACT_CASES,
25
25
  RESOLUTION_QUESTIONS: () => RESOLUTION_QUESTIONS,
26
26
  RESOLUTION_RULES: () => RESOLUTION_RULES,
27
+ RESULT_STEP: () => RESULT_STEP,
28
+ VALUE_STEP: () => VALUE_STEP,
27
29
  checkFactContract: () => checkFactContract
28
30
  });
29
31
  module.exports = __toCommonJS(index_exports);
@@ -123,6 +125,26 @@ var FACT_CONTRACT_CASES = [
123
125
  return contained.some((row) => functions.has(row[2])) ? null : "what a class contains under a method name is not the node that declares the method";
124
126
  }
125
127
  },
128
+ {
129
+ name: "a class constructed with an argument",
130
+ requires: "a module-level class whose constructor takes one parameter, and a site that makes one of it, passing a name",
131
+ // However a language writes construction, it is a call of the class, and
132
+ // the constructor's parameters are the class's. Keying them on the
133
+ // constructor instead leaves the argument with nowhere to land.
134
+ check: (facts) => {
135
+ const classes = new Set(facts("objectValue").map((row) => row[0] ?? ""));
136
+ const parameters = facts("paramOf").filter(
137
+ (row) => classes.has(row[0] ?? "")
138
+ );
139
+ if (parameters.length === 0) {
140
+ return "a class lists none of its constructor's parameters, so an argument a construction site passes reaches nothing";
141
+ }
142
+ if (facts("call").length === 0) {
143
+ return "making one of a class is not written down as a call, so nothing says which class a construction arrives at";
144
+ }
145
+ return facts("callArg").length > 0 ? null : "a construction passes its argument nowhere, so the parameter it lands in has no value";
146
+ }
147
+ },
126
148
  {
127
149
  name: "a value another file declares",
128
150
  requires: "two files, one declaring a value and the other reading it by name",
@@ -182,35 +204,202 @@ function checkFactContract(sources, emit, options = {}) {
182
204
 
183
205
  // src/index.ts
184
206
  var import_datalog = require("@suss/datalog");
207
+ var VALUE_STEP = (0, import_datalog.constant)("value");
208
+ var RESULT_STEP = (0, import_datalog.constant)("result");
185
209
  var RESOLUTION_RULES = [
186
- // A value comes to itself; every chain ends at something written out
187
- // in source, a function or an object.
210
+ // Aliasing: const x = y, or an identifier referencing a declaration.
211
+ // A language with a hop of its own, like JavaScript's `.bind`, states
212
+ // it as a step too, or every question but `comesTo` misses it.
213
+ (0, import_datalog.rule)("stepsTo", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"), VALUE_STEP], [(0, import_datalog.lit)("binds", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"))]),
214
+ // A name written more than once has the value the last write left
215
+ // there. The adapter works out which write that is and stays quiet
216
+ // when control flow decides, so such a name steps nowhere at all.
217
+ (0, import_datalog.rule)(
218
+ "stepsTo",
219
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"), VALUE_STEP],
220
+ [(0, import_datalog.lit)("endsHolding", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"))]
221
+ ),
222
+ // An import steps to what the module exports under that name.
223
+ (0, import_datalog.rule)(
224
+ "stepsTo",
225
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("value"), VALUE_STEP],
226
+ [
227
+ (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")),
228
+ (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value"))
229
+ ]
230
+ ),
231
+ // A parameter steps to what a call passes it. A function called from
232
+ // several places leaves its parameter with more than one value, and a
233
+ // caller that needs those apart asks `paramAt`.
234
+ (0, import_datalog.rule)(
235
+ "stepsTo",
236
+ [(0, import_datalog.variable)("p"), (0, import_datalog.variable)("a"), VALUE_STEP],
237
+ [(0, import_datalog.lit)("passesArgument", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("p"), (0, import_datalog.variable)("a"))]
238
+ ),
239
+ // Reading a property steps to what the object contains under that
240
+ // name, whichever way the object arrived: `routes.list` off a name,
241
+ // or `make(body).handle` off a call.
242
+ (0, import_datalog.rule)(
243
+ "stepsTo",
244
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("held"), VALUE_STEP],
245
+ [
246
+ (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.variable)("n")),
247
+ (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")),
248
+ (0, import_datalog.lit)("contains", (0, import_datalog.variable)("obj"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("held"))
249
+ ]
250
+ ),
251
+ // Calling a class makes one of it, so the call steps to the class and
252
+ // a method read off the result is the one the class declares. The
253
+ // caveat below is about a factory function, and a class is not one.
254
+ (0, import_datalog.rule)(
255
+ "stepsTo",
256
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("cls"), VALUE_STEP],
257
+ [
258
+ (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
259
+ (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("cls")),
260
+ (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("cls"))
261
+ ]
262
+ ),
263
+ // Wrapper transparency, derived: calling a factory that returns a
264
+ // function which calls its parameter k steps to argument k.
265
+ (0, import_datalog.rule)(
266
+ "stepsTo",
267
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("a"), VALUE_STEP],
268
+ [
269
+ (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
270
+ (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
271
+ (0, import_datalog.lit)("unwraps", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k")),
272
+ (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a"))
273
+ ]
274
+ ),
275
+ // Wrapper transparency, declared: a pack says this callee wraps
276
+ // argument k. The callee has to come from the library the pack said,
277
+ // so a local object spelled the same way is not mistaken for it.
278
+ (0, import_datalog.rule)(
279
+ "stepsTo",
280
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("a"), VALUE_STEP],
281
+ [
282
+ (0, import_datalog.lit)("calleeName", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("n")),
283
+ (0, import_datalog.lit)("unwrapsByName", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("k")),
284
+ (0, import_datalog.lit)("wrapperModule", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("m")),
285
+ (0, import_datalog.lit)("calleeOrigin", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("m")),
286
+ (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a"))
287
+ ]
288
+ ),
289
+ // The one step that runs a function forwards: a call steps to what
290
+ // the function it invokes returns.
291
+ (0, import_datalog.rule)(
292
+ "stepsTo",
293
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("ret"), RESULT_STEP],
294
+ [(0, import_datalog.lit)("invokes", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")), (0, import_datalog.lit)("returnsValue", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("ret"))]
295
+ ),
296
+ // Where a walk gets to, and whether it ran a call on the way. A value
297
+ // step keeps whatever the rest of the walk was, and a result step
298
+ // makes the whole walk one that ran a call.
299
+ (0, import_datalog.rule)(
300
+ "reaches",
301
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), (0, import_datalog.variable)("kind")],
302
+ [(0, import_datalog.lit)("stepsTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), (0, import_datalog.variable)("kind"))]
303
+ ),
304
+ (0, import_datalog.rule)(
305
+ "reaches",
306
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), (0, import_datalog.variable)("kind")],
307
+ [
308
+ (0, import_datalog.lit)("stepsTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"), VALUE_STEP),
309
+ (0, import_datalog.lit)("reaches", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"), (0, import_datalog.variable)("kind"))
310
+ ]
311
+ ),
312
+ // Two rules rather than one leaving the rest of the walk's kind free.
313
+ // A free kind is a second question about the same relation, and a
314
+ // demand-driven run then derives both to answer either.
315
+ (0, import_datalog.rule)(
316
+ "reaches",
317
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), RESULT_STEP],
318
+ [
319
+ (0, import_datalog.lit)("stepsTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"), RESULT_STEP),
320
+ (0, import_datalog.lit)("reaches", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"), VALUE_STEP)
321
+ ]
322
+ ),
323
+ (0, import_datalog.rule)(
324
+ "reaches",
325
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), RESULT_STEP],
326
+ [
327
+ (0, import_datalog.lit)("stepsTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"), RESULT_STEP),
328
+ (0, import_datalog.lit)("reaches", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"), RESULT_STEP)
329
+ ]
330
+ ),
331
+ // What a value comes down to: the walk that never ran a call, stopped
332
+ // at a function or an object. Something already written out comes to
333
+ // itself, and every other walk ends at one of those.
188
334
  (0, import_datalog.rule)("comesTo", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("func", (0, import_datalog.variable)("x"))]),
189
335
  (0, import_datalog.rule)("comesTo", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("x"))]),
190
- // Aliasing: const x = y, or an identifier referencing a declaration.
191
336
  (0, import_datalog.rule)(
192
337
  "comesTo",
193
338
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
194
- [(0, import_datalog.lit)("binds", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
339
+ [(0, import_datalog.lit)("reaches", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), VALUE_STEP), (0, import_datalog.lit)("func", (0, import_datalog.variable)("z"))]
195
340
  ),
196
- // A name written more than once has the value the last write left
197
- // there, and `binds` says nothing about it. The adapter works out
198
- // which write that is, in its own language's terms, and stays quiet
199
- // when control flow decides. A name it stays quiet about comes to no
200
- // value, which is the answer.
201
341
  (0, import_datalog.rule)(
202
342
  "comesTo",
203
343
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
204
- [(0, import_datalog.lit)("endsHolding", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
344
+ [(0, import_datalog.lit)("reaches", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), VALUE_STEP), (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("z"))]
205
345
  ),
206
- // An import comes to what the module exports under that name.
346
+ // The same stopping condition, for the walk that ran a call. A call
347
+ // gets no `comesTo` answer on purpose: coming back with what a
348
+ // factory returned would fight the unwrapping answer.
207
349
  (0, import_datalog.rule)(
208
- "comesTo",
350
+ "givesBack",
351
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
352
+ [(0, import_datalog.lit)("reaches", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), RESULT_STEP), (0, import_datalog.lit)("func", (0, import_datalog.variable)("z"))]
353
+ ),
354
+ (0, import_datalog.rule)(
355
+ "givesBack",
356
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
357
+ [(0, import_datalog.lit)("reaches", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), RESULT_STEP), (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("z"))]
358
+ ),
359
+ // Following a value to the expression it is written as, whatever kind
360
+ // that is. A GraphQL document is neither a function nor an object, so
361
+ // `comesTo` never reaches one.
362
+ (0, import_datalog.rule)("isWrittenAs", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("writtenValue", (0, import_datalog.variable)("x"))]),
363
+ (0, import_datalog.rule)("isWrittenAs", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("x"))]),
364
+ (0, import_datalog.rule)(
365
+ "isWrittenAs",
366
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
367
+ [(0, import_datalog.lit)("reaches", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), VALUE_STEP), (0, import_datalog.lit)("writtenValue", (0, import_datalog.variable)("z"))]
368
+ ),
369
+ (0, import_datalog.rule)(
370
+ "isWrittenAs",
209
371
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
372
+ [(0, import_datalog.lit)("reaches", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"), VALUE_STEP), (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("z"))]
373
+ ),
374
+ // What one call site put in a parameter, told apart from what the
375
+ // other callers passed.
376
+ (0, import_datalog.rule)(
377
+ "paramAt",
378
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("p"), (0, import_datalog.variable)("z")],
210
379
  [
211
- (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")),
212
- (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value")),
213
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("value"), (0, import_datalog.variable)("z"))
380
+ (0, import_datalog.lit)("passesArgument", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("p"), (0, import_datalog.variable)("a")),
381
+ (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("z"))
382
+ ]
383
+ ),
384
+ // An argument arriving at the parameter it is passed to, by position
385
+ // or by the name the caller wrote, keeping the call it went through
386
+ // so `paramAt` can tell two call sites apart.
387
+ (0, import_datalog.rule)(
388
+ "passesArgument",
389
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("p"), (0, import_datalog.variable)("a")],
390
+ [
391
+ (0, import_datalog.lit)("paramOf", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("p")),
392
+ (0, import_datalog.lit)("callsFunction", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")),
393
+ (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a"))
394
+ ]
395
+ ),
396
+ (0, import_datalog.rule)(
397
+ "passesArgument",
398
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("p"), (0, import_datalog.variable)("a")],
399
+ [
400
+ (0, import_datalog.lit)("paramNamed", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("p")),
401
+ (0, import_datalog.lit)("callsFunction", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")),
402
+ (0, import_datalog.lit)("callKeywordArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("a"))
214
403
  ]
215
404
  ),
216
405
  // What a module exports: directly, or through re-export chains.
@@ -235,12 +424,22 @@ var RESOLUTION_RULES = [
235
424
  (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value"))
236
425
  ]
237
426
  ),
238
- // The object an expression refers to. A name arrives at one by
239
- // following `comesTo` through aliases and imports. A factory call
240
- // arrives at one through what the function it calls returns; the
241
- // call itself is given no `comesTo`, since a factory call usually IS
242
- // the wrapper and answering with the raw returned function would
243
- // fight the unwraps answer.
427
+ // The function a call runs, written from the call's side because a
428
+ // caller asking what a call gives back has the call in hand.
429
+ (0, import_datalog.rule)(
430
+ "invokes",
431
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")],
432
+ [(0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")), (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f"))]
433
+ ),
434
+ // The callee is itself a call: `daoBuilder()()`.
435
+ (0, import_datalog.rule)(
436
+ "invokes",
437
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")],
438
+ [(0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")), (0, import_datalog.lit)("givesBack", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f"))]
439
+ ),
440
+ // The object an expression refers to: a name through `comesTo`, a
441
+ // factory call through what it gives back. Naming the step is what
442
+ // makes `routes.list` and `make(body).handle` one rule.
244
443
  (0, import_datalog.rule)(
245
444
  "objectOf",
246
445
  [(0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")],
@@ -248,14 +447,8 @@ var RESOLUTION_RULES = [
248
447
  ),
249
448
  (0, import_datalog.rule)(
250
449
  "objectOf",
251
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("obj")],
252
- [
253
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
254
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
255
- (0, import_datalog.lit)("returnsValue", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("ret")),
256
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("ret"), (0, import_datalog.variable)("obj")),
257
- (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("obj"))
258
- ]
450
+ [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("obj")],
451
+ [(0, import_datalog.lit)("givesBack", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("obj")), (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("obj"))]
259
452
  ),
260
453
  // Which calls a function, found by the name the call is written as
261
454
  // rather than by resolving every callee in the project. A caller knows
@@ -276,40 +469,16 @@ var RESOLUTION_RULES = [
276
469
  (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c"))
277
470
  ]
278
471
  ),
279
- // A function whose return is itself a call arrives at the same object
280
- // that call does, so a chain passing through two factories settles.
281
- (0, import_datalog.rule)(
282
- "objectOf",
283
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("obj")],
284
- [
285
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
286
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
287
- (0, import_datalog.lit)("returnsValue", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("ret")),
288
- (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("ret"), (0, import_datalog.variable)("obj"))
289
- ]
290
- ),
291
- // An argument reaches the parameter it is passed to, by position or
292
- // by the name the caller wrote. A function called from several places
293
- // leaves its parameter with more than one value, and the caller
294
- // decides what to do about that.
295
- (0, import_datalog.rule)(
296
- "comesTo",
297
- [(0, import_datalog.variable)("p"), (0, import_datalog.variable)("z")],
298
- [
299
- (0, import_datalog.lit)("paramOf", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("p")),
300
- (0, import_datalog.lit)("callsFunction", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")),
301
- (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a")),
302
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("z"))
303
- ]
304
- ),
472
+ // The same, for a language whose adapter writes the import down as a
473
+ // declaration and the call's callee as the name referring to it.
305
474
  (0, import_datalog.rule)(
306
- "comesTo",
307
- [(0, import_datalog.variable)("p"), (0, import_datalog.variable)("z")],
475
+ "callsFunction",
476
+ [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")],
308
477
  [
309
- (0, import_datalog.lit)("paramNamed", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("p")),
310
- (0, import_datalog.lit)("callsFunction", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")),
311
- (0, import_datalog.lit)("callKeywordArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("a")),
312
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("z"))
478
+ (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("f")),
479
+ (0, import_datalog.lit)("imports", (0, import_datalog.variable)("d"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")),
480
+ (0, import_datalog.lit)("binds", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("d")),
481
+ (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c"))
313
482
  ]
314
483
  ),
315
484
  // What an object contains, its base class included, so a method the base
@@ -331,33 +500,8 @@ var RESOLUTION_RULES = [
331
500
  (0, import_datalog.lit)("contains", (0, import_datalog.variable)("baseCls"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("held"))
332
501
  ]
333
502
  ),
334
- // Calling a class makes one of it, and reading a method off the
335
- // result finds the method the class declares. The caveat above is
336
- // about a factory function, and a class is not one.
337
- (0, import_datalog.rule)(
338
- "comesTo",
339
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("cls")],
340
- [
341
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
342
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("cls")),
343
- (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("cls"))
344
- ]
345
- ),
346
- // Reading a property comes to what the object holds under that name,
347
- // whichever way the object arrived: `routes.list` off a name, or
348
- // `make(body).handle` off a call.
349
- (0, import_datalog.rule)(
350
- "comesTo",
351
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
352
- [
353
- (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.variable)("n")),
354
- (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")),
355
- (0, import_datalog.lit)("contains", (0, import_datalog.variable)("obj"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("held")),
356
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("held"), (0, import_datalog.variable)("z"))
357
- ]
358
- ),
359
- // Wrapper transparency, derived: calling a factory that returns a
360
- // function which calls its parameter k comes to argument k.
503
+ // Which function a factory returns, and which of its parameters that
504
+ // function calls: together they are what makes it a wrapper.
361
505
  (0, import_datalog.rule)(
362
506
  "returnsFunc",
363
507
  [(0, import_datalog.variable)("f"), (0, import_datalog.variable)("g")],
@@ -418,56 +562,20 @@ var RESOLUTION_RULES = [
418
562
  (0, import_datalog.lit)("paramOf", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("p"))
419
563
  ]
420
564
  ),
421
- (0, import_datalog.rule)(
422
- "comesTo",
423
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("h")],
424
- [
425
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
426
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
427
- (0, import_datalog.lit)("unwraps", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k")),
428
- (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a")),
429
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("h"))
430
- ]
431
- ),
432
- // Wrapper transparency, declared: a pack says this callee wraps
433
- // argument k, whatever its implementation looks like. The callee has
434
- // to have been imported from the library the pack named, so a local
435
- // object spelled the same way is not mistaken for it.
436
- (0, import_datalog.rule)(
437
- "comesTo",
438
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("h")],
439
- [
440
- (0, import_datalog.lit)("calleeName", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("n")),
441
- (0, import_datalog.lit)("unwrapsByName", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("k")),
442
- (0, import_datalog.lit)("wrapperModule", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("m")),
443
- (0, import_datalog.lit)("calleeOrigin", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("m")),
444
- (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a")),
445
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("h"))
446
- ]
447
- ),
448
565
  // Where a name comes from, when what it refers to lives outside the
449
- // source being read. `comesTo` ends at something written out, so it
450
- // never reaches a library's own function, and a project's alias of a
451
- // library name has no answer at all. This follows the same aliases
452
- // and barrels in the other direction and answers with the module and
453
- // the name that module exports.
566
+ // source being read. A walk ends at something written out in source,
567
+ // so it never reaches a library's own function.
454
568
  (0, import_datalog.rule)(
455
569
  "comesFrom",
456
570
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
457
571
  [(0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))]
458
572
  ),
459
- (0, import_datalog.rule)(
460
- "comesFrom",
461
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
462
- [(0, import_datalog.lit)("binds", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("comesFrom", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))]
463
- ),
464
573
  (0, import_datalog.rule)(
465
574
  "comesFrom",
466
575
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
467
576
  [
468
- (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n2")),
469
- (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n2"), (0, import_datalog.variable)("value")),
470
- (0, import_datalog.lit)("comesFrom", (0, import_datalog.variable)("value"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))
577
+ (0, import_datalog.lit)("reaches", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y"), VALUE_STEP),
578
+ (0, import_datalog.lit)("imports", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))
471
579
  ]
472
580
  ),
473
581
  // Calling f ends up calling the name n that module m exports. One hop
@@ -503,36 +611,6 @@ var RESOLUTION_RULES = [
503
611
  "resolves",
504
612
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
505
613
  [(0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")), (0, import_datalog.lit)("func", (0, import_datalog.variable)("z"))]
506
- ),
507
- // Following a name to the expression it is written as. A separate
508
- // relation rather than a widening of `comesTo`, so a caller asking
509
- // the older question never sees a new answer: a factory call already
510
- // has an unwrapping answer, and letting it also answer with itself
511
- // would make that pair ambiguous.
512
- //
513
- // The two seeds are the two ways an expression can be written out. An
514
- // object literal is one of them, and it is the shape a generated
515
- // GraphQL document takes.
516
- (0, import_datalog.rule)("isWrittenAs", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("writtenValue", (0, import_datalog.variable)("x"))]),
517
- (0, import_datalog.rule)("isWrittenAs", [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("x")], [(0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("x"))]),
518
- (0, import_datalog.rule)(
519
- "isWrittenAs",
520
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
521
- [(0, import_datalog.lit)("binds", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
522
- ),
523
- (0, import_datalog.rule)(
524
- "isWrittenAs",
525
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
526
- [(0, import_datalog.lit)("endsHolding", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("y")), (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("y"), (0, import_datalog.variable)("z"))]
527
- ),
528
- (0, import_datalog.rule)(
529
- "isWrittenAs",
530
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
531
- [
532
- (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")),
533
- (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value")),
534
- (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("value"), (0, import_datalog.variable)("z"))
535
- ]
536
614
  )
537
615
  ];
538
616
  var RESOLUTION_QUESTIONS = [
@@ -564,6 +642,13 @@ var RESOLUTION_QUESTIONS = [
564
642
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
565
643
  [(0, import_datalog.lit)("wanted", (0, import_datalog.variable)("x")), (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("z"))]
566
644
  ),
645
+ // Keyed by the parameter, since that is what a caller has in hand
646
+ // when it wants the call sites told apart.
647
+ (0, import_datalog.rule)(
648
+ "wantedParamAt",
649
+ [(0, import_datalog.variable)("p"), (0, import_datalog.variable)("r"), (0, import_datalog.variable)("z")],
650
+ [(0, import_datalog.lit)("wanted", (0, import_datalog.variable)("p")), (0, import_datalog.lit)("paramAt", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("p"), (0, import_datalog.variable)("z"))]
651
+ ),
567
652
  (0, import_datalog.rule)(
568
653
  "wantedComesFrom",
569
654
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
@@ -588,5 +673,7 @@ var ANSWER_RELATIONS = [
588
673
  FACT_CONTRACT_CASES,
589
674
  RESOLUTION_QUESTIONS,
590
675
  RESOLUTION_RULES,
676
+ RESULT_STEP,
677
+ VALUE_STEP,
591
678
  checkFactContract
592
679
  });
package/dist/index.d.cts CHANGED
@@ -36,9 +36,23 @@ declare function checkFactContract(sources: Readonly<Record<string, CaseFiles>>,
36
36
  problem: string;
37
37
  }[]>;
38
38
 
39
+ /** A step to the value x is written as. */
40
+ declare const VALUE_STEP: _suss_datalog.Term;
41
+ /** A step to what running the call x is handed back. */
42
+ declare const RESULT_STEP: _suss_datalog.Term;
39
43
  /**
40
44
  * The rules every language adapter shares. Concatenate a language's own
41
45
  * rules onto these before evaluating.
46
+ *
47
+ * Every construct states its hops once, as `stepsTo(x, y, kind)`, which
48
+ * says x leads to y. A value step goes to the value x is written as, and a
49
+ * result step runs the call x is and goes to what that call handed back.
50
+ * `reaches` is the closure of those steps, and a walk counts as a result
51
+ * walk once it has run a call anywhere along it.
52
+ *
53
+ * Each question is that one closure with its own stopping condition, so
54
+ * adding a construct is one step and every question gets it, and adding
55
+ * a question is a stopping condition and no steps at all.
42
56
  */
43
57
  declare const RESOLUTION_RULES: _suss_datalog.Rule[];
44
58
  /**
@@ -62,4 +76,4 @@ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
62
76
  /** The relations `RESOLUTION_QUESTIONS` answers into. */
63
77
  declare const ANSWER_RELATIONS: string[];
64
78
 
65
- export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, checkFactContract };
79
+ export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, VALUE_STEP, checkFactContract };
package/dist/index.d.ts CHANGED
@@ -36,9 +36,23 @@ declare function checkFactContract(sources: Readonly<Record<string, CaseFiles>>,
36
36
  problem: string;
37
37
  }[]>;
38
38
 
39
+ /** A step to the value x is written as. */
40
+ declare const VALUE_STEP: _suss_datalog.Term;
41
+ /** A step to what running the call x is handed back. */
42
+ declare const RESULT_STEP: _suss_datalog.Term;
39
43
  /**
40
44
  * The rules every language adapter shares. Concatenate a language's own
41
45
  * rules onto these before evaluating.
46
+ *
47
+ * Every construct states its hops once, as `stepsTo(x, y, kind)`, which
48
+ * says x leads to y. A value step goes to the value x is written as, and a
49
+ * result step runs the call x is and goes to what that call handed back.
50
+ * `reaches` is the closure of those steps, and a walk counts as a result
51
+ * walk once it has run a call anywhere along it.
52
+ *
53
+ * Each question is that one closure with its own stopping condition, so
54
+ * adding a construct is one step and every question gets it, and adding
55
+ * a question is a stopping condition and no steps at all.
42
56
  */
43
57
  declare const RESOLUTION_RULES: _suss_datalog.Rule[];
44
58
  /**
@@ -62,4 +76,4 @@ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
62
76
  /** The relations `RESOLUTION_QUESTIONS` answers into. */
63
77
  declare const ANSWER_RELATIONS: string[];
64
78
 
65
- export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, checkFactContract };
79
+ export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, VALUE_STEP, checkFactContract };
package/dist/index.js CHANGED
@@ -93,6 +93,26 @@ var FACT_CONTRACT_CASES = [
93
93
  return contained.some((row) => functions.has(row[2])) ? null : "what a class contains under a method name is not the node that declares the method";
94
94
  }
95
95
  },
96
+ {
97
+ name: "a class constructed with an argument",
98
+ requires: "a module-level class whose constructor takes one parameter, and a site that makes one of it, passing a name",
99
+ // However a language writes construction, it is a call of the class, and
100
+ // the constructor's parameters are the class's. Keying them on the
101
+ // constructor instead leaves the argument with nowhere to land.
102
+ check: (facts) => {
103
+ const classes = new Set(facts("objectValue").map((row) => row[0] ?? ""));
104
+ const parameters = facts("paramOf").filter(
105
+ (row) => classes.has(row[0] ?? "")
106
+ );
107
+ if (parameters.length === 0) {
108
+ return "a class lists none of its constructor's parameters, so an argument a construction site passes reaches nothing";
109
+ }
110
+ if (facts("call").length === 0) {
111
+ return "making one of a class is not written down as a call, so nothing says which class a construction arrives at";
112
+ }
113
+ return facts("callArg").length > 0 ? null : "a construction passes its argument nowhere, so the parameter it lands in has no value";
114
+ }
115
+ },
96
116
  {
97
117
  name: "a value another file declares",
98
118
  requires: "two files, one declaring a value and the other reading it by name",
@@ -151,36 +171,203 @@ function checkFactContract(sources, emit, options = {}) {
151
171
  }
152
172
 
153
173
  // src/index.ts
154
- import { lit, rule, variable as v } from "@suss/datalog";
174
+ import { constant, lit, rule, variable as v } from "@suss/datalog";
175
+ var VALUE_STEP = constant("value");
176
+ var RESULT_STEP = constant("result");
155
177
  var RESOLUTION_RULES = [
156
- // A value comes to itself; every chain ends at something written out
157
- // in source, a function or an object.
178
+ // Aliasing: const x = y, or an identifier referencing a declaration.
179
+ // A language with a hop of its own, like JavaScript's `.bind`, states
180
+ // it as a step too, or every question but `comesTo` misses it.
181
+ rule("stepsTo", [v("x"), v("y"), VALUE_STEP], [lit("binds", v("x"), v("y"))]),
182
+ // A name written more than once has the value the last write left
183
+ // there. The adapter works out which write that is and stays quiet
184
+ // when control flow decides, so such a name steps nowhere at all.
185
+ rule(
186
+ "stepsTo",
187
+ [v("x"), v("y"), VALUE_STEP],
188
+ [lit("endsHolding", v("x"), v("y"))]
189
+ ),
190
+ // An import steps to what the module exports under that name.
191
+ rule(
192
+ "stepsTo",
193
+ [v("x"), v("value"), VALUE_STEP],
194
+ [
195
+ lit("imports", v("x"), v("m"), v("n")),
196
+ lit("moduleExport", v("m"), v("n"), v("value"))
197
+ ]
198
+ ),
199
+ // A parameter steps to what a call passes it. A function called from
200
+ // several places leaves its parameter with more than one value, and a
201
+ // caller that needs those apart asks `paramAt`.
202
+ rule(
203
+ "stepsTo",
204
+ [v("p"), v("a"), VALUE_STEP],
205
+ [lit("passesArgument", v("r"), v("p"), v("a"))]
206
+ ),
207
+ // Reading a property steps to what the object contains under that
208
+ // name, whichever way the object arrived: `routes.list` off a name,
209
+ // or `make(body).handle` off a call.
210
+ rule(
211
+ "stepsTo",
212
+ [v("x"), v("held"), VALUE_STEP],
213
+ [
214
+ lit("readsProperty", v("x"), v("o"), v("n")),
215
+ lit("objectOf", v("o"), v("obj")),
216
+ lit("contains", v("obj"), v("n"), v("held"))
217
+ ]
218
+ ),
219
+ // Calling a class makes one of it, so the call steps to the class and
220
+ // a method read off the result is the one the class declares. The
221
+ // caveat below is about a factory function, and a class is not one.
222
+ rule(
223
+ "stepsTo",
224
+ [v("r"), v("cls"), VALUE_STEP],
225
+ [
226
+ lit("call", v("r"), v("c")),
227
+ lit("comesTo", v("c"), v("cls")),
228
+ lit("objectValue", v("cls"))
229
+ ]
230
+ ),
231
+ // Wrapper transparency, derived: calling a factory that returns a
232
+ // function which calls its parameter k steps to argument k.
233
+ rule(
234
+ "stepsTo",
235
+ [v("r"), v("a"), VALUE_STEP],
236
+ [
237
+ lit("call", v("r"), v("c")),
238
+ lit("comesTo", v("c"), v("f")),
239
+ lit("unwraps", v("f"), v("k")),
240
+ lit("callArg", v("r"), v("k"), v("a"))
241
+ ]
242
+ ),
243
+ // Wrapper transparency, declared: a pack says this callee wraps
244
+ // argument k. The callee has to come from the library the pack said,
245
+ // so a local object spelled the same way is not mistaken for it.
246
+ rule(
247
+ "stepsTo",
248
+ [v("r"), v("a"), VALUE_STEP],
249
+ [
250
+ lit("calleeName", v("r"), v("n")),
251
+ lit("unwrapsByName", v("n"), v("k")),
252
+ lit("wrapperModule", v("n"), v("m")),
253
+ lit("calleeOrigin", v("r"), v("m")),
254
+ lit("callArg", v("r"), v("k"), v("a"))
255
+ ]
256
+ ),
257
+ // The one step that runs a function forwards: a call steps to what
258
+ // the function it invokes returns.
259
+ rule(
260
+ "stepsTo",
261
+ [v("r"), v("ret"), RESULT_STEP],
262
+ [lit("invokes", v("r"), v("f")), lit("returnsValue", v("f"), v("ret"))]
263
+ ),
264
+ // Where a walk gets to, and whether it ran a call on the way. A value
265
+ // step keeps whatever the rest of the walk was, and a result step
266
+ // makes the whole walk one that ran a call.
267
+ rule(
268
+ "reaches",
269
+ [v("x"), v("z"), v("kind")],
270
+ [lit("stepsTo", v("x"), v("z"), v("kind"))]
271
+ ),
272
+ rule(
273
+ "reaches",
274
+ [v("x"), v("z"), v("kind")],
275
+ [
276
+ lit("stepsTo", v("x"), v("y"), VALUE_STEP),
277
+ lit("reaches", v("y"), v("z"), v("kind"))
278
+ ]
279
+ ),
280
+ // Two rules rather than one leaving the rest of the walk's kind free.
281
+ // A free kind is a second question about the same relation, and a
282
+ // demand-driven run then derives both to answer either.
283
+ rule(
284
+ "reaches",
285
+ [v("x"), v("z"), RESULT_STEP],
286
+ [
287
+ lit("stepsTo", v("x"), v("y"), RESULT_STEP),
288
+ lit("reaches", v("y"), v("z"), VALUE_STEP)
289
+ ]
290
+ ),
291
+ rule(
292
+ "reaches",
293
+ [v("x"), v("z"), RESULT_STEP],
294
+ [
295
+ lit("stepsTo", v("x"), v("y"), RESULT_STEP),
296
+ lit("reaches", v("y"), v("z"), RESULT_STEP)
297
+ ]
298
+ ),
299
+ // What a value comes down to: the walk that never ran a call, stopped
300
+ // at a function or an object. Something already written out comes to
301
+ // itself, and every other walk ends at one of those.
158
302
  rule("comesTo", [v("x"), v("x")], [lit("func", v("x"))]),
159
303
  rule("comesTo", [v("x"), v("x")], [lit("objectValue", v("x"))]),
160
- // Aliasing: const x = y, or an identifier referencing a declaration.
161
304
  rule(
162
305
  "comesTo",
163
306
  [v("x"), v("z")],
164
- [lit("binds", v("x"), v("y")), lit("comesTo", v("y"), v("z"))]
307
+ [lit("reaches", v("x"), v("z"), VALUE_STEP), lit("func", v("z"))]
165
308
  ),
166
- // A name written more than once has the value the last write left
167
- // there, and `binds` says nothing about it. The adapter works out
168
- // which write that is, in its own language's terms, and stays quiet
169
- // when control flow decides. A name it stays quiet about comes to no
170
- // value, which is the answer.
171
309
  rule(
172
310
  "comesTo",
173
311
  [v("x"), v("z")],
174
- [lit("endsHolding", v("x"), v("y")), lit("comesTo", v("y"), v("z"))]
312
+ [lit("reaches", v("x"), v("z"), VALUE_STEP), lit("objectValue", v("z"))]
175
313
  ),
176
- // An import comes to what the module exports under that name.
314
+ // The same stopping condition, for the walk that ran a call. A call
315
+ // gets no `comesTo` answer on purpose: coming back with what a
316
+ // factory returned would fight the unwrapping answer.
177
317
  rule(
178
- "comesTo",
318
+ "givesBack",
319
+ [v("x"), v("z")],
320
+ [lit("reaches", v("x"), v("z"), RESULT_STEP), lit("func", v("z"))]
321
+ ),
322
+ rule(
323
+ "givesBack",
324
+ [v("x"), v("z")],
325
+ [lit("reaches", v("x"), v("z"), RESULT_STEP), lit("objectValue", v("z"))]
326
+ ),
327
+ // Following a value to the expression it is written as, whatever kind
328
+ // that is. A GraphQL document is neither a function nor an object, so
329
+ // `comesTo` never reaches one.
330
+ rule("isWrittenAs", [v("x"), v("x")], [lit("writtenValue", v("x"))]),
331
+ rule("isWrittenAs", [v("x"), v("x")], [lit("objectValue", v("x"))]),
332
+ rule(
333
+ "isWrittenAs",
334
+ [v("x"), v("z")],
335
+ [lit("reaches", v("x"), v("z"), VALUE_STEP), lit("writtenValue", v("z"))]
336
+ ),
337
+ rule(
338
+ "isWrittenAs",
179
339
  [v("x"), v("z")],
340
+ [lit("reaches", v("x"), v("z"), VALUE_STEP), lit("objectValue", v("z"))]
341
+ ),
342
+ // What one call site put in a parameter, told apart from what the
343
+ // other callers passed.
344
+ rule(
345
+ "paramAt",
346
+ [v("r"), v("p"), v("z")],
180
347
  [
181
- lit("imports", v("x"), v("m"), v("n")),
182
- lit("moduleExport", v("m"), v("n"), v("value")),
183
- lit("comesTo", v("value"), v("z"))
348
+ lit("passesArgument", v("r"), v("p"), v("a")),
349
+ lit("comesTo", v("a"), v("z"))
350
+ ]
351
+ ),
352
+ // An argument arriving at the parameter it is passed to, by position
353
+ // or by the name the caller wrote, keeping the call it went through
354
+ // so `paramAt` can tell two call sites apart.
355
+ rule(
356
+ "passesArgument",
357
+ [v("r"), v("p"), v("a")],
358
+ [
359
+ lit("paramOf", v("f"), v("k"), v("p")),
360
+ lit("callsFunction", v("r"), v("f")),
361
+ lit("callArg", v("r"), v("k"), v("a"))
362
+ ]
363
+ ),
364
+ rule(
365
+ "passesArgument",
366
+ [v("r"), v("p"), v("a")],
367
+ [
368
+ lit("paramNamed", v("f"), v("n"), v("p")),
369
+ lit("callsFunction", v("r"), v("f")),
370
+ lit("callKeywordArg", v("r"), v("n"), v("a"))
184
371
  ]
185
372
  ),
186
373
  // What a module exports: directly, or through re-export chains.
@@ -205,12 +392,22 @@ var RESOLUTION_RULES = [
205
392
  lit("moduleExport", v("m2"), v("n"), v("value"))
206
393
  ]
207
394
  ),
208
- // The object an expression refers to. A name arrives at one by
209
- // following `comesTo` through aliases and imports. A factory call
210
- // arrives at one through what the function it calls returns; the
211
- // call itself is given no `comesTo`, since a factory call usually IS
212
- // the wrapper and answering with the raw returned function would
213
- // fight the unwraps answer.
395
+ // The function a call runs, written from the call's side because a
396
+ // caller asking what a call gives back has the call in hand.
397
+ rule(
398
+ "invokes",
399
+ [v("r"), v("f")],
400
+ [lit("call", v("r"), v("c")), lit("comesTo", v("c"), v("f"))]
401
+ ),
402
+ // The callee is itself a call: `daoBuilder()()`.
403
+ rule(
404
+ "invokes",
405
+ [v("r"), v("f")],
406
+ [lit("call", v("r"), v("c")), lit("givesBack", v("c"), v("f"))]
407
+ ),
408
+ // The object an expression refers to: a name through `comesTo`, a
409
+ // factory call through what it gives back. Naming the step is what
410
+ // makes `routes.list` and `make(body).handle` one rule.
214
411
  rule(
215
412
  "objectOf",
216
413
  [v("o"), v("obj")],
@@ -218,14 +415,8 @@ var RESOLUTION_RULES = [
218
415
  ),
219
416
  rule(
220
417
  "objectOf",
221
- [v("r"), v("obj")],
222
- [
223
- lit("call", v("r"), v("c")),
224
- lit("comesTo", v("c"), v("f")),
225
- lit("returnsValue", v("f"), v("ret")),
226
- lit("comesTo", v("ret"), v("obj")),
227
- lit("objectValue", v("obj"))
228
- ]
418
+ [v("x"), v("obj")],
419
+ [lit("givesBack", v("x"), v("obj")), lit("objectValue", v("obj"))]
229
420
  ),
230
421
  // Which calls a function, found by the name the call is written as
231
422
  // rather than by resolving every callee in the project. A caller knows
@@ -246,40 +437,16 @@ var RESOLUTION_RULES = [
246
437
  lit("call", v("r"), v("c"))
247
438
  ]
248
439
  ),
249
- // A function whose return is itself a call arrives at the same object
250
- // that call does, so a chain passing through two factories settles.
251
- rule(
252
- "objectOf",
253
- [v("r"), v("obj")],
254
- [
255
- lit("call", v("r"), v("c")),
256
- lit("comesTo", v("c"), v("f")),
257
- lit("returnsValue", v("f"), v("ret")),
258
- lit("objectOf", v("ret"), v("obj"))
259
- ]
260
- ),
261
- // An argument reaches the parameter it is passed to, by position or
262
- // by the name the caller wrote. A function called from several places
263
- // leaves its parameter with more than one value, and the caller
264
- // decides what to do about that.
265
- rule(
266
- "comesTo",
267
- [v("p"), v("z")],
268
- [
269
- lit("paramOf", v("f"), v("k"), v("p")),
270
- lit("callsFunction", v("r"), v("f")),
271
- lit("callArg", v("r"), v("k"), v("a")),
272
- lit("comesTo", v("a"), v("z"))
273
- ]
274
- ),
440
+ // The same, for a language whose adapter writes the import down as a
441
+ // declaration and the call's callee as the name referring to it.
275
442
  rule(
276
- "comesTo",
277
- [v("p"), v("z")],
443
+ "callsFunction",
444
+ [v("r"), v("f")],
278
445
  [
279
- lit("paramNamed", v("f"), v("n"), v("p")),
280
- lit("callsFunction", v("r"), v("f")),
281
- lit("callKeywordArg", v("r"), v("n"), v("a")),
282
- lit("comesTo", v("a"), v("z"))
446
+ lit("moduleExport", v("m"), v("n"), v("f")),
447
+ lit("imports", v("d"), v("m"), v("n")),
448
+ lit("binds", v("c"), v("d")),
449
+ lit("call", v("r"), v("c"))
283
450
  ]
284
451
  ),
285
452
  // What an object contains, its base class included, so a method the base
@@ -301,33 +468,8 @@ var RESOLUTION_RULES = [
301
468
  lit("contains", v("baseCls"), v("n"), v("held"))
302
469
  ]
303
470
  ),
304
- // Calling a class makes one of it, and reading a method off the
305
- // result finds the method the class declares. The caveat above is
306
- // about a factory function, and a class is not one.
307
- rule(
308
- "comesTo",
309
- [v("r"), v("cls")],
310
- [
311
- lit("call", v("r"), v("c")),
312
- lit("comesTo", v("c"), v("cls")),
313
- lit("objectValue", v("cls"))
314
- ]
315
- ),
316
- // Reading a property comes to what the object holds under that name,
317
- // whichever way the object arrived: `routes.list` off a name, or
318
- // `make(body).handle` off a call.
319
- rule(
320
- "comesTo",
321
- [v("x"), v("z")],
322
- [
323
- lit("readsProperty", v("x"), v("o"), v("n")),
324
- lit("objectOf", v("o"), v("obj")),
325
- lit("contains", v("obj"), v("n"), v("held")),
326
- lit("comesTo", v("held"), v("z"))
327
- ]
328
- ),
329
- // Wrapper transparency, derived: calling a factory that returns a
330
- // function which calls its parameter k comes to argument k.
471
+ // Which function a factory returns, and which of its parameters that
472
+ // function calls: together they are what makes it a wrapper.
331
473
  rule(
332
474
  "returnsFunc",
333
475
  [v("f"), v("g")],
@@ -388,56 +530,20 @@ var RESOLUTION_RULES = [
388
530
  lit("paramOf", v("f"), v("k"), v("p"))
389
531
  ]
390
532
  ),
391
- rule(
392
- "comesTo",
393
- [v("r"), v("h")],
394
- [
395
- lit("call", v("r"), v("c")),
396
- lit("comesTo", v("c"), v("f")),
397
- lit("unwraps", v("f"), v("k")),
398
- lit("callArg", v("r"), v("k"), v("a")),
399
- lit("comesTo", v("a"), v("h"))
400
- ]
401
- ),
402
- // Wrapper transparency, declared: a pack says this callee wraps
403
- // argument k, whatever its implementation looks like. The callee has
404
- // to have been imported from the library the pack named, so a local
405
- // object spelled the same way is not mistaken for it.
406
- rule(
407
- "comesTo",
408
- [v("r"), v("h")],
409
- [
410
- lit("calleeName", v("r"), v("n")),
411
- lit("unwrapsByName", v("n"), v("k")),
412
- lit("wrapperModule", v("n"), v("m")),
413
- lit("calleeOrigin", v("r"), v("m")),
414
- lit("callArg", v("r"), v("k"), v("a")),
415
- lit("comesTo", v("a"), v("h"))
416
- ]
417
- ),
418
533
  // Where a name comes from, when what it refers to lives outside the
419
- // source being read. `comesTo` ends at something written out, so it
420
- // never reaches a library's own function, and a project's alias of a
421
- // library name has no answer at all. This follows the same aliases
422
- // and barrels in the other direction and answers with the module and
423
- // the name that module exports.
534
+ // source being read. A walk ends at something written out in source,
535
+ // so it never reaches a library's own function.
424
536
  rule(
425
537
  "comesFrom",
426
538
  [v("x"), v("m"), v("n")],
427
539
  [lit("imports", v("x"), v("m"), v("n"))]
428
540
  ),
429
- rule(
430
- "comesFrom",
431
- [v("x"), v("m"), v("n")],
432
- [lit("binds", v("x"), v("y")), lit("comesFrom", v("y"), v("m"), v("n"))]
433
- ),
434
541
  rule(
435
542
  "comesFrom",
436
543
  [v("x"), v("m"), v("n")],
437
544
  [
438
- lit("imports", v("x"), v("m2"), v("n2")),
439
- lit("moduleExport", v("m2"), v("n2"), v("value")),
440
- lit("comesFrom", v("value"), v("m"), v("n"))
545
+ lit("reaches", v("x"), v("y"), VALUE_STEP),
546
+ lit("imports", v("y"), v("m"), v("n"))
441
547
  ]
442
548
  ),
443
549
  // Calling f ends up calling the name n that module m exports. One hop
@@ -473,36 +579,6 @@ var RESOLUTION_RULES = [
473
579
  "resolves",
474
580
  [v("x"), v("z")],
475
581
  [lit("comesTo", v("x"), v("z")), lit("func", v("z"))]
476
- ),
477
- // Following a name to the expression it is written as. A separate
478
- // relation rather than a widening of `comesTo`, so a caller asking
479
- // the older question never sees a new answer: a factory call already
480
- // has an unwrapping answer, and letting it also answer with itself
481
- // would make that pair ambiguous.
482
- //
483
- // The two seeds are the two ways an expression can be written out. An
484
- // object literal is one of them, and it is the shape a generated
485
- // GraphQL document takes.
486
- rule("isWrittenAs", [v("x"), v("x")], [lit("writtenValue", v("x"))]),
487
- rule("isWrittenAs", [v("x"), v("x")], [lit("objectValue", v("x"))]),
488
- rule(
489
- "isWrittenAs",
490
- [v("x"), v("z")],
491
- [lit("binds", v("x"), v("y")), lit("isWrittenAs", v("y"), v("z"))]
492
- ),
493
- rule(
494
- "isWrittenAs",
495
- [v("x"), v("z")],
496
- [lit("endsHolding", v("x"), v("y")), lit("isWrittenAs", v("y"), v("z"))]
497
- ),
498
- rule(
499
- "isWrittenAs",
500
- [v("x"), v("z")],
501
- [
502
- lit("imports", v("x"), v("m"), v("n")),
503
- lit("moduleExport", v("m"), v("n"), v("value")),
504
- lit("isWrittenAs", v("value"), v("z"))
505
- ]
506
582
  )
507
583
  ];
508
584
  var RESOLUTION_QUESTIONS = [
@@ -534,6 +610,13 @@ var RESOLUTION_QUESTIONS = [
534
610
  [v("x"), v("z")],
535
611
  [lit("wanted", v("x")), lit("objectOf", v("x"), v("z"))]
536
612
  ),
613
+ // Keyed by the parameter, since that is what a caller has in hand
614
+ // when it wants the call sites told apart.
615
+ rule(
616
+ "wantedParamAt",
617
+ [v("p"), v("r"), v("z")],
618
+ [lit("wanted", v("p")), lit("paramAt", v("r"), v("p"), v("z"))]
619
+ ),
537
620
  rule(
538
621
  "wantedComesFrom",
539
622
  [v("x"), v("m"), v("n")],
@@ -557,5 +640,7 @@ export {
557
640
  FACT_CONTRACT_CASES,
558
641
  RESOLUTION_QUESTIONS,
559
642
  RESOLUTION_RULES,
643
+ RESULT_STEP,
644
+ VALUE_STEP,
560
645
  checkFactContract
561
646
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/resolution",
3
- "version": "0.7.2",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "Language-neutral rules for following a value to the function it comes down to.",
6
6
  "keywords": [
@@ -53,6 +53,6 @@
53
53
  "access": "public"
54
54
  },
55
55
  "dependencies": {
56
- "@suss/datalog": "0.7.2"
56
+ "@suss/datalog": "0.9.0"
57
57
  }
58
58
  }