@suss/resolution 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _suss_datalog from '@suss/datalog';
2
- import { Database } from '@suss/datalog';
2
+ import { Database, Atom, Tuple, Proof } from '@suss/datalog';
3
3
 
4
4
  /** The tuples of one relation, as strings, which is all a case compares. */
5
5
  type FactsOf = (relation: string) => string[][];
@@ -36,6 +36,75 @@ declare function checkFactContract(sources: Readonly<Record<string, CaseFiles>>,
36
36
  problem: string;
37
37
  }[]>;
38
38
 
39
+ /**
40
+ * Turn a witness proof over the resolution rules into the chain a
41
+ * person reads: which value led to which, one reason per hop, with the
42
+ * assumptions the chain rests on called out.
43
+ *
44
+ * This module knows which rules are hops and which are plumbing: a
45
+ * `stepsTo` firing is a hop somebody should see, a `reaches` firing is
46
+ * the closure gluing hops together, and the base cases ("x is already
47
+ * a function") end a chain without adding to it. Everything here is
48
+ * about the resolution rules; the engine stays prose-free. Atoms in a
49
+ * proof are the adapter's node ids, so every sentence goes through a
50
+ * caller-supplied `describe` to say them in source terms.
51
+ */
52
+
53
+ /** How a caller says an atom in source terms: a name, a file, a line. */
54
+ type DescribeAtom = (atom: Atom) => string;
55
+ /** One hop of a chain, with the sentence saying why it is true. */
56
+ interface ResolutionStep {
57
+ from: Atom;
58
+ to: Atom;
59
+ /** The rule that fired, by the name it was given. */
60
+ rule: string;
61
+ /** Why this hop is true, in one sentence. */
62
+ reason: string;
63
+ /** Detail under the hop: barrel re-exports, a callee's own chain. */
64
+ notes: string[];
65
+ /** What the hop rests on beyond what the source says. */
66
+ assumptions: string[];
67
+ }
68
+ interface ResolutionExplanation {
69
+ /** The chain's atoms in order, the asked-about value first. */
70
+ atoms: Atom[];
71
+ steps: ResolutionStep[];
72
+ /** All assumptions from every step, in chain order. */
73
+ assumptions: string[];
74
+ /** True when the depth cap or the cycle guard stopped the walk. */
75
+ truncated: boolean;
76
+ }
77
+ /** What a phrase reads: the fired rule's premises, spoken through `describe`. */
78
+ interface StepContext {
79
+ tuple: Tuple;
80
+ premises: readonly Proof[];
81
+ describe: DescribeAtom;
82
+ /** Render a nested proof's own hops, for a callee with a chain of its own. */
83
+ inline: (proof: Proof) => string[];
84
+ }
85
+ type StepPhrase = (context: StepContext) => {
86
+ reason: string;
87
+ notes?: string[];
88
+ assumptions?: string[];
89
+ };
90
+ interface ExplainOptions {
91
+ describe: DescribeAtom;
92
+ /** Phrases for a caller's own step rules, over the built-in ones. */
93
+ phrases?: Record<string, StepPhrase>;
94
+ }
95
+ /**
96
+ * Flatten a proof of one resolution answer into its chain. Returns
97
+ * null for a proof that is not over these rules: an absence (the fact
98
+ * was never derived), or some other rule set's relation.
99
+ */
100
+ declare function explainResolutionProof(proof: Proof, options: ExplainOptions): ResolutionExplanation | null;
101
+ /**
102
+ * The explanation as lines a report prints: the chain first, then one
103
+ * reason per hop with its notes indented under it, then what the chain
104
+ * assumes. A truncated walk says so instead of trailing off.
105
+ */
106
+ declare function renderExplanation(explanation: ResolutionExplanation, describe: DescribeAtom): string[];
107
+
39
108
  /** A step to the value x is written as. */
40
109
  declare const VALUE_STEP: _suss_datalog.Term;
