Skip to main content

shape/
accepts.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3
4use indexmap::IndexSet;
5
6use super::Shape;
7use super::ShapeCase;
8
9/// The `Shape::validate*` methods return a vector of `ShapeMismatch` errors
10/// that is non-empty when validation fails.
11///
12/// Each `ShapeMismatch` error contains the `expected` shape and the `received`
13/// shape, whose metadata (e.g. shape.locations) provide context for the
14/// mismatch.
15///
16/// Conveniently, the `expected`/`received` terminology works for both the
17/// `expected.accepts(received)` and `received.satisfies(expected)` directions
18/// of comparison, so `ShapeMismatch` does not encode whether the `accepts` or
19/// `satisfies` was used internally (though `validate` is similar to `accepts`
20/// in its order of arguments).
21///
22/// Once issue #5 is implemented, you'll be able to obtain source location
23/// information from the `expected` and `received` shapes (instead of adding
24/// additional fields to the `ShapeMismatch` struct).
25#[derive(Debug, PartialEq, Eq, Clone, Hash)]
26pub struct ShapeMismatch {
27    pub expected: Shape,
28    pub received: Shape,
29    pub causes: Vec<ShapeMismatch>,
30}
31
32/// Answers already computed during one top-level [`Shape::validate`] call, so
33/// a question the search asks twice is answered once.
34///
35/// Keyed structurally, on the pair of shapes and the split depth the answer was
36/// computed at. Structural keying is affordable only because each case stores
37/// its hash (see `CaseWithHash`): hashing a key is two field reads rather than
38/// two walks, and the walk is the work the memo exists to avoid. Address
39/// keying was the alternative and is strictly weaker -- it hits only when the
40/// repeated question is a repetition of the *same* allocation, and misses every
41/// shape that was rebuilt rather than cloned, which is common enough to have
42/// turned a measured win into overhead.
43///
44/// The depth belongs in the key because the bound is a path property: an answer
45/// computed with one distribution remaining is not necessarily the answer with
46/// three. Keyed that precisely, a hit is exactly what the search would have
47/// recomputed, so the memo can change how long a comparison takes and never
48/// what it answers. That is the difference between this and the comparison
49/// meter the crate used to carry, whose shared state was itself part of the
50/// answer.
51///
52/// "What it answers" is the verdict. The `ShapeMismatch` a hit returns is a
53/// different matter, because `PartialEq for Shape` ignores metadata: two
54/// occurrences of the same shapes at different source locations share an
55/// entry, and the entry holds whichever pair asked first. The outer frame of a
56/// hit is therefore rebuilt from the shapes in hand (see
57/// [`Shape::validate_within`]), so `expected` and `received` always carry the
58/// locations of the comparison that was actually made. The `causes` below
59/// that frame are not rebuilt: a location one level down on a memo hit may
60/// belong to another structurally identical occurrence of the same pair.
61/// Re-anchoring the whole tree is well defined for the same reason the hit is
62/// sound, and can be done if a consumer comes to need it.
63///
64/// [`MAX_SPLIT_DEPTH`] leans on this memo. Its value of twelve is affordable
65/// only because the member loop's repeated union-against-subtree questions are
66/// answered once; without the memo the nested doomed families take minutes
67/// rather than milliseconds, and the tests that pin the depth cap do not
68/// finish. Weakening the memo therefore hangs the suite rather than failing
69/// it, which is worth knowing before touching either.
70///
71/// Equality is verified rather than assumed: a bucket collision falls through
72/// to `PartialEq for Shape`, which short-circuits on pointer equality before
73/// walking. Trusting the 64-bit hash alone would be faster and is very
74/// probably safe at the few thousand keys one comparison generates, but a type
75/// system should not answer a subtyping question wrongly with any probability
76/// at all.
77///
78/// The map lives on the stack of one `validate` call and is dropped with it,
79/// so nothing is retained between comparisons. It is wrapped in an `Option` so
80/// that a comparison which never meets a union constructs nothing but a null:
81/// an empty `HashMap` does not allocate, but building one is not free either,
82/// and `validate` is called often enough on trivially cheap pairs that a fixed
83/// cost there is measurable (17ns per call when the map was built eagerly).
84type SplitMemo = RefCell<Option<HashMap<MemoKey, Option<ShapeMismatch>>>>;
85
86/// Expected shape, received shape, and the number of distributions already
87/// taken on the path to the comparison.
88type MemoKey = (Shape, Shape, usize);
89
90/// The (expected, received) pairs whose comparison is already in progress on
91/// the path from the top of one `validate` call down to here, recorded at
92/// every hop that resolves a bound [`ShapeCase::Name`] reference: the two
93/// `::Name` arms of `validate_uncached` and the union member deferral loop.
94///
95/// Name resolution is the only recursion in `validate` that does not descend
96/// into a strictly smaller shape: every other arm walks a finite tree, so an
97/// infinite descent must pass through a guarded resolution hop on every lap,
98/// and a lap that arrives at a pair already on this path will repeat
99/// deterministically forever. When such a revisit is detected, the pair is
100/// *assumed* valid and the answer is `None` -- the coinductive hypothesis,
101/// which the enclosing occurrence of the same comparison is still in the
102/// process of discharging. A descent that would find a mismatch finds it
103/// before the pair can recur, since everything after the recurrence is an
104/// exact repetition of what already ran, so the hypothesis never hides
105/// evidence that exists.
106///
107/// What a hypothesis must not do is leak. An answer computed while a
108/// hypothesis is live rests on it, and the hypothesis holds only until the
109/// frame that owns its pair completes -- which may refute it. [`SplitMemo`]
110/// answers questions for the rest of the `validate` call, including from
111/// contexts where a given hypothesis had already been refuted or never
112/// applied, so an answer computed under a live hypothesis must never be
113/// memoized. `lowest_assumed` makes that decidable: it is the smallest path
114/// index of any hypothesis assumed anywhere in the subtree currently being
115/// computed, and because a hypothesis about a pair is discharged when that
116/// pair's own frame pops, it is always either `usize::MAX` (nothing assumed)
117/// or the index of a pair still on the path, meaning the answer in progress
118/// still rests on it. `starved` plays the same role for depth-cap answers
119/// (see [`MAX_VALIDATE_DEPTH`]), which are approximations tuned to the
120/// budget that produced them.
121///
122/// The path holds the shapes themselves rather than their stored hashes:
123/// `PartialEq` compares the hashes first, so each probe of the stack costs
124/// two loads per entry, and keeping the whole pair means the answer never
125/// depends on a 64-bit collision going the right way. A `Vec` rather than a
126/// set because an empty `Vec` never allocates, and a comparison that never
127/// resolves a bound name -- the overwhelming majority -- pays nothing at
128/// all.
129struct Resolving {
130    path: Vec<(Shape, Shape)>,
131    /// Smallest `path` index of any hypothesis assumed in the subtree
132    /// currently being computed; `usize::MAX` when it has assumed none.
133    /// `validate_within` saves, resets, and merges this around each memoized
134    /// subtree, so at memo time it describes exactly that subtree.
135    lowest_assumed: usize,
136    /// Whether the depth cap fired anywhere in the subtree currently being
137    /// computed, saved and merged alongside `lowest_assumed`.
138    starved: bool,
139}
140
141impl Default for Resolving {
142    fn default() -> Self {
143        Resolving {
144            path: Vec::new(),
145            lowest_assumed: usize::MAX,
146            starved: false,
147        }
148    }
149}
150
151type ResolvingPairs = RefCell<Resolving>;
152
153/// Runs `f` with `pair` on the resolving path, or answers `None` -- the
154/// coinductive hypothesis -- if `pair` is already on it.
155///
156/// A hypothesis is scoped to the frame that owns its pair: it is discharged
157/// when that frame pops, because by then the pair's verdict stands on its
158/// own, the frame having either found real evidence or completed the
159/// coinductive proof. So a revisit only records the assumed pair's index in
160/// `lowest_assumed`, and when this frame pops, every hypothesis at its own
161/// index is discharged with it. Indices below it belong to pairs still on
162/// the path (stack discipline means nothing deeper survives to this point),
163/// so those stay live; indices above it cannot occur, because a deeper
164/// pair's pop has already discharged its own hypotheses by the time it
165/// merged its accounting upward.
166fn guarded(
167    resolving: &ResolvingPairs,
168    pair: (Shape, Shape),
169    f: impl FnOnce() -> Option<ShapeMismatch>,
170) -> Option<ShapeMismatch> {
171    let index = {
172        let mut r = resolving.borrow_mut();
173        if let Some(i) = r.path.iter().position(|p| *p == pair) {
174            r.lowest_assumed = r.lowest_assumed.min(i);
175            return None;
176        }
177        r.path.push(pair);
178        r.path.len() - 1
179    };
180    let mismatch = f();
181    let mut r = resolving.borrow_mut();
182    r.path.pop();
183    debug_assert_eq!(r.path.len(), index);
184    // Discharge hypotheses about this pair; keep any about pairs still on
185    // the path above.
186    if r.lowest_assumed >= index {
187        r.lowest_assumed = usize::MAX;
188    }
189    mismatch
190}
191
192/// The deepest `validate_within` recursion one comparison will perform before
193/// declining to look further, returning `None` -- no mismatch found -- for
194/// anything deeper.
195///
196/// This is stack-overflow insurance, not semantics. Every other bound in this
197/// file is a structural fact about the shapes, but this one exists because the
198/// recursion runs on the native call stack, which is finite and shared with
199/// whoever called `validate`. The budget counts `validate_within` frames, not
200/// semantic levels: one level of a shape costs one frame in a plain list,
201/// two in a record, and four to six when a bound `Name` resolves along the
202/// way, so 128 frames covers anywhere from ~128 levels of structural nesting
203/// down to ~25 levels of nullable-named-field chaining.
204///
205/// The value is measured, not guessed. In the debug profile on a 2MiB stack
206/// -- the smallest default stack in play, used by both Rust test threads and
207/// tokio workers -- descent overflows at roughly 300 frames for the fattest
208/// measured family (records chained through `One<Name, null>` fields, the
209/// connectors shape) and roughly 470 for the leanest (nested lists); the
210/// per-frame cost is dominated by this function's own locals and runs
211/// 4.5-9KB. 128 retains at least a 2.3x margin under the worst measurement,
212/// and release frames are several times smaller still. Fully validating a
213/// document at `serde_json`'s default 128-level nesting limit would take
214/// ~256 frames, which 2MiB debug stacks cannot afford; that gap is the
215/// known price of fail-open insurance, and closing it belongs to shrinking
216/// the frame, not raising the budget.
217///
218/// The failure direction at the cap is open, matching what the `::Name` arms
219/// already do for unbound names: a comparison too deep to finish is accepted
220/// rather than reported. A cap high enough to be unreachable in practice is
221/// what makes that acceptable -- it can only fire on inputs no honest schema
222/// or default-configured parser produces, where the alternative is a process
223/// abort. Because a starved answer is an approximation of what a
224/// better-funded comparison would compute, `depth_remaining` is not part of
225/// [`MemoKey`]; instead, starvation is tracked in [`Resolving`], and a
226/// starved answer is never memoized at all. The memo only ever serves what
227/// the search would recompute with the hypothesis slate clean and the budget
228/// intact.
229pub(crate) const MAX_VALIDATE_DEPTH: usize = 128;
230
231impl Shape {
232    /// Returns true if the other shape meets all the expectations of the self
233    /// shape. In set theory terms, the set of all values accepted by other is a
234    /// subset of the set of all values accepted by self.
235    #[must_use]
236    pub fn accepts(&self, other: &Shape) -> bool {
237        self.validate(other).is_none()
238    }
239
240    /// Returns true iff the given [`serde_json::Value`] satisfies self.
241    #[must_use]
242    pub fn accepts_json(&self, json: &serde_json::Value) -> bool {
243        self.accepts(&Shape::from_json(json))
244    }
245
246    /// Returns true iff the given [`serde_json_bytes::Value`] satisfies self.
247    pub fn accepts_json_bytes(&self, json: &serde_json_bytes::Value) -> bool {
248        self.accepts(&Shape::from_json_bytes(json))
249    }
250
251    /// `Shape::validate_json` is to `Shape::validate` as `Shape::accepts_json`
252    /// is to `Shape::accepts`.
253    #[must_use]
254    pub fn validate_json(&self, json: &serde_json::Value) -> Option<ShapeMismatch> {
255        self.validate(&Shape::from_json(json))
256    }
257
258    /// `Shape::validate_json_bytes` is to `Shape::validate` as
259    /// `Shape::accepts_json_bytes` is to `Shape::accepts`.
260    pub fn validate_json_bytes(&self, json: &serde_json_bytes::Value) -> Option<ShapeMismatch> {
261        self.validate(&Shape::from_json_bytes(json))
262    }
263
264    /// Returns `true` if the `self` shape meets all the expectations of the
265    /// `other` shape. In set theory terms, `self` satisfying `other` means the
266    /// set of all values accepted by `self` is a subset of the set of all
267    /// values accepted by `other`.
268    ///
269    /// The `satisfies` method is the inverse of the `accepts` method, in the
270    /// sense that `a.accepts(b)` is equivalent to `b.satisfies(a)`. For
271    /// historical reasons, the bulk of the `accepts`/`satisfies` logic happens
272    /// in the internal `validate` method, though I have since realized
273    /// the `accepts` direction generalizes a bit better to situations where
274    /// `other` is not a `Shape`, such as `Shape::accepts_json(&self, json:
275    /// &serde_json::Value)`.
276    #[must_use]
277    pub fn satisfies(&self, other: &Shape) -> bool {
278        other.validate(self).is_none()
279    }
280
281    /// Validates that all expectations of the `self` shape are met by the
282    /// `other` shape, erroring with a non-empty vector of `ShapeMismatch`
283    /// errors when validation fails.
284    #[must_use]
285    pub fn validate(&self, other: &Shape) -> Option<ShapeMismatch> {
286        // Nothing but a null until a union comparison needs somewhere to put an
287        // answer, so a comparison that never meets a union pays nothing here.
288        let memo = SplitMemo::default();
289        let resolving = ResolvingPairs::default();
290        self.validate_within(other, 0, MAX_VALIDATE_DEPTH, &memo, &resolving)
291    }
292
293    /// The body of [`Shape::validate`], carrying the union splitting search's
294    /// depth.
295    ///
296    /// `splits_taken` counts the union positions distributed along the path
297    /// from the top of this comparison down to here. Every recursion in the
298    /// body passes it along unchanged -- member loops, name resolution, and
299    /// each descent into a field or item -- and only the split recursion
300    /// itself increments it. Descending into a field therefore does not hand
301    /// back a fresh allowance, which is what makes [`MAX_SPLIT_DEPTH`] a bound
302    /// on the whole comparison rather than on each object level separately. A
303    /// per-level bound multiplies: with one union-typed field at each of d
304    /// levels, one level's worst case is paid d times over.
305    ///
306    /// It is a property of the path and never a ledger of work done. Sibling
307    /// comparisons do not spend each other's allowance, so the two fields of
308    /// an object are compared with the same one, and a pair of shapes reached
309    /// by the same number of distributions always answers the same way.
310    ///
311    /// `resolving` is the coinductive cycle guard for name resolution (see
312    /// [`Resolving`]). Like `splits_taken` it is a property of the path:
313    /// every recursion passes it along unchanged. The three hops that resolve
314    /// a bound name -- the two `ShapeCase::Name` arms and the union member
315    /// deferral loop -- push and probe its path through [`guarded`], and the
316    /// memo below consults its hypothesis accounting before inserting.
317    ///
318    /// `depth_remaining` is the stack-overflow insurance budget (see
319    /// [`MAX_VALIDATE_DEPTH`]). Each frame spends one unit on entry, so every
320    /// recursion passes the already-decremented value along unchanged.
321    #[must_use]
322    fn validate_within(
323        &self,
324        other: &Shape,
325        splits_taken: usize,
326        depth_remaining: usize,
327        memo: &SplitMemo,
328        resolving: &ResolvingPairs,
329    ) -> Option<ShapeMismatch> {
330        // Past the depth cap, decline to look further and accept: see
331        // MAX_VALIDATE_DEPTH for the measurement and the fail-open rationale.
332        // Mark the subtree starved so the answer is never memoized for a
333        // query holding more budget.
334        if depth_remaining == 0 {
335            resolving.borrow_mut().starved = true;
336            return None;
337        }
338        let depth_remaining = depth_remaining - 1;
339
340        // Only union comparisons are worth an entry. A union is the only
341        // expected shape whose comparison searches, and it is where a repeated
342        // question arrives: the fan-out that makes nesting expensive is the
343        // member loop asking the same union-versus-subtree question once per
344        // branch of every level above it. Everything else answers in time
345        // proportional to the shapes themselves and would spend more on
346        // bookkeeping than it saved.
347        //
348        // Restricting this further to comparisons inside an active search
349        // (splits_taken > 0) was measured. It leaves the allocation baseline
350        // untouched, but a union of records whose fields are wide unions never
351        // starts a search at all and so never gets an entry: that family goes
352        // from 4.6ms back to 402ms. The entries a top-level union comparison
353        // makes are the ones its own subtree comparisons hit.
354        if !matches!(self.case(), ShapeCase::One(_)) {
355            return self.validate_uncached(other, splits_taken, depth_remaining, memo, resolving);
356        }
357
358        let key: MemoKey = (self.clone(), other.clone(), splits_taken);
359        // The borrow ends with the statement, so the recursion below never runs
360        // while the map is borrowed.
361        if let Some(answer) = memo.borrow().as_ref().and_then(|map| map.get(&key)) {
362            // The key is structural and `PartialEq for Shape` ignores metadata,
363            // so the entry may have been computed for a different occurrence
364            // of these shapes, carrying that occurrence's source locations.
365            // The verdict is the same either way; the shapes on the mismatch
366            // are not, and carrying that metadata is what `ShapeMismatch` is
367            // for. Re-anchor the outer frame on the pair actually in hand.
368            // The `causes` beneath it are kept as computed for the first
369            // occurrence, so a location one level down may still belong to
370            // another structurally identical pair; see the note on
371            // `SplitMemo`.
372            return answer.as_ref().map(|mismatch| ShapeMismatch {
373                expected: self.clone(),
374                received: other.clone(),
375                causes: mismatch.causes.clone(),
376            });
377        }
378
379        // An answer that rests on a live hypothesis or on an exhausted depth
380        // budget is valid only in the context that produced it: serving it
381        // to another occurrence of the same key would hand over an
382        // assumption the asker did not make, or a leniency it did not need.
383        // Account for this subtree alone, and memoize only what the search
384        // would recompute anywhere. See Resolving.
385        let (saved_lowest, saved_starved) = {
386            let mut r = resolving.borrow_mut();
387            (
388                std::mem::replace(&mut r.lowest_assumed, usize::MAX),
389                std::mem::replace(&mut r.starved, false),
390            )
391        };
392        let answer = self.validate_uncached(other, splits_taken, depth_remaining, memo, resolving);
393        let mut r = resolving.borrow_mut();
394        if r.lowest_assumed == usize::MAX && !r.starved {
395            memo.borrow_mut()
396                .get_or_insert_with(HashMap::new)
397                .insert(key, answer.clone());
398        }
399        r.lowest_assumed = saved_lowest.min(r.lowest_assumed);
400        r.starved = saved_starved || r.starved;
401        answer
402    }
403
404    /// The uncached body of [`Shape::validate_within`]. Call that instead; this
405    /// exists only so the memo has a single place to wrap.
406    #[allow(clippy::too_many_lines)]
407    #[must_use]
408    fn validate_uncached(
409        &self,
410        other: &Shape,
411        splits_taken: usize,
412        depth_remaining: usize,
413        memo: &SplitMemo,
414        resolving: &ResolvingPairs,
415    ) -> Option<ShapeMismatch> {
416        // Since self.case and other.case are the only information that
417        // logically matters for validation, a shape accepts any shape whose
418        // case equals its own, and we can skip doing any actual validation
419        // work.
420        //
421        // Testing equality rather than pointer identity is what the stored
422        // case hash buys: unequal cases are separated by comparing two u64s,
423        // and equal ones are confirmed by `PartialEq for CaseWithHash`, which
424        // short circuits on the pointer before walking. Pointer identity alone
425        // caught only shapes that share an allocation, and misses the far more
426        // common case of a shape rebuilt rather than cloned.
427        if self.case == other.case {
428            return None;
429        }
430
431        // Helper closure that translates boolean results to vector results. The
432        // `path` and `mismatches` parameters are always the local parameter and
433        // variable of the same names from the outer scope, but passing them in
434        // this way simplifies ownership logic and strips mutability.
435        let report = |is_valid: bool, causes: Vec<ShapeMismatch>| -> Option<ShapeMismatch> {
436            if is_valid {
437                // Use the report closure only in situations where the truth of
438                // `is_valid` should hide any previous mismatches.
439                None
440            } else {
441                Some(ShapeMismatch {
442                    expected: self.clone(),
443                    received: other.clone(),
444                    causes,
445                })
446            }
447        };
448
449        let self_causes = match self.case() {
450            ShapeCase::One(self_shapes) => {
451                // If the union is empty (i.e. self.is_never()), then the only
452                // other shapes that can be validated by this empty union are
453                // other never shapes and None.
454                if self_shapes.is_empty() {
455                    return if other.is_never() || other.is_none() {
456                        None
457                    } else {
458                        // Reporting this error is important, since there are no
459                        // self shapes to produce mismatches below.
460                        Some(ShapeMismatch {
461                            expected: self.clone(),
462                            received: other.clone(),
463                            causes: Vec::new(),
464                        })
465                    };
466                }
467
468                // If other is validated by any single shape in self_shapes,
469                // then other is validated by the union. However, we want to try
470                // any shapes other than *bound* ShapeCase::Name shapes first,
471                // since bound ::Name shapes are the gateway to infinite cycles,
472                // and the names might resolve to shapes we've already seen in
473                // the union, so we don't need to validate them again.
474                let mut bound_name_target_shapes = Vec::new();
475
476                // It's acceptable not to use a MergeSet<Shape> here, because we
477                // don't care about name/location metadata, but only whether one
478                // of the resolved bound_name_target_shapes is one we've seen already,
479                // which concerns only ShapeCase equality/hashing.
480                let mut not_bound_names: IndexSet<&Shape> = IndexSet::new();
481
482                let mut causes = Vec::new();
483
484                for self_shape in self_shapes.iter() {
485                    if let ShapeCase::Name(name, weak) = self_shape.case() {
486                        if let Some(named_shape) = weak.upgrade(name) {
487                            // Handle in a later loop, keeping the reference
488                            // itself: the deferral hop resolves a name, so it
489                            // goes through the same guard as the ::Name arms.
490                            bound_name_target_shapes.push((self_shape, named_shape));
491                            continue;
492                        }
493                    }
494                    // If self_shape is not a bound named shape, validate it
495                    // against other. If it validates other (validate returns
496                    // None), `?` returns None early, skipping the rest of the
497                    // shapes.
498                    let mismatch = self_shape.validate_within(
499                        other,
500                        splits_taken,
501                        depth_remaining,
502                        memo,
503                        resolving,
504                    )?;
505                    // If the unbound named shape does not validate other, we
506                    // need to report it.
507                    causes.push(mismatch);
508                    not_bound_names.insert(self_shape);
509                }
510
511                for (name_shape, target_shape) in &bound_name_target_shapes {
512                    // If we resolved a bound name to a shape we've already
513                    // seen, we do not need to validate it again.
514                    if !not_bound_names.contains(target_shape) {
515                        // If the bound named shape validates other (validate
516                        // returns None), `?` returns None early, skipping the
517                        // rest of the shapes. The hop resolves a name without
518                        // passing through a ::Name arm, so it is guarded
519                        // here: a union whose members are nothing but each
520                        // other's names would otherwise lap this loop until
521                        // the depth cap. (Such non-contractive cycles are
522                        // degenerate -- the equations carry no structure --
523                        // and unreachable from GraphQL, where fields always
524                        // interpose an object between name references.)
525                        let mismatch =
526                            guarded(resolving, ((*name_shape).clone(), other.clone()), || {
527                                target_shape.validate_within(
528                                    other,
529                                    splits_taken,
530                                    depth_remaining,
531                                    memo,
532                                    resolving,
533                                )
534                            })?;
535                        // If the bound named shape does not validate other, we
536                        // need to report it.
537                        causes.push(mismatch);
538                    }
539                }
540
541                // A single union member can cover only part of other when
542                // other hides a union inside a field or item position. No
543                // member of One<{a: Int}, {a: String}> accepts
544                // {a: One<Int, String>} whole, even though every value of the
545                // latter belongs to one of the former. Distributing those
546                // inner unions yields branches the members can cover one at a
547                // time, so the union validates other if it validates every
548                // branch.
549                //
550                // Rather than materializing the whole cartesian product of
551                // union-typed positions, split one position at a time. Each
552                // child re-enters this arm, which tries every member whole
553                // before splitting again, so a child already covered by a
554                // single member prunes every completion below it without
555                // examining any of them. The full product is only ever walked
556                // when no member covers anything until the leaves.
557                //
558                // A union on the other side is not one of those cases. Its
559                // members are already the branches, and the ::One arm of the
560                // match on other.case() below tries all of them, so splitting
561                // here would ask the same question twice and pay for the
562                // privilege.
563                //
564                // That arm carries splits_taken rather than starting over, as
565                // every recursion here does, so it shares this comparison's
566                // remaining allowance instead of getting a fresh one. Skipping
567                // the split is still free of consequence -- the same branches
568                // are tried either way -- but a received union reached with the
569                // allowance already spent is answered without further
570                // distribution, exactly as any other shape would be.
571                //
572                // The search is bounded by three structural caps, so that
573                // where it stops is a fact about the shapes alone, readable
574                // off them in advance, rather than about a meter that ran
575                // out partway through: a union wider than MAX_SPLIT_MEMBERS
576                // never starts a search, a position of arity above
577                // MAX_SPLIT_ARITY is never distributed, and at most
578                // MAX_SPLIT_DEPTH positions are distributed along any one
579                // path through the comparison -- counted across descent into
580                // fields and items, not restarted at each object level, so
581                // that nesting adds to the count rather than multiplying the
582                // cost. A member covering a child whole still ends that
583                // branch immediately, so a shape whose product is
584                // astronomical but whose branches are covered a level down
585                // stays as cheap as ever. Falling outside any cap costs precision only:
586                // validate reports the mismatch it would have reported
587                // before splitting existed.
588                if !matches!(other.case(), ShapeCase::One(_))
589                    && splits_taken < MAX_SPLIT_DEPTH
590                    // take() so refusing a pathologically wide union costs a
591                    // bounded walk, not a count of every member.
592                    && self_shapes.iter().take(MAX_SPLIT_MEMBERS + 1).count() <= MAX_SPLIT_MEMBERS
593                {
594                    if let Split::Children(children) = split_alternatives(other) {
595                        if children.iter().all(|child| {
596                            self.validate_within(
597                                child,
598                                splits_taken + 1,
599                                depth_remaining,
600                                memo,
601                                resolving,
602                            )
603                            .is_none()
604                        }) {
605                            return None;
606                        }
607                    }
608                }
609
610                // Importantly, we do not return false here (in contrast with
611                // the ::All case below), because the union might still validate
612                // other, e.g. when other is Bool and the union contains both
613                // true and false as members.
614                //
615                // We will need to handle ShapeCase::One logic in the ::Bool
616                // case below, but otherwise the loop above saves most of the
617                // cases below from explicitly worrying about self being a
618                // ShapeCase::One.
619                causes
620            }
621
622            ShapeCase::All(self_shapes) => {
623                let mut causes = Vec::new();
624
625                for self_shape in self_shapes.iter() {
626                    if let Some(mismatch) = self_shape.validate_within(
627                        other,
628                        splits_taken,
629                        depth_remaining,
630                        memo,
631                        resolving,
632                    ) {
633                        // If the intersection member does not validate other,
634                        // we need to report it.
635                        causes.push(mismatch);
636                    }
637                }
638
639                return report(causes.is_empty(), causes);
640            }
641
642            // Sometimes we don't know anything (yet) about the structure of a
643            // shape, but still want to refer to it by name and access the
644            // shapes of subproperties (symbolically if not concretely). That's
645            // what the ShapeCase::Name variant models.
646            ShapeCase::Name(name, weak) => {
647                return if let Some(named_shape) = weak.upgrade(name) {
648                    // If this pair is already being compared on the path
649                    // above, another lap of name resolution would repeat it
650                    // deterministically, so assume the pair valid (the
651                    // coinductive hypothesis) and let the enclosing
652                    // comparison finish discharging it. See Resolving.
653                    guarded(resolving, (self.clone(), other.clone()), || {
654                        named_shape.validate_within(
655                            other,
656                            splits_taken,
657                            depth_remaining,
658                            memo,
659                            resolving,
660                        )
661                    })
662                } else {
663                    // When self is an unbound named shape reference, it
664                    // validates any other shape trivially, since it imposes no
665                    // expectations on the other shape (and could eventually
666                    // resolve to a shape that validates other).
667                    None
668                };
669            }
670
671            ShapeCase::Unknown => {
672                // Unknown shapes validate any shape.
673                return None;
674            }
675
676            _ => Vec::new(),
677        };
678
679        match other.case() {
680            ShapeCase::Bool(Some(value)) => report(
681                match self.case() {
682                    ShapeCase::Bool(self_value) => {
683                        Some(value) == self_value.as_ref() || self_value.is_none()
684                    }
685                    // We already handled the ::One and ::All cases above.
686                    _ => false,
687                },
688                self_causes,
689            ),
690
691            ShapeCase::Bool(None) => report(
692                (|| match self.case() {
693                    ShapeCase::Bool(None) => true,
694
695                    // This case goes beyond the basic ShapeCase::One handling
696                    // provided at the top of the function, because we want to allow
697                    // ::Bool(None) to be validated by a union of true and false.
698                    ShapeCase::One(self_shapes) => {
699                        let true_shape = Shape::bool_value(true, []);
700                        let false_shape = Shape::bool_value(false, []);
701                        let mut true_found = false;
702                        let mut false_found = false;
703
704                        for self_shape in self_shapes.iter() {
705                            // Note the direction: the question is whether a
706                            // member CONTAINS true, not whether it is
707                            // contained in {true}. The two coincide on the
708                            // shapes simplification can produce, since the
709                            // only simplified member contained in {true} is
710                            // the true literal itself, which is why the
711                            // reversed test was harmless. It is still the
712                            // wrong question, and it stops being harmless the
713                            // moment an unsimplified union reaches here.
714                            if self_shape
715                                .validate_within(
716                                    &true_shape,
717                                    splits_taken,
718                                    depth_remaining,
719                                    memo,
720                                    resolving,
721                                )
722                                .is_none()
723                            {
724                                true_found = true;
725                            }
726                            if self_shape
727                                .validate_within(
728                                    &false_shape,
729                                    splits_taken,
730                                    depth_remaining,
731                                    memo,
732                                    resolving,
733                                )
734                                .is_none()
735                            {
736                                false_found = true;
737                            }
738                            // Between them the members cover both booleans, so
739                            // the union covers Bool.
740                            if true_found && false_found {
741                                // Returns from the (|| match ...)() closure.
742                                return true;
743                            }
744                        }
745
746                        false
747                    }
748
749                    _ => false,
750                })(),
751                self_causes,
752            ),
753
754            ShapeCase::String(value) => report(
755                match self.case() {
756                    ShapeCase::String(self_value) => value == self_value || self_value.is_none(),
757                    // We already handled the ::One case above.
758                    _ => false,
759                },
760                self_causes,
761            ),
762
763            ShapeCase::Int(value) => report(
764                match self.case() {
765                    ShapeCase::Int(self_value) => value == self_value || self_value.is_none(),
766                    // All Int values are also (trivially convertible to) Float
767                    // values, so Int is a subshape of Float.
768                    ShapeCase::Float => true,
769                    // We already handled the ::One case above.
770                    _ => false,
771                },
772                self_causes,
773            ),
774
775            ShapeCase::Float => report(matches!(self.case(), ShapeCase::Float), Vec::new()),
776
777            // Both ::Null and ::None accept/validate only themselves, but they
778            // have an important difference in behavior when they appear in
779            // ::All intersections. The null value "poisons" intersections,
780            // reducing the whole intersection to null, which can be appropriate
781            // behavior when reporting certain kinds of top-level errors. The
782            // None value simply disappears from intersections, as it imposes no
783            // additional constraints on the intersection shape.
784            ShapeCase::Null => report(matches!(self.case(), ShapeCase::Null), Vec::new()),
785            ShapeCase::None => report(matches!(self.case(), ShapeCase::None), Vec::new()),
786
787            // ShapeCase::Unknown is validated by no other shapes except itself.
788            // We already handled the case when self is ::Unknown above, so we
789            // can report false here.
790            ShapeCase::Unknown => report(false, Vec::new()),
791
792            ShapeCase::Array {
793                prefix: other_prefix,
794                tail: other_tail,
795            } => match self.case() {
796                ShapeCase::Array {
797                    prefix: self_prefix,
798                    tail: self_tail,
799                } => {
800                    let mut causes = Vec::new();
801
802                    for i in 0..self_prefix.len().max(other_prefix.len()) {
803                        if let Some(self_item) = self_prefix.get(i) {
804                            if let Some(other_item) = other_prefix.get(i) {
805                                if let Some(mismatch) = self_item.validate_within(
806                                    other_item,
807                                    splits_taken,
808                                    depth_remaining,
809                                    memo,
810                                    resolving,
811                                ) {
812                                    causes.push(mismatch);
813                                }
814                            } else {
815                                // other is shorter, so position i in other is
816                                // either governed by other_tail or absent
817                                // entirely, since a tailed array may end at
818                                // any length. self_item has to accept both
819                                // possibilities: checking only other_tail
820                                // accepts [Int, Int, ...] against
821                                // [Int, ...Int], which the value [5] refutes.
822                                // This mirrors the One<None, rest> probe the
823                                // object walk already uses for a field other
824                                // does not declare.
825                                //
826                                // The two short circuits are the ones the
827                                // object walk takes for the same probe: a None
828                                // tail means the position is simply absent,
829                                // since One<None, None> is None, and an
830                                // Unknown tail absorbs the None, since
831                                // One<None, Unknown> is Unknown. Both spare a
832                                // union build per position per call, and the
833                                // closed tuple that takes the first one is the
834                                // common case.
835                                let possible = if other_tail.is_none() || other_tail.is_unknown() {
836                                    other_tail.clone()
837                                } else {
838                                    Shape::one([Shape::none(), other_tail.clone()], [])
839                                };
840                                if let Some(mismatch) = self_item.validate_within(
841                                    &possible,
842                                    splits_taken,
843                                    depth_remaining,
844                                    memo,
845                                    resolving,
846                                ) {
847                                    causes.push(mismatch);
848                                }
849                            }
850                        } else if let Some(other_item) = other_prefix.get(i) {
851                            // self has no prefix position i; self_tail
852                            // governs.
853                            if self_tail.is_none() {
854                                // self is closed at length self_prefix.len()
855                                // but other carries another element here.
856                                causes.push(ShapeMismatch {
857                                    expected: self.clone(),
858                                    received: other.clone(),
859                                    causes: Vec::new(),
860                                });
861                            } else if let Some(mismatch) = self_tail.validate_within(
862                                other_item,
863                                splits_taken,
864                                depth_remaining,
865                                memo,
866                                resolving,
867                            ) {
868                                causes.push(mismatch);
869                            }
870                        }
871                        // `i < max(self_prefix.len(), other_prefix.len())`
872                        // guarantees at least one of the two `get(i)` calls
873                        // above returns `Some`, so there is no else branch.
874                    }
875
876                    #[allow(clippy::match_same_arms)]
877                    match (self_tail.case(), other_tail.case()) {
878                        // other's tail is None, so it is effectively a fixed
879                        // tuple; nothing more to check against self_tail.
880                        (_, ShapeCase::None) => {}
881                        // self is closed but other has a tail, so other may
882                        // carry elements beyond self_prefix.len() that self
883                        // cannot accept.
884                        (ShapeCase::None, _) => {
885                            causes.push(ShapeMismatch {
886                                expected: self.clone(),
887                                received: other.clone(),
888                                causes: Vec::new(),
889                            });
890                        }
891                        _ => {
892                            if let Some(mismatch) = self_tail.validate_within(
893                                other_tail,
894                                splits_taken,
895                                depth_remaining,
896                                memo,
897                                resolving,
898                            ) {
899                                causes.push(mismatch);
900                            }
901                        }
902                    }
903
904                    report(causes.is_empty(), causes)
905                }
906
907                // We already handled the ::One and ::All cases above.
908                _ => report(false, Vec::new()),
909            },
910
911            ShapeCase::Object { fields, rest } => match self.case() {
912                ShapeCase::Object {
913                    fields: self_fields,
914                    rest: self_rest,
915                } => {
916                    let mut causes = Vec::new();
917
918                    // For each field self declares, determine the shape it
919                    // can take under `other` and check self's expectation
920                    // accepts it.
921                    for (field_name, field_shape) in self_fields {
922                        if let Some(other_field_shape) = fields.get(field_name) {
923                            if let Some(mismatch) = field_shape.validate_within(
924                                other_field_shape,
925                                splits_taken,
926                                depth_remaining,
927                                memo,
928                                resolving,
929                            ) {
930                                causes.push(mismatch);
931                            }
932                        } else {
933                            // other does not declare field_name. Under other,
934                            // v.field_name is either absent (None) or, if
935                            // other has a non-None rest shape, a dynamic
936                            // property of that rest shape. Self must accept
937                            // any such possibility.
938                            if rest.is_none() {
939                                // Field is guaranteed absent under other;
940                                // self must tolerate None (be optional).
941                                if let Some(mismatch) = field_shape.validate_within(
942                                    &Shape::none(),
943                                    splits_taken,
944                                    depth_remaining,
945                                    memo,
946                                    resolving,
947                                ) {
948                                    causes.push(mismatch);
949                                }
950                            } else if rest.is_unknown() {
951                                // `One<None, Unknown>` simplifies to `Unknown`,
952                                // which is the most permissive shape — it
953                                // validates `field_shape` iff `field_shape`
954                                // validates `Unknown`. This is the new
955                                // open-by-default case after 0.8.0; short-
956                                // circuiting it skips an allocation-heavy
957                                // `Shape::one([Shape::none(), ...])` build per
958                                // missing field per `accepts` call.
959                                if let Some(mismatch) = field_shape.validate_within(
960                                    rest,
961                                    splits_taken,
962                                    depth_remaining,
963                                    memo,
964                                    resolving,
965                                ) {
966                                    causes.push(mismatch);
967                                }
968                            } else {
969                                let possible = Shape::one([Shape::none(), rest.clone()], []);
970                                if let Some(mismatch) = field_shape.validate_within(
971                                    &possible,
972                                    splits_taken,
973                                    depth_remaining,
974                                    memo,
975                                    resolving,
976                                ) {
977                                    causes.push(mismatch);
978                                }
979                            }
980                        }
981                    }
982
983                    // For each field other declares that self does not, the
984                    // field must be absorbed by self's rest shape. A closed
985                    // self (rest: None) cannot absorb any extra declared
986                    // field.
987                    for (field_name, other_field_shape) in fields {
988                        if self_fields.contains_key(field_name) {
989                            continue;
990                        }
991                        // A field whose shape admits no value is provably
992                        // always absent, so it constrains nothing and there is
993                        // nothing for self's rest to absorb. Skipping it is
994                        // exact, not generous, and it has to happen on both
995                        // sides of the closed/open split below: skipping it
996                        // only for a closed self is what made accepts
997                        // non-transitive, since a closed middle shape could
998                        // accept such a field while an open outer shape that
999                        // accepts the middle could not.
1000                        if other_field_shape.is_none() || other_field_shape.is_never() {
1001                            continue;
1002                        }
1003                        if self_rest.is_none() {
1004                            causes.push(ShapeMismatch {
1005                                expected: self.clone(),
1006                                received: other.clone(),
1007                                causes: Vec::new(),
1008                            });
1009                        } else if let Some(mismatch) = self_rest.validate_within(
1010                            other_field_shape,
1011                            splits_taken,
1012                            depth_remaining,
1013                            memo,
1014                            resolving,
1015                        ) {
1016                            causes.push(mismatch);
1017                        }
1018                    }
1019
1020                    // Finally, reconcile the two rest shapes themselves.
1021                    match (rest.case(), self_rest.case()) {
1022                        // Either other declares no dynamic properties, so
1023                        // there is nothing more for self.rest to absorb,
1024                        // regardless of whether self is open or closed.
1025                        (ShapeCase::None, _) => {}
1026                        // other permits dynamic properties but self is
1027                        // closed — other's values can carry extras that
1028                        // self does not permit.
1029                        (_, ShapeCase::None) => {
1030                            causes.push(ShapeMismatch {
1031                                expected: self.clone(),
1032                                received: other.clone(),
1033                                causes: Vec::new(),
1034                            });
1035                        }
1036                        // Both sides permit dynamic properties; self's rest
1037                        // must be at least as permissive as other's.
1038                        _ => {
1039                            if let Some(mismatch) = self_rest.validate_within(
1040                                rest,
1041                                splits_taken,
1042                                depth_remaining,
1043                                memo,
1044                                resolving,
1045                            ) {
1046                                causes.push(mismatch);
1047                            }
1048                        }
1049                    }
1050
1051                    report(causes.is_empty(), causes)
1052                }
1053
1054                // We already handled the ::One and ::All cases above.
1055                _ => report(false, Vec::new()),
1056            },
1057
1058            // If *other* is a ShapeCase::One union, then every possibility must
1059            // be validated by self. For example, if other is One<true, false>,
1060            // and self is Bool, then since true and false are each individually
1061            // validated by Bool, Bool validates the union One<true, false>.
1062            ShapeCase::One(other_shapes) => {
1063                // If other is an empty union (Never), then only None or other
1064                // Never shapes can validate it. We already handled the case
1065                // when self is Never in the first match, so here we only need
1066                // to check for None.
1067                if other_shapes.is_empty() {
1068                    return if self.is_none() {
1069                        None
1070                    } else {
1071                        Some(ShapeMismatch {
1072                            expected: self.clone(),
1073                            received: other.clone(),
1074                            causes: Vec::new(),
1075                        })
1076                    };
1077                }
1078
1079                let mut causes = Vec::new();
1080
1081                for other_shape in other_shapes.iter() {
1082                    if let Some(mismatch) = self.validate_within(
1083                        other_shape,
1084                        splits_taken,
1085                        depth_remaining,
1086                        memo,
1087                        resolving,
1088                    ) {
1089                        // If the union member does not validate self, we need
1090                        // to report it.
1091                        causes.push(mismatch);
1092                    }
1093                }
1094
1095                report(causes.is_empty(), causes)
1096            }
1097
1098            // If other is a ShapeCase::All intersection, then it is validated
1099            // by self if any of the member shapes are validated by self.
1100            ShapeCase::All(other_shapes) => {
1101                let mut causes = Vec::new();
1102
1103                for other_shape in other_shapes.iter() {
1104                    // If self validates this member (validate returns None), `?`
1105                    // returns None early — the intersection is validated.
1106                    let mismatch = self.validate_within(
1107                        other_shape,
1108                        splits_taken,
1109                        depth_remaining,
1110                        memo,
1111                        resolving,
1112                    )?;
1113                    causes.push(mismatch);
1114                }
1115
1116                report(causes.is_empty(), causes)
1117            }
1118
1119            ShapeCase::Name(name, weak) => {
1120                if let Some(other_shape) = weak.upgrade(name) {
1121                    // If other is a bound named shape reference, pretend we
1122                    // validated against the named shape, under the same
1123                    // coinductive guard as the self-side ::Name arm above.
1124                    return guarded(resolving, (self.clone(), other.clone()), || {
1125                        self.validate_within(
1126                            &other_shape,
1127                            splits_taken,
1128                            depth_remaining,
1129                            memo,
1130                            resolving,
1131                        )
1132                    });
1133                }
1134                // When other is an unbound named shape reference, it is
1135                // accepted/validated by no shape except itself.
1136                report(other.case == self.case, Vec::new())
1137            }
1138        }
1139    }
1140}
1141
1142/// The three caps that bound the union splitting search, together. Splitting
1143/// distributes a cartesian product over union-typed positions, so a search
1144/// that reaches the leaves can blow up combinatorially and has to be stopped
1145/// somewhere. These caps are all structural: whether a pair of shapes falls
1146/// inside them is a fact about those shapes alone, readable off them in
1147/// advance, the same at every depth of every larger comparison — never a
1148/// meter that ran out partway, whose position nobody could have predicted.
1149///
1150/// The rule in one breath: a union of at most [`MAX_SPLIT_MEMBERS`] members
1151/// distributes received positions of arity at most [`MAX_SPLIT_ARITY`], at
1152/// most [`MAX_SPLIT_DEPTH`] positions deep along any one path through the
1153/// comparison.
1154///
1155/// The caps are deliberately not a census of the received shape. A member
1156/// that covers a branch whole prunes every completion below it, so only
1157/// positions the search actually resolves count against the depth: a record
1158/// with sixty-four union-typed fields absorbed by the expected members' rest
1159/// resolves one position and stays as cheap as it looks, however astronomical
1160/// its nominal product. Counting the product instead was this bound's first
1161/// design, and it refused exactly those cheap shapes; metering comparisons
1162/// performed was its second, and its cliff depended on how much of the meter
1163/// sibling comparisons had already spent. Falling outside any cap costs
1164/// precision only: `validate` reports the mismatch it would have reported
1165/// before splitting existed.
1166///
1167/// Worst case, all three caps at their limits with nothing pruning:
1168/// `MAX_SPLIT_MEMBERS * (MAX_SPLIT_ARITY ^ MAX_SPLIT_DEPTH and change)` is
1169/// about 300k member-vs-branch comparisons, tens of milliseconds, and
1170/// reaching it takes a shape as contrived as the tests' every-assignment
1171/// unions. The
1172/// common doomed walk — two-member positions that never prune, which is what
1173/// a union of wide records looks like — dies at the depth cap after a few
1174/// thousand comparisons.
1175///
1176/// Note that caps of any size are a cliff for transitivity, not just for
1177/// precision: inside them acceptance composes along chains, outside them the
1178/// fallback answer does not, so `accepts` is not transitive for chains that
1179/// straddle a cap — but which side of the cliff a chain sits on is now
1180/// decidable by inspection. The Lean model on the `lean-model` branch omits
1181/// this splitting clause entirely (its divergence 5), in part because such
1182/// cases do not compose under it.
1183///
1184/// A position is distributed only if its union has at most this many members.
1185/// Real unions reachable from GraphQL stay small — a nullable field is two, a
1186/// union or interface type is one per member or implementor — so eight covers
1187/// them with room while keeping the worst-case fanout per level a single
1188/// digit.
1189const MAX_SPLIT_ARITY: usize = 8;
1190
1191/// At most this many positions are distributed along any one path through a
1192/// comparison before the search falls back. Pruned positions do not count, so
1193/// this is a limit on how many unions must simultaneously be distributed
1194/// before any member covers, not on how many a shape may contain. The count
1195/// survives descent into fields and items (see [`Shape::validate_within`]),
1196/// so it bounds the comparison as a whole; were it per object level instead,
1197/// nesting would multiply one level's worst case once per level.
1198///
1199/// A dozen is far above what the search costs, and that is deliberate: this
1200/// cap buys precision, and it is [`MAX_SPLIT_MEMBERS`] that bounds cost. The
1201/// reason is that the `all` over a split's children short circuits on the
1202/// first child that fails, so a search whose answer is a rejection walks one
1203/// path and abandons it rather than walking the product. The product is only
1204/// walked when branches succeed, and making every branch need its own member
1205/// requires a member per branch, which the member cap already limits to 64.
1206/// Measured against twelve positions of arity eight and 64 members that cover
1207/// nothing, raising this from 4 through 32 moved the worst case from 3.9ms to
1208/// 6.9ms and nothing else; the value is chosen for what it admits, not for
1209/// what it forbids.
1210///
1211/// Twelve nested distributions is deeper than a selection set reaches, and
1212/// past it the benign families stop gaining: a chain needing one distribution
1213/// at each of twelve levels is accepted here and costs 402µs.
1214///
1215/// The short circuit is necessary for twelve to be affordable but not
1216/// sufficient: the memo in [`SplitMemo`] is the other half. The member loop
1217/// asks the same union-against-subtree question once per branch of every
1218/// level above it, and it is the memo answering that question once that keeps
1219/// a doomed search nested several levels deep in the milliseconds. Without it
1220/// the same shapes run for minutes, and the tests that pin this cap
1221/// (`test_union_splitting_cliffs_at_each_cap` and
1222/// `test_split_depth_counts_the_whole_comparison_not_each_object_level`) do
1223/// not finish. The two were raised and introduced together, and should be
1224/// changed together.
1225const MAX_SPLIT_DEPTH: usize = 12;
1226
1227/// An expected union wider than this never starts a split search, since every
1228/// resolved position is compared against every member, and no cap on the
1229/// received shape bounds that factor.
1230const MAX_SPLIT_MEMBERS: usize = 64;
1231
1232/// What [`split_alternatives`] found while walking a shape for a union-typed
1233/// position it can distribute. Only `Children` costs anything; the caller
1234/// treats the other two alike, and they are kept apart so the walk can say
1235/// which of the two reasons it came back empty for.
1236enum Split {
1237    /// No union-typed position to distribute anywhere in the shape.
1238    Nothing,
1239    /// Every union-typed position the walk found was wider than
1240    /// [`MAX_SPLIT_ARITY`], so none of them was distributed.
1241    ///
1242    /// An over-wide position is **skipped, not declined on the spot**: the
1243    /// walk keeps looking for a narrower one. Declining on the spot was the
1244    /// original rule, on the grounds that sliding ahead would let the cap's
1245    /// size decide which position gets distributed, and with it the answer.
1246    /// That is right about the hazard and wrong about which choice avoids it.
1247    /// The walk visits an object's fields in `IndexMap` order, so declining at
1248    /// the first over-wide position makes the answer depend on **field
1249    /// insertion order** -- and insertion order is not part of a shape's
1250    /// identity, since `PartialEq` and `Hash` for `ShapeCase` are both
1251    /// order-insensitive. Two shapes comparing equal could then be accepted
1252    /// and rejected respectively, which
1253    /// `test_union_splitting_ignores_field_order` pins against. A named
1254    /// constant a reader can look up is a far better thing for an answer to
1255    /// depend on than an order nobody can see.
1256    ///
1257    /// Skipping keeps the part of the cap that matters. What the cap is for is
1258    /// that [`split_alternatives`] clones the containing container once per
1259    /// member of the position it distributes, and a position that is skipped
1260    /// is never materialized, so the allocation blowup the cap exists to
1261    /// prevent is prevented either way. Measured on a received record holding
1262    /// a 200,000-member union beside a hundred sibling fields, best of three:
1263    ///
1264    /// ```text
1265    /// declining at the position    12.0ms
1266    /// skipping past it             14.9ms
1267    /// no arity cap at all         872ms
1268    /// ```
1269    ///
1270    /// The 3ms is the walk carrying on through the remaining positions looking
1271    /// for a narrower one and finding none, which is a scan rather than a
1272    /// materialization. That is the honest price of the change, and it buys an
1273    /// answer that does not depend on field order.
1274    TooWide,
1275    /// One shape per member of the first distributable position's union.
1276    Children(Vec<Shape>),
1277}
1278
1279/// Replaces one union-typed position of `shape` with each member of that
1280/// union, returning the resulting shapes as [`Split::Children`], or
1281/// [`Split::Nothing`] when `shape` has no union-typed position to split.
1282///
1283/// The returned shapes are strictly smaller than `shape`, since each drops a
1284/// union node in favor of one of its members. That is what makes the search in
1285/// `validate` terminate.
1286///
1287/// Each returned shape is a clone of the containing object or array, so the
1288/// materialization itself costs the union's arity times the container's
1289/// width. The arity cap is therefore checked before anything is built: a
1290/// union wider than [`MAX_SPLIT_ARITY`] comes back as [`Split::TooWide`], at
1291/// zero allocations, and the walk moves on to look for a narrower position
1292/// rather than giving up where it stood. Skipping rather than declining is
1293/// what keeps the answer independent of the order an object's fields were
1294/// inserted in; see [`Split::TooWide`] for why that matters more than the
1295/// property the earlier rule was protecting.
1296///
1297/// For example `{a: One<Int, String>, b: Bool}` yields `{a: Int, b: Bool}` and
1298/// `{a: String, b: Bool}`. Only the first distributable position found is
1299/// expanded; the caller recurses to reach the rest. A more sophisticated
1300/// version could order positions by which one best discriminates among the
1301/// union members, the way a word search tries the rarest letter first, but the
1302/// prune that matters is the caller's, which tests whole members before
1303/// splitting again.
1304///
1305/// Three positions deliberately never split:
1306///
1307/// * `rest` and `tail` shapes, which constrain unboundedly many values that
1308///   each choose independently. Distributing them would lose meaning, since
1309///   `{...One<Int, String>}` accepts `{"p": 1, "q": "s"}` while neither
1310///   `{...Int}` nor `{...String}` does.
1311/// * Members of a [`ShapeCase::All`] intersection, which the intersection
1312///   itself is responsible for resolving.
1313/// * [`ShapeCase::Name`] references, so splitting cannot follow a cycle.
1314fn split_alternatives(shape: &Shape) -> Split {
1315    match shape.case() {
1316        ShapeCase::One(shapes) => {
1317            // A union of one member, or the empty union, offers no choice to
1318            // distribute. Reporting Nothing for the singleton case also keeps
1319            // the caller from recursing on an unchanged shape forever. The
1320            // count is bounded and taken before any member is cloned, so a
1321            // union too wide to split costs nothing to refuse.
1322            let count = shapes.iter().take(MAX_SPLIT_ARITY + 1).count();
1323            if count <= 1 {
1324                Split::Nothing
1325            } else if count > MAX_SPLIT_ARITY {
1326                Split::TooWide
1327            } else {
1328                Split::Children(shapes.iter().cloned().collect())
1329            }
1330        }
1331
1332        ShapeCase::Object { fields, rest } => {
1333            let mut skipped_too_wide = false;
1334            for (key, field_shape) in fields {
1335                let options = match split_alternatives(field_shape) {
1336                    Split::Nothing => continue,
1337                    // Skip and keep looking rather than declining here, so
1338                    // that field order cannot decide the answer. See
1339                    // [`Split::TooWide`].
1340                    Split::TooWide => {
1341                        skipped_too_wide = true;
1342                        continue;
1343                    }
1344                    Split::Children(options) => options,
1345                };
1346                return Split::Children(
1347                    options
1348                        .into_iter()
1349                        .map(|option| {
1350                            let mut split_fields = fields.clone();
1351                            split_fields.insert(key.clone(), option);
1352                            Shape::object(split_fields, rest.clone(), shape.locations().cloned())
1353                        })
1354                        .collect(),
1355                );
1356            }
1357            if skipped_too_wide {
1358                Split::TooWide
1359            } else {
1360                Split::Nothing
1361            }
1362        }
1363
1364        ShapeCase::Array { prefix, tail } => {
1365            let mut skipped_too_wide = false;
1366            for (index, item_shape) in prefix.iter().enumerate() {
1367                let options = match split_alternatives(item_shape) {
1368                    Split::Nothing => continue,
1369                    // Skip and keep looking, as in the object walk above.
1370                    Split::TooWide => {
1371                        skipped_too_wide = true;
1372                        continue;
1373                    }
1374                    Split::Children(options) => options,
1375                };
1376                return Split::Children(
1377                    options
1378                        .into_iter()
1379                        .map(|option| {
1380                            let mut split_prefix = prefix.clone();
1381                            split_prefix[index] = option;
1382                            Shape::array(split_prefix, tail.clone(), shape.locations().cloned())
1383                        })
1384                        .collect(),
1385                );
1386            }
1387            if skipped_too_wide {
1388                Split::TooWide
1389            } else {
1390                Split::Nothing
1391            }
1392        }
1393
1394        _ => Split::Nothing,
1395    }
1396}