@suss/resolution 0.8.0 → 0.10.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);
@@ -202,35 +204,202 @@ function checkFactContract(sources, emit, options = {}) {
202
204
 
203
205
  // src/index.ts
204
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");
205
209
  var RESOLUTION_RULES = [
206
- // A value comes to itself; every chain ends at something written out
207
- // 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.
208
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"))]),
209
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"))]),
210
- // Aliasing: const x = y, or an identifier referencing a declaration.
211
336
  (0, import_datalog.rule)(
212
337
  "comesTo",
213
338
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
214
- [(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"))]
215
340
  ),
216
- // A name written more than once has the value the last write left
217
- // there, and `binds` says nothing about it. The adapter works out
218
- // which write that is, in its own language's terms, and stays quiet
219
- // when control flow decides. A name it stays quiet about comes to no
220
- // value, which is the answer.
221
341
  (0, import_datalog.rule)(
222
342
  "comesTo",
223
343
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
224
- [(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"))]
225
345
  ),
226
- // 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.
227
349
  (0, import_datalog.rule)(
228
- "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",
229
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",
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")],
230
379
  [
231
- (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")),
232
- (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value")),
233
- (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"))
234
403
  ]
235
404
  ),
236
405
  // What a module exports: directly, or through re-export chains.
@@ -255,12 +424,22 @@ var RESOLUTION_RULES = [
255
424
  (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value"))
256
425
  ]
257
426
  ),
258
- // The object an expression refers to. A name arrives at one by
259
- // following `comesTo` through aliases and imports. A factory call
260
- // arrives at one through what the function it calls returns; the
261
- // call itself is given no `comesTo`, since a factory call usually IS
262
- // the wrapper and answering with the raw returned function would
263
- // 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.
264
443
  (0, import_datalog.rule)(
265
444
  "objectOf",
266
445
  [(0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")],
@@ -268,14 +447,8 @@ var RESOLUTION_RULES = [
268
447
  ),
269
448
  (0, import_datalog.rule)(
270
449
  "objectOf",
271
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("obj")],
272
- [
273
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
274
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
275
- (0, import_datalog.lit)("returnsValue", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("ret")),
276
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("ret"), (0, import_datalog.variable)("obj")),
277
- (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("obj"))
278
- ]
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"))]
279
452
  ),
280
453
  // Which calls a function, found by the name the call is written as
281
454
  // rather than by resolving every callee in the project. A caller knows
@@ -308,42 +481,6 @@ var RESOLUTION_RULES = [
308
481
  (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c"))
309
482
  ]
310
483
  ),
311
- // A function whose return is itself a call arrives at the same object
312
- // that call does, so a chain passing through two factories settles.
313
- (0, import_datalog.rule)(
314
- "objectOf",
315
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("obj")],
316
- [
317
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
318
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
319
- (0, import_datalog.lit)("returnsValue", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("ret")),
320
- (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("ret"), (0, import_datalog.variable)("obj"))
321
- ]
322
- ),
323
- // An argument reaches the parameter it is passed to, by position or
324
- // by the name the caller wrote. A function called from several places
325
- // leaves its parameter with more than one value, and the caller
326
- // decides what to do about that.
327
- (0, import_datalog.rule)(
328
- "comesTo",
329
- [(0, import_datalog.variable)("p"), (0, import_datalog.variable)("z")],
330
- [
331
- (0, import_datalog.lit)("paramOf", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("p")),
332
- (0, import_datalog.lit)("callsFunction", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")),
333
- (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a")),
334
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("z"))
335
- ]
336
- ),
337
- (0, import_datalog.rule)(
338
- "comesTo",
339
- [(0, import_datalog.variable)("p"), (0, import_datalog.variable)("z")],
340
- [
341
- (0, import_datalog.lit)("paramNamed", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("p")),
342
- (0, import_datalog.lit)("callsFunction", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("f")),
343
- (0, import_datalog.lit)("callKeywordArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("a")),
344
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("z"))
345
- ]
346
- ),
347
484
  // What an object contains, its base class included, so a method the base
348
485
  // declares is found on a subclass that never overrode it. A method both
349
486
  // declare gives two, and the caller decides. This is its own relation
@@ -363,33 +500,8 @@ var RESOLUTION_RULES = [
363
500
  (0, import_datalog.lit)("contains", (0, import_datalog.variable)("baseCls"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("held"))
364
501
  ]
365
502
  ),
366
- // Calling a class makes one of it, and reading a method off the
367
- // result finds the method the class declares. The caveat above is
368
- // about a factory function, and a class is not one.
369
- (0, import_datalog.rule)(
370
- "comesTo",
371
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("cls")],
372
- [
373
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
374
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("cls")),
375
- (0, import_datalog.lit)("objectValue", (0, import_datalog.variable)("cls"))
376
- ]
377
- ),
378
- // Reading a property comes to what the object holds under that name,
379
- // whichever way the object arrived: `routes.list` off a name, or
380
- // `make(body).handle` off a call.
381
- (0, import_datalog.rule)(
382
- "comesTo",
383
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
384
- [
385
- (0, import_datalog.lit)("readsProperty", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("o"), (0, import_datalog.variable)("n")),
386
- (0, import_datalog.lit)("objectOf", (0, import_datalog.variable)("o"), (0, import_datalog.variable)("obj")),
387
- (0, import_datalog.lit)("contains", (0, import_datalog.variable)("obj"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("held")),
388
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("held"), (0, import_datalog.variable)("z"))
389
- ]
390
- ),
391
- // Wrapper transparency, derived: calling a factory that returns a
392
- // 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.
393
505
  (0, import_datalog.rule)(
394
506
  "returnsFunc",
395
507
  [(0, import_datalog.variable)("f"), (0, import_datalog.variable)("g")],
@@ -450,56 +562,20 @@ var RESOLUTION_RULES = [
450
562
  (0, import_datalog.lit)("paramOf", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("p"))
451
563
  ]
452
564
  ),
453
- (0, import_datalog.rule)(
454
- "comesTo",
455
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("h")],
456
- [
457
- (0, import_datalog.lit)("call", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("c")),
458
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("c"), (0, import_datalog.variable)("f")),
459
- (0, import_datalog.lit)("unwraps", (0, import_datalog.variable)("f"), (0, import_datalog.variable)("k")),
460
- (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a")),
461
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("h"))
462
- ]
463
- ),
464
- // Wrapper transparency, declared: a pack says this callee wraps
465
- // argument k, whatever its implementation looks like. The callee has
466
- // to have been imported from the library the pack named, so a local
467
- // object spelled the same way is not mistaken for it.
468
- (0, import_datalog.rule)(
469
- "comesTo",
470
- [(0, import_datalog.variable)("r"), (0, import_datalog.variable)("h")],
471
- [
472
- (0, import_datalog.lit)("calleeName", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("n")),
473
- (0, import_datalog.lit)("unwrapsByName", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("k")),
474
- (0, import_datalog.lit)("wrapperModule", (0, import_datalog.variable)("n"), (0, import_datalog.variable)("m")),
475
- (0, import_datalog.lit)("calleeOrigin", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("m")),
476
- (0, import_datalog.lit)("callArg", (0, import_datalog.variable)("r"), (0, import_datalog.variable)("k"), (0, import_datalog.variable)("a")),
477
- (0, import_datalog.lit)("comesTo", (0, import_datalog.variable)("a"), (0, import_datalog.variable)("h"))
478
- ]
479
- ),
480
565
  // Where a name comes from, when what it refers to lives outside the
481
- // source being read. `comesTo` ends at something written out, so it
482
- // never reaches a library's own function, and a project's alias of a
483
- // library name has no answer at all. This follows the same aliases
484
- // and barrels in the other direction and answers with the module and
485
- // 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.
486
568
  (0, import_datalog.rule)(
487
569
  "comesFrom",
488
570
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
489
571
  [(0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"))]
490
572
  ),
491
- (0, import_datalog.rule)(
492
- "comesFrom",
493
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
494
- [(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"))]
495
- ),
496
573
  (0, import_datalog.rule)(
497
574
  "comesFrom",
498
575
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
499
576
  [
500
- (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n2")),
501
- (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m2"), (0, import_datalog.variable)("n2"), (0, import_datalog.variable)("value")),
502
- (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"))
503
579
  ]
504
580
  ),
505
581
  // Calling f ends up calling the name n that module m exports. One hop
@@ -535,36 +611,6 @@ var RESOLUTION_RULES = [
535
611
  "resolves",
536
612
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
537
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"))]
538
- ),
539
- // Following a name to the expression it is written as. A separate
540
- // relation rather than a widening of `comesTo`, so a caller asking
541
- // the older question never sees a new answer: a factory call already
542
- // has an unwrapping answer, and letting it also answer with itself
543
- // would make that pair ambiguous.
544
- //
545
- // The two seeds are the two ways an expression can be written out. An
546
- // object literal is one of them, and it is the shape a generated
547
- // GraphQL document takes.
548
- (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"))]),
549
- (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"))]),
550
- (0, import_datalog.rule)(
551
- "isWrittenAs",
552
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
553
- [(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"))]
554
- ),
555
- (0, import_datalog.rule)(
556
- "isWrittenAs",
557
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
558
- [(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"))]
559
- ),
560
- (0, import_datalog.rule)(
561
- "isWrittenAs",
562
- [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
563
- [
564
- (0, import_datalog.lit)("imports", (0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")),
565
- (0, import_datalog.lit)("moduleExport", (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n"), (0, import_datalog.variable)("value")),
566
- (0, import_datalog.lit)("isWrittenAs", (0, import_datalog.variable)("value"), (0, import_datalog.variable)("z"))
567
- ]
568
614
  )
569
615
  ];
570
616
  var RESOLUTION_QUESTIONS = [
@@ -596,6 +642,13 @@ var RESOLUTION_QUESTIONS = [
596
642
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("z")],
597
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"))]
598
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
+ ),
599
652
  (0, import_datalog.rule)(
600
653
  "wantedComesFrom",
601
654
  [(0, import_datalog.variable)("x"), (0, import_datalog.variable)("m"), (0, import_datalog.variable)("n")],
@@ -620,5 +673,7 @@ var ANSWER_RELATIONS = [
620
673
  FACT_CONTRACT_CASES,
621
674
  RESOLUTION_QUESTIONS,
622
675
  RESOLUTION_RULES,
676
+ RESULT_STEP,
677
+ VALUE_STEP,
623
678
  checkFactContract
624
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
@@ -171,36 +171,203 @@ function checkFactContract(sources, emit, options = {}) {
171
171
  }
172
172
 
173
173
  // src/index.ts
174
- 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");
175
177
  var RESOLUTION_RULES = [
176
- // A value comes to itself; every chain ends at something written out
177
- // 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.
178
302
  rule("comesTo", [v("x"), v("x")], [lit("func", v("x"))]),
179
303
  rule("comesTo", [v("x"), v("x")], [lit("objectValue", v("x"))]),
180
- // Aliasing: const x = y, or an identifier referencing a declaration.
181
304
  rule(
182
305
  "comesTo",
183
306
  [v("x"), v("z")],
184
- [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"))]
185
308
  ),
186
- // A name written more than once has the value the last write left
187
- // there, and `binds` says nothing about it. The adapter works out
188
- // which write that is, in its own language's terms, and stays quiet
189
- // when control flow decides. A name it stays quiet about comes to no
190
- // value, which is the answer.
191
309
  rule(
192
310
  "comesTo",
193
311
  [v("x"), v("z")],
194
- [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"))]
195
313
  ),
196
- // 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.
197
317
  rule(
198
- "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",
199
334
  [v("x"), v("z")],
335
+ [lit("reaches", v("x"), v("z"), VALUE_STEP), lit("writtenValue", v("z"))]
336
+ ),
337
+ rule(
338
+ "isWrittenAs",
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")],
200
347
  [
201
- lit("imports", v("x"), v("m"), v("n")),
202
- lit("moduleExport", v("m"), v("n"), v("value")),
203
- 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"))
204
371
  ]
205
372
  ),
206
373
  // What a module exports: directly, or through re-export chains.
@@ -225,12 +392,22 @@ var RESOLUTION_RULES = [
225
392
  lit("moduleExport", v("m2"), v("n"), v("value"))
226
393
  ]
227
394
  ),
228
- // The object an expression refers to. A name arrives at one by
229
- // following `comesTo` through aliases and imports. A factory call
230
- // arrives at one through what the function it calls returns; the
231
- // call itself is given no `comesTo`, since a factory call usually IS
232
- // the wrapper and answering with the raw returned function would
233
- // 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.
234
411
  rule(
235
412
  "objectOf",
236
413
  [v("o"), v("obj")],
@@ -238,14 +415,8 @@ var RESOLUTION_RULES = [
238
415
  ),
239
416
  rule(
240
417
  "objectOf",
241
- [v("r"), v("obj")],
242
- [
243
- lit("call", v("r"), v("c")),
244
- lit("comesTo", v("c"), v("f")),
245
- lit("returnsValue", v("f"), v("ret")),
246
- lit("comesTo", v("ret"), v("obj")),
247
- lit("objectValue", v("obj"))
248
- ]
418
+ [v("x"), v("obj")],
419
+ [lit("givesBack", v("x"), v("obj")), lit("objectValue", v("obj"))]
249
420
  ),
250
421
  // Which calls a function, found by the name the call is written as
251
422
  // rather than by resolving every callee in the project. A caller knows
@@ -278,42 +449,6 @@ var RESOLUTION_RULES = [
278
449
  lit("call", v("r"), v("c"))
279
450
  ]
280
451
  ),
281
- // A function whose return is itself a call arrives at the same object
282
- // that call does, so a chain passing through two factories settles.
283
- rule(
284
- "objectOf",
285
- [v("r"), v("obj")],
286
- [
287
- lit("call", v("r"), v("c")),
288
- lit("comesTo", v("c"), v("f")),
289
- lit("returnsValue", v("f"), v("ret")),
290
- lit("objectOf", v("ret"), v("obj"))
291
- ]
292
- ),
293
- // An argument reaches the parameter it is passed to, by position or
294
- // by the name the caller wrote. A function called from several places
295
- // leaves its parameter with more than one value, and the caller
296
- // decides what to do about that.
297
- rule(
298
- "comesTo",
299
- [v("p"), v("z")],
300
- [
301
- lit("paramOf", v("f"), v("k"), v("p")),
302
- lit("callsFunction", v("r"), v("f")),
303
- lit("callArg", v("r"), v("k"), v("a")),
304
- lit("comesTo", v("a"), v("z"))
305
- ]
306
- ),
307
- rule(
308
- "comesTo",
309
- [v("p"), v("z")],
310
- [
311
- lit("paramNamed", v("f"), v("n"), v("p")),
312
- lit("callsFunction", v("r"), v("f")),
313
- lit("callKeywordArg", v("r"), v("n"), v("a")),
314
- lit("comesTo", v("a"), v("z"))
315
- ]
316
- ),
317
452
  // What an object contains, its base class included, so a method the base
318
453
  // declares is found on a subclass that never overrode it. A method both
319
454
  // declare gives two, and the caller decides. This is its own relation
@@ -333,33 +468,8 @@ var RESOLUTION_RULES = [
333
468
  lit("contains", v("baseCls"), v("n"), v("held"))
334
469
  ]
335
470
  ),
336
- // Calling a class makes one of it, and reading a method off the
337
- // result finds the method the class declares. The caveat above is
338
- // about a factory function, and a class is not one.
339
- rule(
340
- "comesTo",
341
- [v("r"), v("cls")],
342
- [
343
- lit("call", v("r"), v("c")),
344
- lit("comesTo", v("c"), v("cls")),
345
- lit("objectValue", v("cls"))
346
- ]
347
- ),
348
- // Reading a property comes to what the object holds under that name,
349
- // whichever way the object arrived: `routes.list` off a name, or
350
- // `make(body).handle` off a call.
351
- rule(
352
- "comesTo",
353
- [v("x"), v("z")],
354
- [
355
- lit("readsProperty", v("x"), v("o"), v("n")),
356
- lit("objectOf", v("o"), v("obj")),
357
- lit("contains", v("obj"), v("n"), v("held")),
358
- lit("comesTo", v("held"), v("z"))
359
- ]
360
- ),
361
- // Wrapper transparency, derived: calling a factory that returns a
362
- // 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.
363
473
  rule(
364
474
  "returnsFunc",
365
475
  [v("f"), v("g")],
@@ -420,56 +530,20 @@ var RESOLUTION_RULES = [
420
530
  lit("paramOf", v("f"), v("k"), v("p"))
421
531
  ]
422
532
  ),
423
- rule(
424
- "comesTo",
425
- [v("r"), v("h")],
426
- [
427
- lit("call", v("r"), v("c")),
428
- lit("comesTo", v("c"), v("f")),
429
- lit("unwraps", v("f"), v("k")),
430
- lit("callArg", v("r"), v("k"), v("a")),
431
- lit("comesTo", v("a"), v("h"))
432
- ]
433
- ),
434
- // Wrapper transparency, declared: a pack says this callee wraps
435
- // argument k, whatever its implementation looks like. The callee has
436
- // to have been imported from the library the pack named, so a local
437
- // object spelled the same way is not mistaken for it.
438
- rule(
439
- "comesTo",
440
- [v("r"), v("h")],
441
- [
442
- lit("calleeName", v("r"), v("n")),
443
- lit("unwrapsByName", v("n"), v("k")),
444
- lit("wrapperModule", v("n"), v("m")),
445
- lit("calleeOrigin", v("r"), v("m")),
446
- lit("callArg", v("r"), v("k"), v("a")),
447
- lit("comesTo", v("a"), v("h"))
448
- ]
449
- ),
450
533
  // Where a name comes from, when what it refers to lives outside the
451
- // source being read. `comesTo` ends at something written out, so it
452
- // never reaches a library's own function, and a project's alias of a
453
- // library name has no answer at all. This follows the same aliases
454
- // and barrels in the other direction and answers with the module and
455
- // 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.
456
536
  rule(
457
537
  "comesFrom",
458
538
  [v("x"), v("m"), v("n")],
459
539
  [lit("imports", v("x"), v("m"), v("n"))]
460
540
  ),
461
- rule(
462
- "comesFrom",
463
- [v("x"), v("m"), v("n")],
464
- [lit("binds", v("x"), v("y")), lit("comesFrom", v("y"), v("m"), v("n"))]
465
- ),
466
541
  rule(
467
542
  "comesFrom",
468
543
  [v("x"), v("m"), v("n")],
469
544
  [
470
- lit("imports", v("x"), v("m2"), v("n2")),
471
- lit("moduleExport", v("m2"), v("n2"), v("value")),
472
- 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"))
473
547
  ]
474
548
  ),
475
549
  // Calling f ends up calling the name n that module m exports. One hop
@@ -505,36 +579,6 @@ var RESOLUTION_RULES = [
505
579
  "resolves",
506
580
  [v("x"), v("z")],
507
581
  [lit("comesTo", v("x"), v("z")), lit("func", v("z"))]
508
- ),
509
- // Following a name to the expression it is written as. A separate
510
- // relation rather than a widening of `comesTo`, so a caller asking
511
- // the older question never sees a new answer: a factory call already
512
- // has an unwrapping answer, and letting it also answer with itself
513
- // would make that pair ambiguous.
514
- //
515
- // The two seeds are the two ways an expression can be written out. An
516
- // object literal is one of them, and it is the shape a generated
517
- // GraphQL document takes.
518
- rule("isWrittenAs", [v("x"), v("x")], [lit("writtenValue", v("x"))]),
519
- rule("isWrittenAs", [v("x"), v("x")], [lit("objectValue", v("x"))]),
520
- rule(
521
- "isWrittenAs",
522
- [v("x"), v("z")],
523
- [lit("binds", v("x"), v("y")), lit("isWrittenAs", v("y"), v("z"))]
524
- ),
525
- rule(
526
- "isWrittenAs",
527
- [v("x"), v("z")],
528
- [lit("endsHolding", v("x"), v("y")), lit("isWrittenAs", v("y"), v("z"))]
529
- ),
530
- rule(
531
- "isWrittenAs",
532
- [v("x"), v("z")],
533
- [
534
- lit("imports", v("x"), v("m"), v("n")),
535
- lit("moduleExport", v("m"), v("n"), v("value")),
536
- lit("isWrittenAs", v("value"), v("z"))
537
- ]
538
582
  )
539
583
  ];
540
584
  var RESOLUTION_QUESTIONS = [
@@ -566,6 +610,13 @@ var RESOLUTION_QUESTIONS = [
566
610
  [v("x"), v("z")],
567
611
  [lit("wanted", v("x")), lit("objectOf", v("x"), v("z"))]
568
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
+ ),
569
620
  rule(
570
621
  "wantedComesFrom",
571
622
  [v("x"), v("m"), v("n")],
@@ -589,5 +640,7 @@ export {
589
640
  FACT_CONTRACT_CASES,
590
641
  RESOLUTION_QUESTIONS,
591
642
  RESOLUTION_RULES,
643
+ RESULT_STEP,
644
+ VALUE_STEP,
592
645
  checkFactContract
593
646
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/resolution",
3
- "version": "0.8.0",
3
+ "version": "0.10.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.8.0"
56
+ "@suss/datalog": "0.10.0"
57
57
  }
58
58
  }