41
110
  /** A step to what running the call x is handed back. */
@@ -76,4 +145,4 @@ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
76
145
  /** The relations `RESOLUTION_QUESTIONS` answers into. */
77
146
  declare const ANSWER_RELATIONS: string[];
78
147
 
79
- export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, VALUE_STEP, checkFactContract };
148
+ export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, type DescribeAtom, type ExplainOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ResolutionExplanation, type ResolutionStep, type StepContext, type StepPhrase, VALUE_STEP, checkFactContract, explainResolutionProof, renderExplanation };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _suss_datalog from '@suss/datalog';
2
- import { Database } from '@suss/datalog';
2
+ import { Database, Atom, Tuple, Proof } from '@suss/datalog';
3
3
 
4
4
  /** The tuples of one relation, as strings, which is all a case compares. */
5
5
  type FactsOf = (relation: string) => string[][];
@@ -36,6 +36,75 @@ declare function checkFactContract(sources: Readonly<Record<string, CaseFiles>>,
36
36
  problem: string;
37
37
  }[]>;
38
38
 
39
+ /**
40
+ * Turn a witness proof over the resolution rules into the chain a
41
+ * person reads: which value led to which, one reason per hop, with the
42
+ * assumptions the chain rests on called out.
43
+ *
44
+ * This module knows which rules are hops and which are plumbing: a
45
+ * `stepsTo` firing is a hop somebody should see, a `reaches` firing is
46
+ * the closure gluing hops together, and the base cases ("x is already
47
+ * a function") end a chain without adding to it. Everything here is
48
+ * about the resolution rules; the engine stays prose-free. Atoms in a
49
+ * proof are the adapter's node ids, so every sentence goes through a
50
+ * caller-supplied `describe` to say them in source terms.
51
+ */
52
+
53
+ /** How a caller says an atom in source terms: a name, a file, a line. */
54
+ type DescribeAtom = (atom: Atom) => string;
55
+ /** One hop of a chain, with the sentence saying why it is true. */
56
+ interface ResolutionStep {
57
+ from: Atom;
58
+ to: Atom;
59
+ /** The rule that fired, by the name it was given. */
60
+ rule: string;
61
+ /** Why this hop is true, in one sentence. */
62
+ reason: string;
63
+ /** Detail under the hop: barrel re-exports, a callee's own chain. */
64
+ notes: string[];
65
+ /** What the hop rests on beyond what the source says. */
66
+ assumptions: string[];
67
+ }
68
+ interface ResolutionExplanation {
69
+ /** The chain's atoms in order, the asked-about value first. */
70
+ atoms: Atom[];
71
+ steps: ResolutionStep[];
72
+ /** All assumptions from every step, in chain order. */
73
+ assumptions: string[];
74
+ /** True when the depth cap or the cycle guard stopped the walk. */
75
+ truncated: boolean;
76
+ }
77
+ /** What a phrase reads: the fired rule's premises, spoken through `describe`. */
78
+ interface StepContext {
79
+ tuple: Tuple;
80
+ premises: readonly Proof[];
81
+ describe: DescribeAtom;
82
+ /** Render a nested proof's own hops, for a callee with a chain of its own. */
83
+ inline: (proof: Proof) => string[];
84
+ }
85
+ type StepPhrase = (context: StepContext) => {
86
+ reason: string;
87
+ notes?: string[];
88
+ assumptions?: string[];
89
+ };
90
+ interface ExplainOptions {
91
+ describe: DescribeAtom;
92
+ /** Phrases for a caller's own step rules, over the built-in ones. */
93
+ phrases?: Record<string, StepPhrase>;
94
+ }
95
+ /**
96
+ * Flatten a proof of one resolution answer into its chain. Returns
97
+ * null for a proof that is not over these rules: an absence (the fact
98
+ * was never derived), or some other rule set's relation.
99
+ */
100
+ declare function explainResolutionProof(proof: Proof, options: ExplainOptions): ResolutionExplanation | null;
101
+ /**
102
+ * The explanation as lines a report prints: the chain first, then one
103
+ * reason per hop with its notes indented under it, then what the chain
104
+ * assumes. A truncated walk says so instead of trailing off.
105
+ */
106
+ declare function renderExplanation(explanation: ResolutionExplanation, describe: DescribeAtom): string[];
107
+
39
108
  /** A step to the value x is written as. */
