Skip to content

Commit 8334a56

Browse files
erightsgibson042
andauthored
refactor(patterns): use confirm/reject style in @endo/patterns (#2922)
Staged on #2917 Closes: #XXXX Refs: #2911 #2285 #2918 #2917 ## Description Uses the new Rejector pattern suggested by #2285 and implemented in #2923 to restructure all the check*/Checker patterns in @endo/patterns to use the confirm*/reject pattern instead. This is also a test and demonstration of the basic functionality of #2923. For this experiment, as @gibson042 expected, the code is both smaller and more readable. Should be a pure refactor, not causing any effects observable from outside @endo/patterns ### Security Considerations If it is indeed a pure refactor, none. Except of course that the code is simpler, smaller, and easier to understand and review, which helps security. ### Scaling Considerations From eyeballing these, I suspect `matches` and the non-throwing cases of `mustMatch` will be faster. But we need to measure that to see how much. This could be significant, as previous measurement have shown pattern matching to be somewhat of a bottleneck. ### Documentation Considerations The actual confim/reject pattern needs to be documented somewhere, for developers that wish to use it internally. But since the confirm* functions/methods are normally encapsulated, as they are here, normal developers who consume our APIs won't need to be aware of these changes. ### Testing Considerations No tests needed to change, ***except for some error message improvements***, raising our confidence that it is a pure refactor. ### Compatibility Considerations As a pure refactor with no change to the representation of any data, should be none. There remains the linking problem if these changes to @endo/patterns are linked with a version of @endo/errors prior to #2923. This PR does pull in the `Rejector` type exported by @endo/errors/rejector.js, which does not exist in earlier forms of @endo/errors. But these are only static types and static type imports, so it is hard to see how it could cause a dynamic problem. The ***error message improvements*** might cause some golden tests to fail, which will need fixing. However, such improvements are explicitly not considered semantic/normative, and thus the need to fix those golden tests is within the rules of the road for what we consider a "pure refactor". ### Upgrade Considerations As a pure refactor, none. --------- Co-authored-by: Richard Gibson <[email protected]>
1 parent 7408280 commit 8334a56

File tree

8 files changed

+557
-546
lines changed

8 files changed

+557
-546
lines changed

packages/patterns/src/keys/checkKey.js

Lines changed: 71 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,57 @@
11
/// <reference types="ses"/>
22

3-
import { X, q, Fail } from '@endo/errors';
4-
import { identChecker } from '@endo/common/ident-checker.js';
5-
import {
6-
assertChecker,
7-
Far,
8-
getTag,
9-
makeTagged,
10-
passStyleOf,
11-
isAtom,
12-
} from '@endo/pass-style';
3+
import { Fail, q, hideAndHardenFunction } from '@endo/errors';
4+
import { Far, getTag, makeTagged, passStyleOf, isAtom } from '@endo/pass-style';
135
import {
146
compareAntiRank,
157
makeFullOrderComparatorKit,
168
sortByRank,
179
} from '@endo/marshal';
1810

19-
import { checkElements, makeSetOfElements } from './copySet.js';
20-
import { checkBagEntries, makeBagOfEntries } from './copyBag.js';
11+
import { confirmElements, makeSetOfElements } from './copySet.js';
12+
import { confirmBagEntries, makeBagOfEntries } from './copyBag.js';
2113

2214
const { ownKeys } = Reflect;
2315

2416
/**
25-
* @import {Passable, Atom, Checker} from '@endo/pass-style'
26-
* @import {CopyBag, CopyMap, CopySet, Key, ScalarKey} from '../types.js'
17+
* @import {Rejector} from '@endo/errors/rejector.js';
18+
* @import {Passable, Atom} from '@endo/pass-style';
19+
* @import {CopyBag, CopyMap, CopySet, Key, ScalarKey} from '../types.js';
2720
*/
2821

2922
// ////////////////// Atom and Scalar keys ////////////////////////////////
3023

3124
/**
3225
* @param {any} val
33-
* @param {Checker} check
26+
* @param {Rejector} reject
3427
* @returns {boolean}
3528
*/
36-
export const checkScalarKey = (val, check) => {
29+
export const confirmScalarKey = (val, reject) => {
3730
if (isAtom(val)) {
3831
return true;
3932
}
4033
const passStyle = passStyleOf(val);
4134
if (passStyle === 'remotable') {
4235
return true;
4336
}
44-
return check(false, X`A ${q(passStyle)} cannot be a scalar key: ${val}`);
37+
return reject && reject`A ${q(passStyle)} cannot be a scalar key: ${val}`;
4538
};
4639

4740
/**
4841
* @param {any} val
4942
* @returns {val is ScalarKey}
5043
*/
51-
export const isScalarKey = val => checkScalarKey(val, identChecker);
52-
harden(isScalarKey);
44+
export const isScalarKey = val => confirmScalarKey(val, false);
45+
hideAndHardenFunction(isScalarKey);
5346

5447
/**
5548
* @param {Passable} val
5649
* @returns {asserts val is ScalarKey}
5750
*/
5851
export const assertScalarKey = val => {
59-
checkScalarKey(val, assertChecker);
52+
confirmScalarKey(val, Fail);
6053
};
61-
harden(assertScalarKey);
54+
hideAndHardenFunction(assertScalarKey);
6255

6356
// ////////////////////////////// Keys /////////////////////////////////////////
6457

@@ -69,10 +62,10 @@ const keyMemo = new WeakSet();
6962

7063
/**
7164
* @param {unknown} val
72-
* @param {Checker} check
65+
* @param {Rejector} reject
7366
* @returns {boolean}
7467
*/
75-
export const checkKey = (val, check) => {
68+
export const confirmKey = (val, reject) => {
7669
if (isAtom(val)) {
7770
return true;
7871
}
@@ -81,36 +74,36 @@ export const checkKey = (val, check) => {
8174
return true;
8275
}
8376
// eslint-disable-next-line no-use-before-define
84-
const result = checkKeyInternal(val, check);
77+
const result = confirmKeyInternal(val, reject);
8578
if (result) {
8679
// Don't cache the undefined cases, so that if it is tried again
87-
// with `assertChecker` it'll throw a diagnostic again
80+
// with `Fail` it'll throw a diagnostic again
8881
// @ts-expect-error narrowed
8982
keyMemo.add(val);
9083
}
91-
// Note that we do not memoize a negative judgement, so that if it is tried
92-
// again with a checker, it will still produce a useful diagnostic.
84+
// Note that we must not memoize a negative judgement, so that if it is tried
85+
// again with `Fail`, it will still produce a useful diagnostic.
9386
return result;
9487
};
95-
harden(checkKey);
88+
harden(confirmKey);
9689

9790
/**
9891
* @type {{
9992
* (val: Passable): val is Key;
10093
* (val: any): boolean;
10194
* }}
10295
*/
103-
export const isKey = val => checkKey(val, identChecker);
104-
harden(isKey);
96+
export const isKey = val => confirmKey(val, false);
97+
hideAndHardenFunction(isKey);
10598

10699
/**
107100
* @param {Key} val
108101
* @returns {asserts val is Key}
109102
*/
110103
export const assertKey = val => {
111-
checkKey(val, assertChecker);
104+
confirmKey(val, Fail);
112105
};
113-
harden(assertKey);
106+
hideAndHardenFunction(assertKey);
114107

115108
// //////////////////////////// CopySet ////////////////////////////////////////
116109

@@ -122,31 +115,31 @@ const copySetMemo = new WeakSet();
122115

123116
/**
124117
* @param {any} s
125-
* @param {Checker} check
118+
* @param {Rejector} reject
126119
* @returns {boolean}
127120
*/
128-
export const checkCopySet = (s, check) => {
121+
export const confirmCopySet = (s, reject) => {
129122
if (copySetMemo.has(s)) {
130123
return true;
131124
}
132125
const result =
133126
((passStyleOf(s) === 'tagged' && getTag(s) === 'copySet') ||
134-
check(false, X`Not a copySet: ${s}`)) &&
135-
checkElements(s.payload, check) &&
136-
checkKey(s.payload, check);
127+
(reject && reject`Not a copySet: ${s}`)) &&
128+
confirmElements(s.payload, reject) &&
129+
confirmKey(s.payload, reject);
137130
if (result) {
138131
copySetMemo.add(s);
139132
}
140133
return result;
141134
};
142-
harden(checkCopySet);
135+
harden(confirmCopySet);
143136

144137
/**
145138
* @param {any} s
146139
* @returns {s is CopySet}
147140
*/
148-
export const isCopySet = s => checkCopySet(s, identChecker);
149-
harden(isCopySet);
141+
export const isCopySet = s => confirmCopySet(s, false);
142+
hideAndHardenFunction(isCopySet);
150143

151144
/**
152145
* @callback AssertCopySet
@@ -156,9 +149,9 @@ harden(isCopySet);
156149

157150
/** @type {AssertCopySet} */
158151
export const assertCopySet = s => {
159-
checkCopySet(s, assertChecker);
152+
confirmCopySet(s, Fail);
160153
};
161-
harden(assertCopySet);
154+
hideAndHardenFunction(assertCopySet);
162155

163156
/**
164157
* @template {Key} K
@@ -203,31 +196,31 @@ const copyBagMemo = new WeakSet();
203196

204197
/**
205198
* @param {any} b
206-
* @param {Checker} check
199+
* @param {Rejector} reject
207200
* @returns {boolean}
208201
*/
209-
export const checkCopyBag = (b, check) => {
202+
export const confirmCopyBag = (b, reject) => {
210203
if (copyBagMemo.has(b)) {
211204
return true;
212205
}
213206
const result =
214207
((passStyleOf(b) === 'tagged' && getTag(b) === 'copyBag') ||
215-
check(false, X`Not a copyBag: ${b}`)) &&
216-
checkBagEntries(b.payload, check) &&
217-
checkKey(b.payload, check);
208+
(reject && reject`Not a copyBag: ${b}`)) &&
209+
confirmBagEntries(b.payload, reject) &&
210+
confirmKey(b.payload, reject);
218211
if (result) {
219212
copyBagMemo.add(b);
220213
}
221214
return result;
222215
};
223-
harden(checkCopyBag);
216+
harden(confirmCopyBag);
224217

225218
/**
226219
* @param {any} b
227220
* @returns {b is CopyBag}
228221
*/
229-
export const isCopyBag = b => checkCopyBag(b, identChecker);
230-
harden(isCopyBag);
222+
export const isCopyBag = b => confirmCopyBag(b, false);
223+
hideAndHardenFunction(isCopyBag);
231224

232225
/**
233226
* @callback AssertCopyBag
@@ -237,9 +230,9 @@ harden(isCopyBag);
237230

238231
/** @type {AssertCopyBag} */
239232
export const assertCopyBag = b => {
240-
checkCopyBag(b, assertChecker);
233+
confirmCopyBag(b, Fail);
241234
};
242-
harden(assertCopyBag);
235+
hideAndHardenFunction(assertCopyBag);
243236

244237
/**
245238
* @template {Key} K
@@ -309,58 +302,54 @@ const copyMapMemo = new WeakSet();
309302

310303
/**
311304
* @param {any} m
312-
* @param {Checker} check
305+
* @param {Rejector} reject
313306
* @returns {boolean}
314307
*/
315-
export const checkCopyMap = (m, check) => {
308+
export const confirmCopyMap = (m, reject) => {
316309
if (copyMapMemo.has(m)) {
317310
return true;
318311
}
319312
if (!(passStyleOf(m) === 'tagged' && getTag(m) === 'copyMap')) {
320-
return check(false, X`Not a copyMap: ${m}`);
313+
return reject && reject`Not a copyMap: ${m}`;
321314
}
322315
const { payload } = m;
323316
if (passStyleOf(payload) !== 'copyRecord') {
324-
return check(false, X`A copyMap's payload must be a record: ${m}`);
317+
return reject && reject`A copyMap's payload must be a record: ${m}`;
325318
}
326319
const { keys, values, ...rest } = payload;
327320
const result =
328321
(ownKeys(rest).length === 0 ||
329-
check(
330-
false,
331-
X`A copyMap's payload must only have .keys and .values: ${m}`,
332-
)) &&
333-
checkElements(keys, check) &&
334-
checkKey(keys, check) &&
322+
(reject &&
323+
reject`A copyMap's payload must only have .keys and .values: ${m}`)) &&
324+
confirmElements(keys, reject) &&
325+
confirmKey(keys, reject) &&
335326
(passStyleOf(values) === 'copyArray' ||
336-
check(false, X`A copyMap's .values must be a copyArray: ${m}`)) &&
327+
(reject && reject`A copyMap's .values must be a copyArray: ${m}`)) &&
337328
(keys.length === values.length ||
338-
check(
339-
false,
340-
X`A copyMap must have the same number of keys and values: ${m}`,
341-
));
329+
(reject &&
330+
reject`A copyMap must have the same number of keys and values: ${m}`));
342331
if (result) {
343332
copyMapMemo.add(m);
344333
}
345334
return result;
346335
};
347-
harden(checkCopyMap);
336+
harden(confirmCopyMap);
348337

349338
/**
350339
* @param {any} m
351340
* @returns {m is CopyMap<Key, Passable>}
352341
*/
353-
export const isCopyMap = m => checkCopyMap(m, identChecker);
354-
harden(isCopyMap);
342+
export const isCopyMap = m => confirmCopyMap(m, false);
343+
hideAndHardenFunction(isCopyMap);
355344

356345
/**
357346
* @param {Passable} m
358347
* @returns {asserts m is CopyMap<Key, Passable>}
359348
*/
360349
export const assertCopyMap = m => {
361-
checkCopyMap(m, assertChecker);
350+
confirmCopyMap(m, Fail);
362351
};
363-
harden(assertCopyMap);
352+
hideAndHardenFunction(assertCopyMap);
364353

365354
/**
366355
* @template {Key} K
@@ -497,14 +486,14 @@ harden(makeCopyMap);
497486
// //////////////////////// Keys Recur /////////////////////////////////////////
498487

499488
/**
500-
* `checkKeyInternal` is only called if `val` is Passable but is not an Atom.
489+
* `confirmKeyInternal` is only called if `val` is Passable but is not an Atom.
501490
*
502491
* @param {any} val
503-
* @param {Checker} check
492+
* @param {Rejector} reject
504493
* @returns {boolean}
505494
*/
506-
const checkKeyInternal = (val, check) => {
507-
const checkIt = child => checkKey(child, check);
495+
const confirmKeyInternal = (val, reject) => {
496+
const checkIt = child => confirmKey(child, reject);
508497

509498
const passStyle = passStyleOf(val);
510499
switch (passStyle) {
@@ -524,31 +513,30 @@ const checkKeyInternal = (val, check) => {
524513
const tag = getTag(val);
525514
switch (tag) {
526515
case 'copySet': {
527-
return checkCopySet(val, check);
516+
return confirmCopySet(val, reject);
528517
}
529518
case 'copyBag': {
530-
return checkCopyBag(val, check);
519+
return confirmCopyBag(val, reject);
531520
}
532521
case 'copyMap': {
533522
return (
534-
checkCopyMap(val, check) &&
523+
confirmCopyMap(val, reject) &&
535524
// For a copyMap to be a key, all its keys and values must
536-
// be keys. Keys already checked by `checkCopyMap` since
525+
// be keys. Keys already checked by `confirmCopyMap` since
537526
// that's a copyMap requirement in general.
538527
everyCopyMapValue(val, checkIt)
539528
);
540529
}
541530
default: {
542531
return (
543-
check !== identChecker &&
544-
check(false, X`A passable tagged ${q(tag)} is not a key: ${val}`)
532+
reject && reject`A passable tagged ${q(tag)} is not a key: ${val}`
545533
);
546534
}
547535
}
548536
}
549537
case 'error':
550538
case 'promise': {
551-
return check(false, X`A ${q(passStyle)} cannot be a key`);
539+
return reject && reject`A ${q(passStyle)} cannot be a key`;
552540
}
553541
default: {
554542
// Unexpected tags are just non-keys, but an unexpected passStyle

0 commit comments

Comments
 (0)