@oh-my-pi/omptype 17.2.7 → 17.2.8

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.
@@ -15,13 +15,10 @@
15
15
  * members compile to inline predicates
16
16
  */
17
17
  import { MISSING, OmpErrors } from "./errors.js";
18
- import { canRefineUnionFailure, unionFail, walk } from "./interp.js";
18
+ import { canRefineUnionFailure, materializeDefault, unionFail, walk } from "./interp.js";
19
19
  import { expectedOf, hasMorph } from "./ir.js";
20
20
  const own = Object.prototype.hasOwnProperty;
21
21
  const IDENT = /^[A-Za-z_$][\w$]*$/;
22
- function access(base, key) {
23
- return IDENT.test(key) ? `${base}.${key}` : `${base}[${JSON.stringify(key)}]`;
24
- }
25
22
  /** Inline-able literal, else undefined (caller hoists into the refs pool). */
26
23
  function litSource(v) {
27
24
  if (v === null)
@@ -98,8 +95,11 @@ class Builder {
98
95
  lit(v) {
99
96
  return litSource(v) ?? this.ref(v);
100
97
  }
98
+ access(base, key) {
99
+ return typeof key === "string" && IDENT.test(key) ? `${base}.${key}` : `${base}[${this.lit(key)}]`;
100
+ }
101
101
  pathExpr(segs) {
102
- const parts = segs.map(seg => ("s" in seg ? JSON.stringify(seg.s) : seg.d));
102
+ const parts = segs.map(seg => ("s" in seg ? this.lit(seg.s) : seg.d));
103
103
  return `[${parts.join(",")}]`;
104
104
  }
105
105
  storedPathExpr(segs) {
@@ -107,7 +107,7 @@ class Builder {
107
107
  return "undefined";
108
108
  if (segs.length === 1) {
109
109
  const seg = segs[0];
110
- return "s" in seg ? JSON.stringify(seg.s) : seg.d;
110
+ return "s" in seg ? this.lit(seg.s) : seg.d;
111
111
  }
112
112
  const staticParts = [];
113
113
  for (const seg of segs) {
@@ -117,8 +117,14 @@ class Builder {
117
117
  }
118
118
  return this.ref(staticParts);
119
119
  }
120
+ error(segs, expected, dataExpr) {
121
+ return `new AE(${this.storedPathExpr(segs)},${JSON.stringify(expected)},${dataExpr})`;
122
+ }
120
123
  fail(segs, expected, dataExpr) {
121
- return `return new AE(${this.storedPathExpr(segs)},${JSON.stringify(expected)},${dataExpr})`;
124
+ return `return ${this.error(segs, expected, dataExpr)}`;
125
+ }
126
+ appendError(errors, error) {
127
+ return `if(${errors}===undefined)${errors}=${error};else ${errors}.append(${error});`;
122
128
  }
123
129
  /** Pure boolean predicate for a morph-free subtree. */
124
130
  predicate(node, v) {
@@ -193,10 +199,10 @@ class Builder {
193
199
  return out;
194
200
  }
195
201
  case "object": {
196
- const checks = [`typeof ${v}==="object"`, `${v}!==null`, `!Array.isArray(${v})`];
202
+ const checks = [`typeof ${v}==="object"`, `${v}!==null`];
197
203
  for (const p of node.props) {
198
- const av = access(v, p.key);
199
- const present = `${JSON.stringify(p.key)} in ${v}`;
204
+ const av = this.access(v, p.key);
205
+ const present = `${this.lit(p.key)} in ${v}`;
200
206
  const predicate = this.predicate(p.val, av);
201
207
  checks.push(p.opt || p.hasDefault
202
208
  ? rejectsUndefined(p.val)
@@ -206,13 +212,29 @@ class Builder {
206
212
  ? predicate
207
213
  : `((${present})&&(${predicate}))`);
208
214
  }
209
- if (node.index) {
210
- const k = this.next("k");
211
- checks.push(`(()=>{for(const ${k} in ${v})if(own.call(${v},${k})&&!(${this.predicate(node.index, `${v}[${k}]`)}))return false;return true})()`);
215
+ const stringKey = this.next("k");
216
+ if (node.index !== undefined) {
217
+ checks.push(`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&!(${this.predicate(node.index, `${v}[${stringKey}]`)}))return false;return true})()`);
212
218
  }
213
- else if (node.extras === "reject") {
214
- const k = this.next("k");
215
- checks.push(`(()=>{for(const ${k} in ${v})if(own.call(${v},${k})&&!(${this.declaredCheck(node.props, k)}))return false;return true})()`);
219
+ if (node.patternIndexes !== undefined) {
220
+ for (const pattern of node.patternIndexes) {
221
+ checks.push(`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&(${this.predicate(pattern.key, stringKey)})&&!(${this.predicate(pattern.val, `${v}[${stringKey}]`)}))return false;return true})()`);
222
+ }
223
+ }
224
+ if (node.symbolIndex !== undefined) {
225
+ const symbol = this.next("s");
226
+ checks.push(`(()=>{for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.predicate(node.symbolIndex, `${v}[${symbol}]`)}))return false;return true})()`);
227
+ }
228
+ if (node.extras === "reject") {
229
+ const patternMatch = node.patternIndexes?.map(pattern => `(${this.predicate(pattern.key, stringKey)})`).join("||") ??
230
+ "false";
231
+ if (node.index === undefined) {
232
+ checks.push(`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&!(${this.declaredCheck(node.props, stringKey)})&&!(${patternMatch}))return false;return true})()`);
233
+ }
234
+ if (node.symbolIndex === undefined) {
235
+ const symbol = this.next("s");
236
+ checks.push(`(()=>{for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.declaredCheck(node.props, symbol)}))return false;return true})()`);
237
+ }
216
238
  }
217
239
  return `(${checks.join("&&")})`;
218
240
  }
@@ -229,125 +251,207 @@ class Builder {
229
251
  const set = this.ref(new Set(props.map(p => p.key)));
230
252
  return `${set}.has(${keyVar})`;
231
253
  }
232
- return `(${props.map(p => `${keyVar}===${JSON.stringify(p.key)}`).join("||")})`;
254
+ return `(${props.map(p => `${keyVar}===${this.lit(p.key)}`).join("||")})`;
233
255
  }
234
- emitDelegate(node, v, segs, out) {
235
- const runner = node.k === "sub" ? node.schema.run : boundWalk(node);
256
+ /**
257
+ * Run a node through its interpreter/sub-schema runner, appending any
258
+ * failure to `errors`. `brk` (when given) exits the enclosing block on
259
+ * failure so dependent statements (output assignment, morph fns) are
260
+ * skipped. Both runner kinds receive the absolute path so nested step
261
+ * callbacks observe ctx.path; walk-produced errors are already absolute,
262
+ * while sub runners return schema-relative errors that need prefixing.
263
+ */
264
+ emitCollectDelegate(node, v, segs, errors, brk, out) {
265
+ const sub = node.k === "sub";
266
+ const runner = sub ? node.schema.run : boundWalk(node);
236
267
  const result = this.next("r");
237
- this.push(`const ${result}=${this.ref(runner)}(${v});`);
238
- this.push(segs.length === 0
239
- ? `if(${result} instanceof AE)return ${result};`
240
- : `if(${result} instanceof AE)return PF(${result},${this.pathExpr(segs)});`);
268
+ const args = segs.length > 0 ? `${v},${this.pathExpr(segs)}` : v;
269
+ this.push(`const ${result}=${this.ref(runner)}(${args});`);
270
+ const failure = sub && segs.length > 0 ? `PF(${result},${this.pathExpr(segs)})` : result;
271
+ this.push(`if(${result} instanceof AE){${this.appendError(errors, failure)}${brk === undefined ? "" : `break ${brk};`}}`);
241
272
  if (out !== undefined)
242
273
  this.push(`${out}=${result};`);
243
274
  }
244
- emitTupleShape(node, v, segs, failureData) {
245
- this.push(`if(!Array.isArray(${v}))${this.fail(segs, "an array", failureData)};`);
246
- const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
247
- const minimum = requiredPrefix + node.postfix.length;
248
- if (minimum > 0) {
249
- this.push(`if(${v}.length<${minimum})${this.fail(segs, `an array of at least length ${minimum}`, failureData)};`);
250
- }
251
- if (node.variadic === undefined) {
252
- const maximum = node.prefix.length + node.postfix.length;
253
- this.push(`if(${v}.length>${maximum})${this.fail(segs, `an array of at most length ${maximum}`, failureData)};`);
254
- }
255
- let postfixStart = `${v}.length`;
256
- if (node.postfix.length > 0) {
257
- postfixStart = this.next("p");
258
- this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
259
- }
260
- let prefixCount = String(node.prefix.length);
261
- if (requiredPrefix !== node.prefix.length) {
262
- prefixCount = this.next("n");
263
- this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
264
- }
265
- return { postfixStart, prefixCount, requiredPrefix };
275
+ /** Snapshot the error count so sequencing sites can detect soft failures. */
276
+ markErrors(errors) {
277
+ const mark = this.next("n");
278
+ this.push(`const ${mark}=${errors}===void 0?0:${errors}.length;`);
279
+ return mark;
266
280
  }
267
- /** Statement-form check for a morph-free subtree with precise error paths. */
268
- emitCheck(node, v, segs, failureData = v) {
281
+ /** Exit `brk` when errors were appended since `mark` (interp's return-on-error). */
282
+ guardGrowth(errors, mark, brk) {
283
+ this.push(`if((${errors}===void 0?0:${errors}.length)!==${mark})break ${brk};`);
284
+ }
285
+ /** Aggregate every independent failure in a morph-free subtree. */
286
+ emitCollectCheck(node, v, segs, errors, failureData = v) {
287
+ if (node.cfg !== undefined || node.k === "refine") {
288
+ this.emitCollectDelegate(node, v, segs, errors);
289
+ return;
290
+ }
269
291
  switch (node.k) {
270
292
  case "unknown":
271
293
  return;
272
294
  case "array": {
273
- let head = `Array.isArray(${v})`;
274
- if (node.min !== undefined)
275
- head += `&&${v}.length>=${node.min}`;
276
- if (node.max !== undefined)
277
- head += `&&${v}.length<=${node.max}`;
278
- this.push(`if(!(${head}))${this.fail(segs, expectedOf(node), failureData)};`);
279
- const i = this.next("i");
280
- const x = this.next("x");
281
- this.push(`for(let ${i}=0;${i}<${v}.length;${i}++){const ${x}=${v}[${i}];`);
282
- this.emitCheck(node.el, x, [...segs, { d: i }]);
283
- this.push("}");
295
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}}`);
296
+ if (node.min !== undefined) {
297
+ this.push(`else if(${v}.length<${node.min}){${this.appendError(errors, this.error(segs, `at least length ${node.min}`, `${v}.length`))}}`);
298
+ }
299
+ if (node.max !== undefined) {
300
+ this.push(`else if(${v}.length>${node.max}){${this.appendError(errors, this.error(segs, `at most length ${node.max}`, `${v}.length`))}}`);
301
+ }
302
+ this.push("else{");
303
+ const index = this.next("i");
304
+ this.push(`for(let ${index}=0;${index}<${v}.length;${index}++){`);
305
+ this.emitCollectCheck(node.el, `${v}[${index}]`, [...segs, { d: index }], errors);
306
+ this.push("}}");
284
307
  return;
285
308
  }
286
309
  case "tuple": {
287
- const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(node, v, segs, failureData);
310
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}}else{`);
311
+ const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
312
+ const minimum = requiredPrefix + node.postfix.length;
313
+ const maximum = node.prefix.length + node.postfix.length;
314
+ if (minimum > 0) {
315
+ this.push(`if(${v}.length<${minimum}){${this.appendError(errors, this.error(segs, `an array of at least length ${minimum}`, failureData))}}else{`);
316
+ }
317
+ if (node.variadic === undefined) {
318
+ this.push(`if(${v}.length>${maximum}){${this.appendError(errors, this.error(segs, `an array of at most length ${maximum}`, failureData))}}else{`);
319
+ }
320
+ let postfixStart = `${v}.length`;
321
+ if (node.postfix.length > 0) {
322
+ postfixStart = this.next("p");
323
+ this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
324
+ }
325
+ let prefixCount = String(node.prefix.length);
326
+ if (requiredPrefix !== node.prefix.length) {
327
+ prefixCount = this.next("n");
328
+ this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
329
+ }
288
330
  for (let index = 0; index < node.prefix.length; index++) {
289
331
  if (index >= requiredPrefix)
290
332
  this.push(`if(${index}<${prefixCount}){`);
291
- this.emitCheck(node.prefix[index].val, `${v}[${index}]`, [...segs, { d: String(index) }]);
333
+ this.emitCollectCheck(node.prefix[index].val, `${v}[${index}]`, [...segs, { d: String(index) }], errors);
292
334
  if (index >= requiredPrefix)
293
335
  this.push("}");
294
336
  }
295
337
  if (node.variadic !== undefined) {
296
338
  const index = this.next("i");
297
339
  this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){`);
298
- this.emitCheck(node.variadic, `${v}[${index}]`, [...segs, { d: index }]);
340
+ this.emitCollectCheck(node.variadic, `${v}[${index}]`, [...segs, { d: index }], errors);
299
341
  this.push("}");
300
342
  }
301
343
  for (let index = 0; index < node.postfix.length; index++) {
302
344
  const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
303
- this.emitCheck(node.postfix[index], `${v}[${inputIndex}]`, [...segs, { d: inputIndex }]);
345
+ this.emitCollectCheck(node.postfix[index], `${v}[${inputIndex}]`, [...segs, { d: inputIndex }], errors);
304
346
  }
347
+ if (node.variadic === undefined)
348
+ this.push("}");
349
+ if (minimum > 0)
350
+ this.push("}");
351
+ this.push("}");
305
352
  return;
306
353
  }
307
354
  case "object": {
308
- this.push(`if(typeof ${v}!=="object"||${v}===null||Array.isArray(${v}))${this.fail(segs, "an object", failureData)};`);
309
- for (const p of node.props) {
310
- const present = `${JSON.stringify(p.key)} in ${v}`;
311
- const propSegs = [...segs, { s: p.key }];
312
- if (p.opt || p.hasDefault) {
355
+ if (node.patternIndexes !== undefined ||
356
+ node.symbolIndex !== undefined ||
357
+ node.props.some(prop => typeof prop.key === "symbol")) {
358
+ this.emitCollectDelegate(node, v, segs, errors);
359
+ return;
360
+ }
361
+ this.push(`if(typeof ${v}!=="object"||${v}===null){${this.appendError(errors, this.error(segs, "an object", failureData))}}else{`);
362
+ for (const prop of node.props) {
363
+ const present = `${this.lit(prop.key)} in ${v}`;
364
+ const propSegs = [...segs, { s: prop.key }];
365
+ if (prop.opt || prop.hasDefault) {
313
366
  this.push(`if(${present}){`);
314
- this.emitCheck(p.val, access(v, p.key), propSegs);
367
+ this.emitCollectCheck(prop.val, this.access(v, prop.key), propSegs, errors);
315
368
  this.push("}");
316
369
  }
317
370
  else {
318
- this.push(`if(!(${present}))${this.fail(propSegs, expectedOf(p.val), "M")};`);
319
- this.emitCheck(p.val, access(v, p.key), propSegs);
371
+ this.push(`if(!(${present})){${this.appendError(errors, this.error(propSegs, expectedOf(prop.val), "M"))}}else{`);
372
+ this.emitCollectCheck(prop.val, this.access(v, prop.key), propSegs, errors);
373
+ this.push("}");
320
374
  }
321
375
  }
322
- if (node.index) {
323
- const k = this.next("k");
324
- this.push(`for(const ${k} in ${v})if(own.call(${v},${k})){`);
325
- this.emitCheck(node.index, `${v}[${k}]`, [...segs, { d: k }]);
376
+ if (node.index !== undefined) {
377
+ const key = this.next("k");
378
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})){`);
379
+ this.emitCollectCheck(node.index, `${v}[${key}]`, [...segs, { d: key }], errors);
326
380
  this.push("}");
327
381
  }
328
382
  else if (node.extras === "reject") {
329
- const k = this.next("k");
330
- this.push(`for(const ${k} in ${v})if(own.call(${v},${k})&&!(${this.declaredCheck(node.props, k)}))${this.fail([...segs, { d: k }], "removed (undeclared key)", `${v}[${k}]`)};`);
383
+ const key = this.next("k");
384
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})&&!(${this.declaredCheck(node.props, key)})){`);
385
+ this.push(this.appendError(errors, this.error([...segs, { d: key }], "removed", `${v}[${key}]`)));
386
+ this.push("}");
387
+ }
388
+ if (node.extras === "reject") {
389
+ const symbol = this.next("s");
390
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.declaredCheck(node.props, symbol)})){${this.appendError(errors, this.error([...segs, { d: symbol }], "removed", `${v}[${symbol}]`))}}`);
331
391
  }
392
+ this.push("}");
332
393
  return;
333
394
  }
334
- case "sub":
335
- this.emitDelegate(node, v, segs);
336
- return;
337
395
  case "union": {
338
396
  const failure = node.members.some(canRefineUnionFailure)
339
- ? `return UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))})`
340
- : this.fail(segs, expectedOf(node), failureData);
341
- const literals = node.members.filter(isPrimitiveLiteral);
342
- if (literals.length === node.members.length && literals.length >= 4) {
343
- const cases = literals.map(member => `case ${this.lit(member.v)}:`).join("");
344
- this.push(`switch(${v}){${cases}break;default:${failure};}`);
397
+ ? `UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))})`
398
+ : this.error(segs, expectedOf(node), failureData);
399
+ this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, failure)}}`);
400
+ return;
401
+ }
402
+ case "string": {
403
+ this.push(`if(typeof ${v}!=="string"){${this.appendError(errors, this.error(segs, "a string", failureData))}}`);
404
+ if (node.min !== undefined) {
405
+ this.push(`else if(${v}.length<${node.min}){${this.appendError(errors, this.error(segs, `at least length ${node.min}`, `${v}.length`))}}`);
345
406
  }
346
- else {
347
- this.push(`if(!(${this.predicate(node, v)}))${failure};`);
407
+ if (node.max !== undefined) {
408
+ this.push(`else if(${v}.length>${node.max}){${this.appendError(errors, this.error(segs, `at most length ${node.max}`, `${v}.length`))}}`);
348
409
  }
410
+ if (node.url) {
411
+ this.push(`else if(!URL.canParse(${v})){${this.appendError(errors, this.error(segs, "a URL string", failureData))}}`);
412
+ }
413
+ return;
414
+ }
415
+ case "number": {
416
+ this.push(`if(typeof ${v}!=="number"||!Number.isFinite(${v})){${this.appendError(errors, this.error(segs, node.int ? "an integer" : "a number", failureData))}}else{`);
417
+ if (node.int) {
418
+ this.push(`if(!Number.isInteger(${v})){${this.appendError(errors, this.error(segs, "an integer", failureData))}}`);
419
+ }
420
+ if (node.divisor !== undefined) {
421
+ this.push(`if(${v}%${node.divisor}!==0){${this.appendError(errors, this.error(segs, `a number divisible by ${node.divisor}`, failureData))}}`);
422
+ }
423
+ if (node.min !== undefined) {
424
+ const expected = node.min === 0
425
+ ? node.xmin
426
+ ? "positive"
427
+ : "non-negative"
428
+ : `a number ${node.xmin ? "more than" : "at least"} ${node.min}`;
429
+ this.push(`if(${v}${node.xmin ? "<=" : "<"}${node.min}){${this.appendError(errors, this.error(segs, expected, failureData))}}`);
430
+ }
431
+ if (node.max !== undefined) {
432
+ const expected = node.max === 0
433
+ ? node.xmax
434
+ ? "negative"
435
+ : "non-positive"
436
+ : `a number ${node.xmax ? "less than" : "at most"} ${node.max}`;
437
+ this.push(`if(${v}${node.xmax ? ">=" : ">"}${node.max}){${this.appendError(errors, this.error(segs, expected, failureData))}}`);
438
+ }
439
+ this.push("}");
349
440
  return;
350
441
  }
442
+ case "lit":
443
+ if ((node.v !== null && typeof node.v === "object" && !(node.v instanceof Date)) ||
444
+ typeof node.v === "function") {
445
+ this.emitCollectDelegate(node, v, segs, errors);
446
+ }
447
+ else {
448
+ this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, this.error(segs, expectedOf(node), failureData))}}`);
449
+ }
450
+ return;
451
+ case "intersection":
452
+ case "sub":
453
+ this.emitCollectDelegate(node, v, segs, errors);
454
+ return;
351
455
  case "null":
352
456
  case "undefined":
353
457
  case "boolean":
@@ -355,53 +459,87 @@ class Builder {
355
459
  case "symbol":
356
460
  case "never":
357
461
  case "anyobject":
358
- case "lit":
359
- case "string":
360
- case "number":
361
462
  case "instance":
362
- this.push(`if(!(${this.predicate(node, v)}))${this.fail(segs, expectedOf(node), failureData)};`);
363
- return;
364
- case "refine": {
365
- this.emitCheck(node.base, v, segs, failureData);
366
- const failure = this.fail(segs, node.expected, v);
367
- this.push(`try{if(!${this.ref(node.pred)}(${v}))${failure};}catch{${failure};}`);
368
- return;
369
- }
370
- case "intersection":
371
- for (const member of node.members)
372
- this.emitCheck(member, v, segs, failureData);
463
+ this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, this.error(segs, expectedOf(node), failureData))}}`);
373
464
  return;
374
465
  default:
375
- this.emitDelegate(node, v, segs);
466
+ this.emitCollectDelegate(node, v, segs, errors);
467
+ }
468
+ }
469
+ emitTupleShape(node, v, segs, errors, brk, failureData) {
470
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}break ${brk};}`);
471
+ const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
472
+ const minimum = requiredPrefix + node.postfix.length;
473
+ if (minimum > 0) {
474
+ this.push(`if(${v}.length<${minimum}){${this.appendError(errors, this.error(segs, `an array of at least length ${minimum}`, failureData))}break ${brk};}`);
475
+ }
476
+ if (node.variadic === undefined) {
477
+ const maximum = node.prefix.length + node.postfix.length;
478
+ this.push(`if(${v}.length>${maximum}){${this.appendError(errors, this.error(segs, `an array of at most length ${maximum}`, failureData))}break ${brk};}`);
479
+ }
480
+ let postfixStart = `${v}.length`;
481
+ if (node.postfix.length > 0) {
482
+ postfixStart = this.next("p");
483
+ this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
484
+ }
485
+ let prefixCount = String(node.prefix.length);
486
+ if (requiredPrefix !== node.prefix.length) {
487
+ prefixCount = this.next("n");
488
+ this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
489
+ }
490
+ return { postfixStart, prefixCount, requiredPrefix };
491
+ }
492
+ /** Fill `target` with a validated default (factory output revalidated per call). */
493
+ emitDefaultFill(val, def, isFactory, target, segs, errors) {
494
+ if (isFactory && typeof def === "function") {
495
+ const candidate = this.next("d");
496
+ const resolved = this.next("t");
497
+ const label = this.next("L");
498
+ this.push(`const ${candidate}=${this.ref(def)}();let ${resolved};${label}:{`);
499
+ this.emitCollectProduce(val, candidate, segs, resolved, errors, label);
500
+ this.push(`${target}=${resolved};}`);
501
+ }
502
+ else {
503
+ // Static defaults were prevalidated at construction; MD clones
504
+ // mutable payloads so callers cannot alias the schema's copy.
505
+ this.push(`${target}=${litSource(def) ?? `MD(${this.ref(def)})`};`);
376
506
  }
377
507
  }
378
508
  /**
379
509
  * Validate `v` against a morphing subtree and assign the produced output
380
- * to `out` (an already-declared `let`).
510
+ * to `out` (an already-declared `let`). Failures append to `errors` and
511
+ * `break ${brk}` (skipping the output assignment), mirroring interp: an
512
+ * error in one child never suppresses sibling validation or morphs.
381
513
  */
382
- emitProduce(node, v, segs, out, failureData = v) {
514
+ emitCollectProduce(node, v, segs, out, errors, brk, failureData = v) {
515
+ if (node.cfg !== undefined || node.k === "refine") {
516
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
517
+ return;
518
+ }
383
519
  if (!hasMorph(node)) {
384
- this.emitCheck(node, v, segs, failureData);
520
+ this.emitCollectCheck(node, v, segs, errors, failureData);
385
521
  this.push(`${out}=${v};`);
386
522
  return;
387
523
  }
388
524
  switch (node.k) {
389
525
  case "sub":
390
- this.emitDelegate(node, v, segs, out);
526
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
391
527
  return;
392
528
  case "morph": {
393
529
  const input = this.next("t");
394
530
  this.push(`let ${input};`);
395
- this.emitProduce(node.input, v, segs, input, failureData);
531
+ const mark = this.markErrors(errors);
532
+ this.emitCollectProduce(node.input, v, segs, input, errors, brk, failureData);
533
+ this.guardGrowth(errors, mark, brk);
396
534
  const context = this.next("c");
397
535
  const result = this.next("r");
398
536
  this.push(`const ${context}=new MC(${this.storedPathExpr(segs)},${input});`);
399
537
  this.push(`const ${result}=${this.ref(node.fn)}(${input},${context});`);
400
- this.push(`if(${result} instanceof AE)return ${result};`);
538
+ this.push(`if(${result} instanceof AE){${this.appendError(errors, result)}break ${brk};}`);
401
539
  if (node.out === undefined)
402
540
  this.push(`${out}=${result};`);
403
541
  else
404
- this.emitProduce(node.out, result, segs, out);
542
+ this.emitCollectProduce(node.out, result, segs, out, errors, brk);
405
543
  return;
406
544
  }
407
545
  case "alias": {
@@ -411,12 +549,12 @@ class Builder {
411
549
  this.#activeAliases = active;
412
550
  }
413
551
  if (active.has(node)) {
414
- this.emitDelegate(node, v, segs, out);
552
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
415
553
  return;
416
554
  }
417
555
  active.add(node);
418
556
  try {
419
- this.emitProduce(node.resolve(), v, segs, out, failureData);
557
+ this.emitCollectProduce(node.resolve(), v, segs, out, errors, brk, failureData);
420
558
  }
421
559
  finally {
422
560
  active.delete(node);
@@ -442,47 +580,58 @@ class Builder {
442
580
  }
443
581
  }
444
582
  this.push("}");
445
- this.push(`if(!${ok})return UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))});`);
583
+ const failure = `UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))})`;
584
+ this.push(`if(!${ok}){${this.appendError(errors, failure)}break ${brk};}`);
446
585
  return;
447
586
  }
448
587
  case "array": {
449
- let head = `Array.isArray(${v})`;
450
- if (node.min !== undefined)
451
- head += `&&${v}.length>=${node.min}`;
452
- if (node.max !== undefined)
453
- head += `&&${v}.length<=${node.max}`;
454
- this.push(`if(!(${head}))${this.fail(segs, expectedOf(node), failureData)};`);
455
- const arr = this.next("a");
456
- const i = this.next("i");
457
- const x = this.next("x");
458
- const el = this.next("t");
459
- this.push(`const ${arr}=new Array(${v}.length);`);
460
- this.push(`for(let ${i}=0;${i}<${v}.length;${i}++){const ${x}=${v}[${i}];let ${el};`);
461
- this.emitProduce(node.el, x, [...segs, { d: i }], el);
462
- this.push(`${arr}[${i}]=${el};}`);
463
- this.push(`${out}=${arr};`);
588
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}break ${brk};}`);
589
+ if (node.min !== undefined) {
590
+ this.push(`if(${v}.length<${node.min}){${this.appendError(errors, this.error(segs, `at least length ${node.min}`, `${v}.length`))}break ${brk};}`);
591
+ }
592
+ if (node.max !== undefined) {
593
+ this.push(`if(${v}.length>${node.max}){${this.appendError(errors, this.error(segs, `at most length ${node.max}`, `${v}.length`))}break ${brk};}`);
594
+ }
595
+ const array = this.next("a");
596
+ const index = this.next("i");
597
+ const input = this.next("x");
598
+ const element = this.next("t");
599
+ const label = this.next("L");
600
+ this.push(`const ${array}=new Array(${v}.length);`);
601
+ this.push(`for(let ${index}=0;${index}<${v}.length;${index}++){const ${input}=${v}[${index}];let ${element};${label}:{`);
602
+ this.emitCollectProduce(node.el, input, [...segs, { d: index }], element, errors, label);
603
+ this.push(`${array}[${index}]=${element};}}`);
604
+ this.push(`${out}=${array};`);
464
605
  return;
465
606
  }
466
607
  case "tuple": {
467
- const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(node, v, segs, failureData);
608
+ const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(node, v, segs, errors, brk, failureData);
468
609
  const tuple = this.next("a");
469
610
  this.push(`const ${tuple}=[...${v}];`);
470
611
  for (let index = 0; index < node.prefix.length; index++) {
471
612
  const item = node.prefix[index];
613
+ const itemSegs = [...segs, { s: index }];
472
614
  const input = `${v}[${index}]`;
473
615
  const output = `${tuple}[${index}]`;
616
+ const label = this.next("L");
474
617
  if (index >= requiredPrefix)
475
618
  this.push(`if(${index}<${prefixCount}){`);
476
- if (hasMorph(item.val))
477
- this.emitProduce(item.val, input, [...segs, { d: String(index) }], output);
619
+ this.push(`${label}:{`);
620
+ if (hasMorph(item.val)) {
621
+ const temporary = this.next("t");
622
+ this.push(`let ${temporary};`);
623
+ this.emitCollectProduce(item.val, input, itemSegs, temporary, errors, label);
624
+ this.push(`${output}=${temporary};`);
625
+ }
478
626
  else {
479
- this.emitCheck(item.val, input, [...segs, { d: String(index) }]);
480
- this.push(`${output}=${input};`);
627
+ this.emitCollectCheck(item.val, input, itemSegs, errors);
481
628
  }
629
+ this.push("}");
482
630
  if (index >= requiredPrefix) {
483
631
  if (item.hasDefault) {
484
- const defaultValue = item.defFactory ? `${this.ref(item.def)}()` : this.lit(item.def);
485
- this.push(`}else{${output}=${defaultValue};}`);
632
+ this.push("}else{");
633
+ this.emitDefaultFill(item.val, item.def, item.defFactory === true, output, itemSegs, errors);
634
+ this.push("}");
486
635
  }
487
636
  else {
488
637
  this.push("}");
@@ -492,125 +641,142 @@ class Builder {
492
641
  if (node.variadic !== undefined) {
493
642
  const index = this.next("i");
494
643
  const input = this.next("x");
495
- this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){const ${input}=${v}[${index}];`);
644
+ const label = this.next("L");
645
+ this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){const ${input}=${v}[${index}];${label}:{`);
496
646
  if (hasMorph(node.variadic)) {
497
- this.emitProduce(node.variadic, input, [...segs, { d: index }], `${tuple}[${index}]`);
647
+ const temporary = this.next("t");
648
+ this.push(`let ${temporary};`);
649
+ this.emitCollectProduce(node.variadic, input, [...segs, { d: index }], temporary, errors, label);
650
+ this.push(`${tuple}[${index}]=${temporary};`);
498
651
  }
499
652
  else {
500
- this.emitCheck(node.variadic, input, [...segs, { d: index }]);
501
- this.push(`${tuple}[${index}]=${input};`);
653
+ this.emitCollectCheck(node.variadic, input, [...segs, { d: index }], errors);
502
654
  }
503
- this.push("}");
655
+ this.push("}}");
504
656
  }
505
657
  for (let index = 0; index < node.postfix.length; index++) {
506
658
  const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
507
659
  const input = `${v}[${inputIndex}]`;
508
- const output = `${tuple}[${inputIndex}]`;
509
660
  const item = node.postfix[index];
510
- if (hasMorph(item))
511
- this.emitProduce(item, input, [...segs, { d: inputIndex }], output);
661
+ const label = this.next("L");
662
+ this.push(`${label}:{`);
663
+ if (hasMorph(item)) {
664
+ const temporary = this.next("t");
665
+ this.push(`let ${temporary};`);
666
+ this.emitCollectProduce(item, input, [...segs, { d: inputIndex }], temporary, errors, label);
667
+ this.push(`${tuple}[${inputIndex}]=${temporary};`);
668
+ }
512
669
  else {
513
- this.emitCheck(item, input, [...segs, { d: inputIndex }]);
514
- this.push(`${output}=${input};`);
670
+ this.emitCollectCheck(item, input, [...segs, { d: inputIndex }], errors);
515
671
  }
672
+ this.push("}");
516
673
  }
517
674
  this.push(`${out}=${tuple};`);
518
675
  return;
519
676
  }
520
677
  case "object": {
521
- this.push(`if(typeof ${v}!=="object"||${v}===null||Array.isArray(${v}))${this.fail(segs, "an object", failureData)};`);
522
- const o = this.next("o");
523
- const fresh = node.extras !== "keep" && !node.index;
524
- this.push(fresh ? `const ${o}={};` : `const ${o}={...${v}};`);
525
- for (const p of node.props) {
526
- const present = `${JSON.stringify(p.key)} in ${v}`;
527
- const propSegs = [...segs, { s: p.key }];
528
- const av = access(v, p.key);
529
- const ao = access(o, p.key);
530
- const morphChild = hasMorph(p.val);
531
- const missing = [];
532
- if (p.hasDefault) {
533
- const dflt = p.defFactory ? `${this.ref(p.def)}()` : this.lit(p.def);
534
- missing.push(`${ao}=${dflt};`);
678
+ if (node.patternIndexes !== undefined ||
679
+ node.symbolIndex !== undefined ||
680
+ node.props.some(prop => typeof prop.key === "symbol")) {
681
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
682
+ return;
683
+ }
684
+ this.push(`if(typeof ${v}!=="object"||${v}===null){${this.appendError(errors, this.error(segs, "an object", failureData))}break ${brk};}`);
685
+ const object = this.next("o");
686
+ const fresh = node.extras === "delete" && node.index === undefined;
687
+ this.push(fresh ? `const ${object}={};` : `const ${object}={...${v}};`);
688
+ for (const prop of node.props) {
689
+ const present = `${this.lit(prop.key)} in ${v}`;
690
+ const propSegs = [...segs, { s: prop.key }];
691
+ const input = this.access(v, prop.key);
692
+ const output = this.access(object, prop.key);
693
+ const label = this.next("L");
694
+ this.push(`if(!(${present})){`);
695
+ if (prop.hasDefault) {
696
+ this.emitDefaultFill(prop.val, prop.def, prop.defFactory === true, output, propSegs, errors);
535
697
  }
536
- else if (!p.opt) {
537
- missing.push(`${this.fail(propSegs, expectedOf(p.val), "M")};`);
698
+ else if (!prop.opt) {
699
+ this.push(this.appendError(errors, this.error(propSegs, expectedOf(prop.val), "M")));
538
700
  }
539
- this.push(`if(!(${present})){${missing.join("")}}else{`);
540
- if (morphChild) {
541
- const t = this.next("t");
542
- this.push(`let ${t};`);
543
- this.emitProduce(p.val, av, propSegs, t);
544
- this.push(`${ao}=${t};`);
701
+ this.push(`}else{${label}:{`);
702
+ if (hasMorph(prop.val)) {
703
+ const temporary = this.next("t");
704
+ this.push(`let ${temporary};`);
705
+ this.emitCollectProduce(prop.val, input, propSegs, temporary, errors, label);
706
+ this.push(`${output}=${temporary};`);
545
707
  }
546
708
  else {
547
- this.emitCheck(p.val, av, propSegs);
709
+ this.emitCollectCheck(prop.val, input, propSegs, errors);
548
710
  if (fresh)
549
- this.push(`${ao}=${av};`);
711
+ this.push(`${output}=${input};`);
550
712
  }
551
- this.push("}");
713
+ this.push("}}");
552
714
  }
553
- if (node.index) {
554
- const k = this.next("k");
555
- this.push(`for(const ${k} in ${v})if(own.call(${v},${k})){`);
715
+ if (node.index !== undefined) {
716
+ const key = this.next("k");
717
+ const label = this.next("L");
718
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})){${label}:{`);
556
719
  if (hasMorph(node.index)) {
557
- const t = this.next("t");
558
- this.push(`let ${t};`);
559
- this.emitProduce(node.index, `${v}[${k}]`, [...segs, { d: k }], t);
560
- this.push(`${o}[${k}]=${t};`);
720
+ const temporary = this.next("t");
721
+ this.push(`let ${temporary};`);
722
+ this.emitCollectProduce(node.index, `${v}[${key}]`, [...segs, { d: key }], temporary, errors, label);
723
+ this.push(`${object}[${key}]=${temporary};`);
561
724
  }
562
725
  else {
563
- this.emitCheck(node.index, `${v}[${k}]`, [...segs, { d: k }]);
726
+ this.emitCollectCheck(node.index, `${v}[${key}]`, [...segs, { d: key }], errors);
564
727
  }
565
- this.push("}");
728
+ this.push("}}");
566
729
  }
567
730
  else if (node.extras === "reject") {
568
- const k = this.next("k");
569
- this.push(`for(const ${k} in ${v})if(own.call(${v},${k})&&!(${this.declaredCheck(node.props, k)}))${this.fail([...segs, { d: k }], "removed (undeclared key)", `${v}[${k}]`)};`);
731
+ const key = this.next("k");
732
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})&&!(${this.declaredCheck(node.props, key)})){${this.appendError(errors, this.error([...segs, { d: key }], "removed", `${v}[${key}]`))}}`);
570
733
  }
571
- this.push(`${out}=${o};`);
572
- return;
573
- }
574
- case "refine": {
575
- const refined = this.next("t");
576
- this.push(`let ${refined};`);
577
- this.emitProduce(node.base, v, segs, refined, failureData);
578
- const failure = this.fail(segs, node.expected, refined);
579
- this.push(`try{if(!${this.ref(node.pred)}(${refined}))${failure};}catch{${failure};}`);
580
- this.push(`${out}=${refined};`);
734
+ if (node.extras === "reject") {
735
+ const symbol = this.next("s");
736
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.declaredCheck(node.props, symbol)})){${this.appendError(errors, this.error([...segs, { d: symbol }], "removed", `${v}[${symbol}]`))}}`);
737
+ }
738
+ this.push(`${out}=${object};`);
581
739
  return;
582
740
  }
583
741
  case "intersection": {
584
742
  const current = this.next("t");
585
743
  this.push(`let ${current}=${v};`);
586
- for (const member of node.members) {
744
+ const mark = this.markErrors(errors);
745
+ for (let index = 0; index < node.members.length; index++) {
746
+ if (index > 0)
747
+ this.guardGrowth(errors, mark, brk);
748
+ const member = node.members[index];
587
749
  if (hasMorph(member))
588
- this.emitProduce(member, current, segs, current);
750
+ this.emitCollectProduce(member, current, segs, current, errors, brk);
589
751
  else
590
- this.emitCheck(member, current, segs);
752
+ this.emitCollectCheck(member, current, segs, errors);
591
753
  }
592
754
  this.push(`${out}=${current};`);
593
755
  return;
594
756
  }
595
757
  default:
596
- this.emitDelegate(node, v, segs, out);
758
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
597
759
  }
598
760
  }
599
761
  build(ir) {
762
+ const errors = this.next("e");
600
763
  let ret;
601
764
  if (hasMorph(ir)) {
602
- this.push("let o;");
603
- // body emitted below needs `o` declared first, so splice ordering:
604
- this.emitProduce(ir, "v", [], "o");
765
+ const label = this.next("L");
766
+ this.push(`let ${errors};let o;${label}:{`);
767
+ this.emitCollectProduce(ir, "v", [], "o", errors, label);
768
+ this.push("}");
605
769
  ret = "o";
606
770
  }
607
771
  else {
608
- this.emitCheck(ir, "v", []);
772
+ this.push(`let ${errors};`);
773
+ this.emitCollectCheck(ir, "v", [], errors);
609
774
  ret = "v";
610
775
  }
776
+ this.push(`if(${errors}!==undefined)return ${errors};`);
611
777
  const src = `return function(v){${this.#lines.join("")}return ${ret}}`;
612
- const make = new Function("R", "AE", "M", "PF", "UF", "MC", "own", src);
613
- return make(this.#refs, OmpErrors, MISSING, prefixErrors, unionFail, CompiledMorphContext, own);
778
+ const make = new Function("R", "AE", "M", "PF", "UF", "MC", "own", "MD", src);
779
+ return make(this.#refs, OmpErrors, MISSING, prefixErrors, unionFail, CompiledMorphContext, own, materializeDefault);
614
780
  }
615
781
  emitAllows(node, v) {
616
782
  switch (node.k) {
@@ -629,11 +795,11 @@ class Builder {
629
795
  }
630
796
  case "object": {
631
797
  const object = this.next("o");
632
- this.push(`const ${object}=${v};if(typeof ${object}!=="object"||${object}===null||Array.isArray(${object}))return false;`);
798
+ this.push(`const ${object}=${v};if(typeof ${object}!=="object"||${object}===null)return false;`);
633
799
  for (const prop of node.props) {
634
800
  const value = this.next("p");
635
- const present = `${JSON.stringify(prop.key)} in ${object}`;
636
- this.push(`const ${value}=${access(object, prop.key)};`);
801
+ const present = `${this.lit(prop.key)} in ${object}`;
802
+ this.push(`const ${value}=${this.access(object, prop.key)};`);
637
803
  if (prop.opt || prop.hasDefault) {
638
804
  if (rejectsUndefined(prop.val)) {
639
805
  this.push(`if(${value}!==undefined){`);
@@ -652,15 +818,33 @@ class Builder {
652
818
  this.emitAllows(prop.val, value);
653
819
  }
654
820
  }
655
- if (node.index) {
656
- const key = this.next("k");
657
- this.push(`for(const ${key} in ${object}){if(!own.call(${object},${key}))continue;`);
658
- this.emitAllows(node.index, `${object}[${key}]`);
821
+ const stringKey = this.next("k");
822
+ if (node.index !== undefined) {
823
+ this.push(`for(const ${stringKey} in ${object}){if(!own.call(${object},${stringKey}))continue;`);
824
+ this.emitAllows(node.index, `${object}[${stringKey}]`);
659
825
  this.push("}");
660
826
  }
661
- else if (node.extras === "reject") {
662
- const key = this.next("k");
663
- this.push(`for(const ${key} in ${object})if(own.call(${object},${key})&&!(${this.declaredCheck(node.props, key)}))return false;`);
827
+ if (node.patternIndexes !== undefined) {
828
+ for (const pattern of node.patternIndexes) {
829
+ this.push(`for(const ${stringKey} in ${object})if(own.call(${object},${stringKey})&&(${this.predicate(pattern.key, stringKey)})&&!(${this.predicate(pattern.val, `${object}[${stringKey}]`)}))return false;`);
830
+ }
831
+ }
832
+ if (node.symbolIndex !== undefined) {
833
+ const symbol = this.next("s");
834
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${object})){if(!Object.prototype.propertyIsEnumerable.call(${object},${symbol}))continue;`);
835
+ this.emitAllows(node.symbolIndex, `${object}[${symbol}]`);
836
+ this.push("}");
837
+ }
838
+ if (node.extras === "reject") {
839
+ const patternMatch = node.patternIndexes?.map(pattern => `(${this.predicate(pattern.key, stringKey)})`).join("||") ??
840
+ "false";
841
+ if (node.index === undefined) {
842
+ this.push(`for(const ${stringKey} in ${object})if(own.call(${object},${stringKey})&&!(${this.declaredCheck(node.props, stringKey)})&&!(${patternMatch}))return false;`);
843
+ }
844
+ if (node.symbolIndex === undefined) {
845
+ const symbol = this.next("s");
846
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${object}))if(Object.prototype.propertyIsEnumerable.call(${object},${symbol})&&!(${this.declaredCheck(node.props, symbol)}))return false;`);
847
+ }
664
848
  }
665
849
  return;
666
850
  }
@@ -703,7 +887,7 @@ function boundWalk(node) {
703
887
  const tagged = node;
704
888
  let fn = tagged[kWalk];
705
889
  if (!fn) {
706
- fn = (value) => walk(node, value);
890
+ fn = (value, path) => walk(node, value, path);
707
891
  tagged[kWalk] = fn;
708
892
  }
709
893
  return fn;
@@ -716,20 +900,31 @@ const allowsCache = new WeakMap();
716
900
  /** Compile `ir` into a specialized validator. */
717
901
  export function compile(ir) {
718
902
  const root = resolvedRoot(ir);
719
- let validator = compiledCache.get(root);
903
+ const validator = compiledCache.get(root);
720
904
  if (validator === undefined) {
721
- validator = new Builder().build(root);
722
- compiledCache.set(root, validator);
905
+ // Publish a deferred wrapper before building: recursive schemas re-enter
906
+ // compile() for the same root mid-build (e.g. an alias element inside an
907
+ // array), and each re-entry must reuse this build instead of starting a
908
+ // fresh one forever. The wrapper resolves to the built validator by call
909
+ // time; the interpreter is a safety net that never triggers post-build.
910
+ let built;
911
+ compiledCache.set(root, value => (built === undefined ? walk(root, value) : built(value)));
912
+ built = new Builder().build(root);
913
+ compiledCache.set(root, built);
914
+ return built;
723
915
  }
724
916
  return validator;
725
917
  }
726
918
  /** Compile `ir` into an allocation-free boolean validator. */
727
919
  export function compileAllows(ir) {
728
920
  const root = resolvedRoot(ir);
729
- let validator = allowsCache.get(root);
921
+ const validator = allowsCache.get(root);
730
922
  if (validator === undefined) {
731
- validator = new Builder().buildAllows(root);
732
- allowsCache.set(root, validator);
923
+ let built;
924
+ allowsCache.set(root, ((value) => built === undefined ? !(walk(root, value) instanceof OmpErrors) : built(value)));
925
+ built = new Builder().buildAllows(root);
926
+ allowsCache.set(root, built);
927
+ return built;
733
928
  }
734
929
  return validator;
735
930
  }
@@ -739,9 +934,9 @@ export function compileToSource(ir) {
739
934
  const builder = new Builder();
740
935
  if (hasMorph(root)) {
741
936
  builder.push("let o;");
742
- builder.emitProduce(root, "v", [], "o");
937
+ builder.emitCollectProduce(root, "v", [], "o", "e", "L0");
743
938
  return `function(v){/* refs elided */return o}`;
744
939
  }
745
- builder.emitCheck(root, "v", []);
940
+ builder.emitCollectCheck(root, "v", [], "e");
746
941
  return `function(v){/* refs elided */return v}`;
747
942
  }