40
109
  declare const VALUE_STEP: _suss_datalog.Term;
41
110
  /** A step to what running the call x is handed back. */
@@ -76,4 +145,4 @@ declare const RESOLUTION_QUESTIONS: _suss_datalog.Rule[];
76
145
  /** The relations `RESOLUTION_QUESTIONS` answers into. */
77
146
  declare const ANSWER_RELATIONS: string[];
78
147
 
79
- export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, VALUE_STEP, checkFactContract };
148
+ export { ANSWER_RELATIONS, type CaseFiles, type ContractCase, type ContractOptions, type DescribeAtom, type ExplainOptions, FACT_CONTRACT_CASES, type FactsOf, RESOLUTION_QUESTIONS, RESOLUTION_RULES, RESULT_STEP, type ResolutionExplanation, type ResolutionStep, type StepContext, type StepPhrase, VALUE_STEP, checkFactContract, explainResolutionProof, renderExplanation };
package/dist/index.js CHANGED
@@ -113,6 +113,27 @@ var FACT_CONTRACT_CASES = [
113
113
  return facts("callArg").length > 0 ? null : "a construction passes its argument nowhere, so the parameter it lands in has no value";
114
114
  }
115
115
  },
116
+ {
117
+ name: "a name declared as a fallback",
118
+ requires: "a module-level name declared as a fallback between a property read and a call, `cached || build()` say",
119
+ // The fallback expression states its branches; each branch has to be
120
+ // keyed as the value node itself, or the chain stops at the fallback
121
+ // instead of continuing into whichever branch resolves.
122
+ check: (facts) => {
123
+ const branches = facts("fallbackBranch");
124
+ if (branches.length < 2) {
125
+ return `expected a fallback with two branches, found ${branches.length}`;
126
+ }
127
+ if (distinct(branches.map((row) => row[0] ?? "")) !== 1) {
128
+ return "the two branches are keyed under different expressions, so nothing says they are alternatives for one value";
129
+ }
130
+ const call = facts("call")[0]?.[0];
131
+ if (call === void 0) {
132
+ return "the call branch is not written down as a call, so the branch that resolves has nothing behind it";
133
+ }
134
+ return branches.some((row) => row[1] === call) ? null : "no branch is the call node itself, so the chain cannot continue into the branch that resolves";
135
+ }
136
+ },
116
137
  {
117
138
  name: "a value another file declares",
118
139
  requires: "two files, one declaring a value and the other reading it by name",
@@ -170,6 +191,242 @@ function checkFactContract(sources, emit, options = {}) {
170
191
  }, Promise.resolve([]));
171
192
  }
172
193
 
194
+ // src/explain.ts
195
+ import { ruleLabel } from "@suss/datalog";
196
+ function exportTrail(proof, describe) {
197
+ if (proof.kind !== "derived") {
198
+ return [];
199
+ }
200
+ const name = ruleLabel(proof.rule);
201
+ if (name === "re-export") {
202
+ const [m, n, m2, n2] = proof.premises[0].tuple;
203
+ return [
204
+ `${describe(m)} takes ${String(n)} from ${describe(m2)}, where it is called ${String(n2)}`,
205
+ ...exportTrail(proof.premises[1], describe)
206
+ ];
207
+ }
208
+ if (name === "re-export all") {
209
+ const [m, m2] = proof.premises[0].tuple;
210
+ return [
211
+ `${describe(m)} forwards everything ${describe(m2)} exports`,
212
+ ...exportTrail(proof.premises[1], describe)
213
+ ];
214
+ }
215
+ return [];
216
+ }
217
+ function argumentReason(proof, describe) {
218
+ const [, p, a] = proof.tuple;
219
+ if (proof.kind !== "derived") {
220
+ return `${describe(p)} is a parameter, and a call passes it ${describe(a)}`;
221
+ }
222
+ const f = proof.premises[0].tuple[0];
223
+ return `${describe(p)} is a parameter of ${describe(f)}, and a call passes it ${describe(a)}`;
224
+ }
225
+ var DEFAULT_PHRASES = {
226
+ alias: ({ tuple, describe }) => ({
227
+ reason: `${describe(tuple[0])} is declared as ${describe(tuple[1])}`
228
+ }),
229
+ "last write": ({ tuple, describe }) => ({
230
+ reason: `${describe(tuple[0])} is written more than once, and the last write leaves it as ${describe(tuple[1])}`
231
+ }),
232
+ fallback: ({ tuple, describe }) => ({
233
+ reason: `${describe(tuple[0])} is a fallback expression, and ${describe(tuple[1])} is the branch that resolves`
234
+ }),
235
+ import: ({ tuple, premises, describe }) => {
236
+ const [, module, name] = premises[0].tuple;
237
+ return {
238
+ reason: `${describe(tuple[0])} is imported from ${describe(module)} under the name ${String(name)}`,
239
+ notes: exportTrail(premises[1], describe)
240
+ };
241
+ },
242
+ argument: ({ premises, describe }) => ({
243
+ reason: argumentReason(premises[0], describe)
244
+ }),
245
+ "property read": ({ tuple, premises, describe }) => {
246
+ const [, object, name] = premises[0].tuple;
247
+ const contains = premises[2];
248
+ const inherited = contains.kind === "derived" && contains.premises.length > 1;
249
+ return {
250
+ reason: `${describe(tuple[0])} reads ${String(name)} off ${describe(object)}, which contains ${describe(tuple[1])}`,
251
+ notes: inherited ? [`${String(name)} comes from a base class`] : []
252
+ };
253
+ },
254
+ "class instance": ({ tuple, premises, describe, inline }) => ({
255
+ reason: `${describe(tuple[0])} makes an instance of ${describe(tuple[1])}`,
256
+ notes: inline(premises[1])
257
+ }),
258
+ "factory unwrap": ({ tuple, premises, describe, inline }) => {
259
+ const factory = premises[1].tuple[1];
260
+ return {
261
+ reason: `${describe(tuple[0])} calls ${describe(factory)}, a factory that passes its argument through, so it comes down to ${describe(tuple[1])}`,
262
+ notes: inline(premises[1])
263
+ };
264
+ },
265
+ "declared wrapper": ({ tuple, premises, describe }) => {
266
+ const callee = String(premises[0].tuple[1]);
267
+ const module = String(premises[2].tuple[1]);
268
+ const argument = String(premises[1].tuple[1]);
269
+ return {
270
+ reason: `${describe(tuple[0])} calls ${callee} from ${module}, which passes argument ${argument} through, so it comes down to ${describe(tuple[1])}`,
271
+ assumptions: [
272
+ `a pack declares that ${callee} from ${module} passes argument ${argument} through to its result`
273
+ ]
274
+ };
275
+ },
276
+ "call result": ({ tuple, premises, describe, inline }) => {
277
+ const invokes = premises[0];
278
+ const invoked = invokes.tuple[1];
279
+ const calleeWalks = invokes.kind === "derived" ? invokes.premises.filter(
280
+ (premise) => premise.kind === "derived" && (premise.relation === "comesTo" || premise.relation === "givesBack")
281
+ ) : [];
282
+ return {
283
+ reason: `${describe(tuple[0])} runs ${describe(invoked)}, which returns ${describe(tuple[1])}`,
284
+ notes: calleeWalks.flatMap((premise) => inline(premise))
285
+ };
286
+ }
287
+ };
288
+ var CHAIN_RELATIONS = /* @__PURE__ */ new Set([
289
+ "resolves",
290
+ "comesTo",
291
+ "givesBack",
292
+ "isWrittenAs",
293
+ "comesFrom",
294
+ "reaches",
295
+ "stepsTo"
296
+ ]);
297
+ function inlineChain(proof, state) {
298
+ const explained = explainResolutionProof(proof, {
299
+ describe: state.describe,
300
+ phrases: state.phrases
301
+ });
302
+ if (explained === null || explained.steps.length === 0) {
303
+ return [];
304
+ }
305
+ if (explained.truncated) {
306
+ state.truncated = true;
307
+ }
308
+ return explained.steps.map((step) => step.reason);
309
+ }
310
+ function stepFrom(proof, state) {
311
+ const name = ruleLabel(proof.rule);
312
+ const phrase = state.phrases[name];
313
+ const from = proof.tuple[0];
314
+ const to = proof.tuple[1];
315
+ if (phrase === void 0) {
316
+ return {
317
+ from,
318
+ to,
319
+ rule: name,
320
+ reason: `${state.describe(from)} steps to ${state.describe(to)} (${name})`,
321
+ notes: [],
322
+ assumptions: []
323
+ };
324
+ }
325
+ const said = phrase({
326
+ tuple: proof.tuple,
327
+ premises: proof.premises,
328
+ describe: state.describe,
329
+ inline: (nested) => inlineChain(nested, state)
330
+ });
331
+ return {
332
+ from,
333
+ to,
334
+ rule: name,
335
+ reason: said.reason,
336
+ notes: said.notes ?? [],
337
+ assumptions: said.assumptions ?? []
338
+ };
339
+ }
340
+ function importStep(proof, state) {
341
+ const [x, module, name] = proof.tuple;
342
+ return {
343
+ from: x,
344
+ to: module,
345
+ rule: "import",
346
+ reason: `${state.describe(x)} is imported from ${state.describe(module)} under the name ${String(name)}`,
347
+ notes: [],
348
+ assumptions: []
349
+ };
350
+ }
351
+ var flattenInto = (proof, state) => {
352
+ if (proof.kind === "truncated") {
353
+ state.truncated = true;
354
+ return;
355
+ }
356
+ if (proof.kind !== "derived") {
357
+ return;
358
+ }
359
+ const relation = proof.relation;
360
+ if (relation === "stepsTo") {
361
+ state.steps.push(stepFrom(proof, state));
362
+ return;
363
+ }
364
+ if (relation === "reaches") {
365
+ for (const p of proof.premises) {
366
+ flattenInto(p, state);
367
+ }
368
+ return;
369
+ }
370
+ if (relation === "comesFrom") {
371
+ if (proof.premises.length === 2) {
372
+ flattenInto(proof.premises[0], state);
373
+ state.steps.push(importStep(proof.premises[1], state));
374
+ } else {
375
+ state.steps.push(importStep(proof.premises[0], state));
376
+ }
377
+ return;
378
+ }
379
+ for (const p of proof.premises) {
380
+ if (p.kind === "derived" && (p.relation === "reaches" || p.relation === "comesTo")) {
381
+ flattenInto(p, state);
382
+ }
383
+ if (p.kind === "truncated") {
384
+ state.truncated = true;
385
+ }
386
+ }
387
+ };
388
+ function explainResolutionProof(proof, options) {
389
+ if (proof.kind === "absence") {
390
+ return null;
391
+ }
392
+ if (proof.kind === "derived" && !CHAIN_RELATIONS.has(proof.relation)) {
393
+ return null;
394
+ }
395
+ const state = {
396
+ steps: [],
397
+ truncated: proof.kind === "truncated",
398
+ describe: options.describe,
399
+ phrases: { ...DEFAULT_PHRASES, ...options.phrases }
400
+ };
401
+ flattenInto(proof, state);
402
+ const atoms = proof.kind === "fact" || proof.kind === "truncated" ? [proof.tuple[0]] : [proof.tuple[0], ...state.steps.map((step) => step.to)];
403
+ return {
404
+ atoms,
405
+ steps: state.steps,
406
+ assumptions: state.steps.flatMap((step) => step.assumptions),
407
+ truncated: state.truncated
408
+ };
409
+ }
410
+ function renderExplanation(explanation, describe) {
411
+ const lines = [];
412
+ if (explanation.steps.length > 0) {
413
+ lines.push(explanation.atoms.map((atom) => describe(atom)).join(" -> "));
414
+ }
415
+ for (const step of explanation.steps) {
416
+ lines.push(` ${step.reason}`);
417
+ for (const note of step.notes) {
418
+ lines.push(` ${note}`);
419
+ }
420
+ }
421
+ for (const assumption of explanation.assumptions) {
422
+ lines.push(` assuming ${assumption}`);
423
+ }
424
+ if (explanation.truncated) {
425
+ lines.push(" the chain goes on past the proof depth cap");
426
+ }
427
+ return lines;
428
+ }
429
+
173
430
  // src/index.ts
174
431
  import { constant, lit, rule, variable as v } from "@suss/datalog";
175
432
  var VALUE_STEP = constant("value");
@@ -178,14 +435,29 @@ var RESOLUTION_RULES = [
178
435
  // Aliasing: const x = y, or an identifier referencing a declaration.
179
436
  // A language with a hop of its own, like JavaScript's `.bind`, states
180
437
  // 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"))]),
438
+ rule(
439
+ "stepsTo",
440
+ [v("x"), v("y"), VALUE_STEP],
441
+ [lit("binds", v("x"), v("y"))],
442
+ "alias"
443
+ ),
182
444
  // A name written more than once has the value the last write left
183
445
  // there. The adapter works out which write that is and stays quiet
184
446
  // when control flow decides, so such a name steps nowhere at all.
185
447
  rule(
186
448
  "stepsTo",
187
449
  [v("x"), v("y"), VALUE_STEP],
188
- [lit("endsHolding", v("x"), v("y"))]
450
+ [lit("endsHolding", v("x"), v("y"))],
451
+ "last write"
452
+ ),
453
+ // A fallback says the value is one of its branches, so each branch is
454
+ // a step. A branch that resolves to nothing makes no claim, and two
455
+ // branches resolving to different things fail the single-answer policy.
456
+ rule(
457
+ "stepsTo",
458
+ [v("x"), v("b"), VALUE_STEP],
459
+ [lit("fallbackBranch", v("x"), v("b"))],
460
+ "fallback"
189
461
  ),
190
462
  // An import steps to what the module exports under that name.
191
463
  rule(
@@ -194,7 +466,8 @@ var RESOLUTION_RULES = [
194
466
  [
195
467
  lit("imports", v("x"), v("m"), v("n")),
196
468
  lit("moduleExport", v("m"), v("n"), v("value"))
197
- ]
469
+ ],
470
+ "import"
198
471
  ),
199
472
  // A parameter steps to what a call passes it. A function called from
200
473
  // several places leaves its parameter with more than one value, and a
@@ -202,7 +475,8 @@ var RESOLUTION_RULES = [
202
475
  rule(
203
476
  "stepsTo",
204
477
  [v("p"), v("a"), VALUE_STEP],
205
- [lit("passesArgument", v("r"), v("p"), v("a"))]
478
+ [lit("passesArgument", v("r"), v("p"), v("a"))],
479
+ "argument"
206
480
  ),
207
481
  // Reading a property steps to what the object contains under that
208
482
  // name, whichever way the object arrived: `routes.list` off a name,
@@ -214,7 +488,8 @@ var RESOLUTION_RULES = [
214
488
  lit("readsProperty", v("x"), v("o"), v("n")),
215
489
  lit("objectOf", v("o"), v("obj")),
216
490
  lit("contains", v("obj"), v("n"), v("held"))
217
- ]
491
+ ],
492
+ "property read"
218
493
  ),
219
494
  // Calling a class makes one of it, so the call steps to the class and
220
495
  // a method read off the result is the one the class declares. The
@@ -226,7 +501,8 @@ var RESOLUTION_RULES = [
226
501
  lit("call", v("r"), v("c")),
227
502
  lit("comesTo", v("c"), v("cls")),
228
503
  lit("objectValue", v("cls"))
229
- ]
504
+ ],
505
+ "class instance"
230
506
  ),
231
507
  // Wrapper transparency, derived: calling a factory that returns a
232
508
  // function which calls its parameter k steps to argument k.
@@ -238,7 +514,8 @@ var RESOLUTION_RULES = [
238
514
  lit("comesTo", v("c"), v("f")),
239
515
  lit("unwraps", v("f"), v("k")),
240
516
  lit("callArg", v("r"), v("k"), v("a"))
241
- ]
517
+ ],
518
+ "factory unwrap"
242
519
  ),
243
520
  // Wrapper transparency, declared: a pack says this callee wraps
244
521
  // argument k. The callee has to come from the library the pack said,
@@ -252,14 +529,16 @@ var RESOLUTION_RULES = [
252
529
  lit("wrapperModule", v("n"), v("m")),
253
530
  lit("calleeOrigin", v("r"), v("m")),
254
531
  lit("callArg", v("r"), v("k"), v("a"))
255
- ]
532
+ ],
533
+ "declared wrapper"
256
534
  ),
257
535
  // The one step that runs a function forwards: a call steps to what
258
536
  // the function it invokes returns.
259
537
  rule(
260
538
  "stepsTo",
261
539
  [v("r"), v("ret"), RESULT_STEP],
262
- [lit("invokes", v("r"), v("f")), lit("returnsValue", v("f"), v("ret"))]
540
+ [lit("invokes", v("r"), v("f")), lit("returnsValue", v("f"), v("ret"))],
541
+ "call result"
263
542
  ),
264
543
  // Where a walk gets to, and whether it ran a call on the way. A value
265
544
  // step keeps whatever the rest of the walk was, and a result step
@@ -374,7 +653,8 @@ var RESOLUTION_RULES = [
374
653
  rule(
375
654
  "moduleExport",
376
655
  [v("m"), v("n"), v("value")],
377
- [lit("exportsAs", v("m"), v("n"), v("value"))]
656
+ [lit("exportsAs", v("m"), v("n"), v("value"))],
657
+ "export"
378
658
  ),
379
659
  rule(
380
660
  "moduleExport",
@@ -382,7 +662,8 @@ var RESOLUTION_RULES = [
382
662
  [
383
663
  lit("reExports", v("m"), v("n"), v("m2"), v("n2")),
384
664
  lit("moduleExport", v("m2"), v("n2"), v("value"))
385
- ]
665
+ ],
666
+ "re-export"
386
667
  ),
387
668
  rule(
388
669
  "moduleExport",
@@ -390,7 +671,8 @@ var RESOLUTION_RULES = [
390
671
  [
391
672
  lit("reExportsAll", v("m"), v("m2")),
392
673
  lit("moduleExport", v("m2"), v("n"), v("value"))
393
- ]
674
+ ],
675
+ "re-export all"
394
676
  ),
395
677
  // The function a call runs, written from the call's side because a
396
678
  // caller asking what a call gives back has the call in hand.
@@ -642,5 +924,7 @@ export {
642
924
  RESOLUTION_RULES,
643
925
  RESULT_STEP,
644
926
  VALUE_STEP,
645
- checkFactContract
927
+ checkFactContract,
928
+ explainResolutionProof,
929
+ renderExplanation
646
930
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/resolution",
3
- "version": "0.10.0",
3
+ "version": "0.12.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.10.0"
56
+ "@suss/datalog": "0.12.0"
57
57
  }
58
58
  }