Skip to main content

antlr4_runtime/
prediction.rs

1use std::cmp::Ordering;
2use std::collections::{BTreeMap, BTreeSet, HashMap};
3use std::hash::{BuildHasherDefault, Hash, Hasher};
4use std::rc::Rc;
5
6pub const EMPTY_RETURN_STATE: usize = usize::MAX;
7
8/// Lightweight `FxHash`-style hasher.
9///
10/// Used by `BaseLexer`'s DFA-trace map and the `epsilon_closure` `seen`
11/// set to avoid the `SipHash` overhead of `std::collections::HashMap`'s
12/// default hasher on the hot lexer path.
13#[derive(Debug, Default)]
14pub struct PredictionFxHasher {
15    hash: u64,
16}
17
18const FX_ROT: u32 = 5;
19const FX_SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
20
21impl Hasher for PredictionFxHasher {
22    #[inline]
23    fn write(&mut self, bytes: &[u8]) {
24        let mut bytes = bytes;
25        while bytes.len() >= 8 {
26            let (head, rest) = bytes.split_at(8);
27            let word = u64::from_le_bytes(head.try_into().expect("8-byte chunk"));
28            self.hash = (self.hash.rotate_left(FX_ROT) ^ word).wrapping_mul(FX_SEED);
29            bytes = rest;
30        }
31        for &b in bytes {
32            self.hash = (self.hash.rotate_left(FX_ROT) ^ u64::from(b)).wrapping_mul(FX_SEED);
33        }
34    }
35
36    #[inline]
37    fn write_u8(&mut self, value: u8) {
38        self.hash = (self.hash.rotate_left(FX_ROT) ^ u64::from(value)).wrapping_mul(FX_SEED);
39    }
40
41    #[inline]
42    fn write_u32(&mut self, value: u32) {
43        self.hash = (self.hash.rotate_left(FX_ROT) ^ u64::from(value)).wrapping_mul(FX_SEED);
44    }
45
46    #[inline]
47    fn write_u64(&mut self, value: u64) {
48        self.hash = (self.hash.rotate_left(FX_ROT) ^ value).wrapping_mul(FX_SEED);
49    }
50
51    #[inline]
52    fn write_usize(&mut self, value: usize) {
53        self.hash = (self.hash.rotate_left(FX_ROT) ^ value as u64).wrapping_mul(FX_SEED);
54    }
55
56    #[inline]
57    fn write_i32(&mut self, value: i32) {
58        self.write_u32(i32::cast_unsigned(value));
59    }
60
61    #[inline]
62    fn finish(&self) -> u64 {
63        self.hash
64    }
65}
66
67type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PredictionFxHasher>>;
68
69#[derive(Clone, Debug)]
70pub enum PredictionContext {
71    Empty {
72        cached_hash: u64,
73    },
74    Singleton {
75        parent: Rc<Self>,
76        return_state: usize,
77        cached_hash: u64,
78    },
79    Array {
80        parents: Vec<Rc<Self>>,
81        return_states: Vec<usize>,
82        cached_hash: u64,
83    },
84}
85
86impl PredictionContext {
87    pub fn empty() -> Rc<Self> {
88        EMPTY_PREDICTION_CONTEXT.with(Rc::clone)
89    }
90
91    pub fn singleton(parent: Rc<Self>, return_state: usize) -> Rc<Self> {
92        if return_state == EMPTY_RETURN_STATE {
93            Self::empty()
94        } else {
95            Rc::new(Self::Singleton {
96                cached_hash: prediction_context_singleton_hash(&parent, return_state),
97                parent,
98                return_state,
99            })
100        }
101    }
102
103    fn array(parents: Vec<Rc<Self>>, return_states: Vec<usize>) -> Rc<Self> {
104        Rc::new(Self::Array {
105            cached_hash: prediction_context_array_hash(&parents, &return_states),
106            parents,
107            return_states,
108        })
109    }
110
111    pub const fn cached_hash(&self) -> u64 {
112        match self {
113            Self::Empty { cached_hash }
114            | Self::Singleton { cached_hash, .. }
115            | Self::Array { cached_hash, .. } => *cached_hash,
116        }
117    }
118
119    pub const fn len(&self) -> usize {
120        match self {
121            Self::Empty { .. } => 1,
122            Self::Singleton { .. } => 1,
123            Self::Array { return_states, .. } => return_states.len(),
124        }
125    }
126
127    pub const fn is_empty(&self) -> bool {
128        matches!(self, Self::Empty { .. })
129    }
130
131    pub fn return_state(&self, index: usize) -> Option<usize> {
132        match self {
133            Self::Empty { .. } if index == 0 => Some(EMPTY_RETURN_STATE),
134            Self::Singleton { return_state, .. } if index == 0 => Some(*return_state),
135            Self::Array { return_states, .. } => return_states.get(index).copied(),
136            Self::Empty { .. } => None,
137            Self::Singleton { .. } => None,
138        }
139    }
140
141    pub fn parent(&self, index: usize) -> Option<Rc<Self>> {
142        match self {
143            Self::Empty { .. } => None,
144            Self::Singleton { parent, .. } if index == 0 => Some(Rc::clone(parent)),
145            Self::Array { parents, .. } => parents.get(index).cloned(),
146            Self::Singleton { .. } => None,
147        }
148    }
149
150    pub fn has_empty_path(&self) -> bool {
151        match self {
152            Self::Empty { .. } => true,
153            Self::Singleton { return_state, .. } => *return_state == EMPTY_RETURN_STATE,
154            // Array return states are kept sorted ascending and
155            // `EMPTY_RETURN_STATE` is `usize::MAX`, so the empty path can only be
156            // the final entry — an O(1) check instead of a linear scan.
157            Self::Array { return_states, .. } => return_states.last() == Some(&EMPTY_RETURN_STATE),
158        }
159    }
160
161    pub fn merge(left: Rc<Self>, right: Rc<Self>) -> Rc<Self> {
162        Self::merge_with_options(left, right, false, None)
163    }
164
165    /// Merges two prediction contexts using ANTLR's SLL/LL root semantics.
166    ///
167    /// In SLL mode the empty root is a wildcard: `$ + x = $`. In full LL mode
168    /// it is an ordinary array entry: `$ + x = [$, x]`. The optional merge
169    /// cache is intentionally per prediction operation so large conflict-heavy
170    /// parses can drop the cache immediately after `adaptive_predict`.
171    #[allow(clippy::needless_pass_by_value)]
172    pub fn merge_with_options(
173        left: Rc<Self>,
174        right: Rc<Self>,
175        root_is_wildcard: bool,
176        mut cache: Option<&mut PredictionContextMergeCache>,
177    ) -> Rc<Self> {
178        #[cfg(feature = "perf-counters")]
179        crate::perf::record_context_merge_call();
180        if left == right {
181            #[cfg(feature = "perf-counters")]
182            crate::perf::record_context_merge_identical();
183            return left;
184        }
185        if let Some(cache) = cache.as_deref_mut() {
186            if let Some(merged) = cache.get(&left, &right) {
187                #[cfg(feature = "perf-counters")]
188                crate::perf::record_context_merge_cache_hit();
189                return merged;
190            }
191            #[cfg(feature = "perf-counters")]
192            crate::perf::record_context_merge_cache_miss();
193        }
194        let merged = if root_is_wildcard && (left.is_empty() || right.is_empty()) {
195            Self::empty()
196        } else {
197            #[cfg(feature = "perf-counters")]
198            crate::perf::record_context_merge_uncached();
199            merge_contexts_uncached(&left, &right)
200        };
201        if let Some(cache) = cache {
202            cache.insert(&left, &right, &merged);
203        }
204        merged
205    }
206}
207
208fn merge_contexts_uncached(
209    left: &Rc<PredictionContext>,
210    right: &Rc<PredictionContext>,
211) -> Rc<PredictionContext> {
212    if left == right {
213        return Rc::clone(left);
214    }
215    match (left.as_ref(), right.as_ref()) {
216        (PredictionContext::Empty { .. }, PredictionContext::Empty { .. }) => {
217            PredictionContext::empty()
218        }
219        (
220            PredictionContext::Singleton {
221                parent: left_parent,
222                return_state: left_return_state,
223                ..
224            },
225            PredictionContext::Singleton {
226                parent: right_parent,
227                return_state: right_return_state,
228                ..
229            },
230        ) => merge_two_context_entries(
231            Rc::clone(left_parent),
232            *left_return_state,
233            Rc::clone(right_parent),
234            *right_return_state,
235        ),
236        (PredictionContext::Empty { .. }, PredictionContext::Singleton { .. })
237        | (PredictionContext::Singleton { .. }, PredictionContext::Empty { .. }) => {
238            let (left_parent, left_return_state) = first_context_entry(left);
239            let (right_parent, right_return_state) = first_context_entry(right);
240            merge_two_context_entries(
241                left_parent,
242                left_return_state,
243                right_parent,
244                right_return_state,
245            )
246        }
247        (
248            PredictionContext::Array {
249                parents,
250                return_states,
251                ..
252            },
253            PredictionContext::Singleton { .. } | PredictionContext::Empty { .. },
254        ) => {
255            let (parent, return_state) = first_context_entry(right);
256            merge_array_with_entry(
257                Rc::clone(left),
258                parents,
259                return_states,
260                parent,
261                return_state,
262                false,
263            )
264        }
265        (
266            PredictionContext::Singleton { .. } | PredictionContext::Empty { .. },
267            PredictionContext::Array {
268                parents,
269                return_states,
270                ..
271            },
272        ) => {
273            let (parent, return_state) = first_context_entry(left);
274            merge_array_with_entry(
275                Rc::clone(right),
276                parents,
277                return_states,
278                parent,
279                return_state,
280                true,
281            )
282        }
283        (
284            PredictionContext::Array {
285                parents: left_parents,
286                return_states: left_return_states,
287                ..
288            },
289            PredictionContext::Array {
290                parents: right_parents,
291                return_states: right_return_states,
292                ..
293            },
294        ) => merge_arrays(
295            left_parents,
296            left_return_states,
297            right_parents,
298            right_return_states,
299        ),
300    }
301}
302
303fn first_context_entry(context: &Rc<PredictionContext>) -> (Rc<PredictionContext>, usize) {
304    match context.as_ref() {
305        PredictionContext::Empty { .. } => (Rc::clone(context), EMPTY_RETURN_STATE),
306        PredictionContext::Singleton {
307            parent,
308            return_state,
309            ..
310        } => (Rc::clone(parent), *return_state),
311        PredictionContext::Array { .. } => unreachable!("array contexts have multiple entries"),
312    }
313}
314
315fn merge_two_context_entries(
316    left_parent: Rc<PredictionContext>,
317    left_return_state: usize,
318    right_parent: Rc<PredictionContext>,
319    right_return_state: usize,
320) -> Rc<PredictionContext> {
321    if left_return_state == right_return_state && left_parent == right_parent {
322        return PredictionContext::singleton(left_parent, left_return_state);
323    }
324    let (first_parent, first_return_state, second_parent, second_return_state) =
325        if compare_entries(&right_parent, right_return_state, &left_parent, left_return_state)
326            == Ordering::Less
327        {
328            (
329                right_parent,
330                right_return_state,
331                left_parent,
332                left_return_state,
333            )
334        } else {
335            (
336                left_parent,
337                left_return_state,
338                right_parent,
339                right_return_state,
340            )
341        };
342    PredictionContext::array(
343        vec![first_parent, second_parent],
344        vec![first_return_state, second_return_state],
345    )
346}
347
348fn merge_array_with_entry(
349    array_context: Rc<PredictionContext>,
350    array_parents: &[Rc<PredictionContext>],
351    array_return_states: &[usize],
352    entry_parent: Rc<PredictionContext>,
353    entry_return_state: usize,
354    entry_on_left: bool,
355) -> Rc<PredictionContext> {
356    let mut insert_index = array_parents.len();
357    for (index, (parent, return_state)) in array_parents
358        .iter()
359        .zip(array_return_states)
360        .enumerate()
361    {
362        let ordering = compare_entries(&entry_parent, entry_return_state, parent, *return_state);
363        if ordering == Ordering::Equal && parent == &entry_parent {
364            return array_context;
365        }
366        let should_insert = if entry_on_left {
367            ordering != Ordering::Greater
368        } else {
369            ordering == Ordering::Less
370        };
371        if should_insert {
372            insert_index = index;
373            break;
374        }
375    }
376
377    let mut parents = Vec::with_capacity(array_parents.len() + 1);
378    let mut return_states = Vec::with_capacity(array_return_states.len() + 1);
379    parents.extend(array_parents[..insert_index].iter().cloned());
380    return_states.extend_from_slice(&array_return_states[..insert_index]);
381    parents.push(entry_parent);
382    return_states.push(entry_return_state);
383    parents.extend(array_parents[insert_index..].iter().cloned());
384    return_states.extend_from_slice(&array_return_states[insert_index..]);
385    PredictionContext::array(parents, return_states)
386}
387
388fn merge_arrays(
389    left_parents: &[Rc<PredictionContext>],
390    left_return_states: &[usize],
391    right_parents: &[Rc<PredictionContext>],
392    right_return_states: &[usize],
393) -> Rc<PredictionContext> {
394    let mut parents = Vec::with_capacity(left_parents.len() + right_parents.len());
395    let mut return_states = Vec::with_capacity(left_return_states.len() + right_return_states.len());
396    let mut left_index = 0;
397    let mut right_index = 0;
398
399    while left_index < left_parents.len() && right_index < right_parents.len() {
400        match compare_entries(
401            &left_parents[left_index],
402            left_return_states[left_index],
403            &right_parents[right_index],
404            right_return_states[right_index],
405        ) {
406            Ordering::Less => {
407                parents.push(Rc::clone(&left_parents[left_index]));
408                return_states.push(left_return_states[left_index]);
409                left_index += 1;
410            }
411            Ordering::Greater => {
412                parents.push(Rc::clone(&right_parents[right_index]));
413                return_states.push(right_return_states[right_index]);
414                right_index += 1;
415            }
416            // `compare_entries` is a strict total order whose final tie-break is a
417            // structural `parent.cmp`, so `Equal` means the two entries are
418            // structurally identical — keep one and drop the duplicate.
419            Ordering::Equal => {
420                parents.push(Rc::clone(&left_parents[left_index]));
421                return_states.push(left_return_states[left_index]);
422                left_index += 1;
423                right_index += 1;
424            }
425        }
426    }
427
428    for index in left_index..left_parents.len() {
429        parents.push(Rc::clone(&left_parents[index]));
430        return_states.push(left_return_states[index]);
431    }
432    for index in right_index..right_parents.len() {
433        parents.push(Rc::clone(&right_parents[index]));
434        return_states.push(right_return_states[index]);
435    }
436
437    if parents.len() == 1 {
438        return PredictionContext::singleton(
439            parents.pop().expect("single merged parent"),
440            return_states.pop().expect("single merged return state"),
441        );
442    }
443    PredictionContext::array(parents, return_states)
444}
445
446/// Strict total order over array context entries, used as the merge-sort key by
447/// all three merge helpers (`merge_two_context_entries`, `merge_array_with_entry`,
448/// `merge_arrays`). Orders by `return_state`, then `cached_hash`, then a
449/// structural `parent.cmp(parent)` tie-break.
450///
451/// The structural tie-break is what makes Array canonicalization collision-proof:
452/// two structurally-distinct parents that share a `return_state` *and* a colliding
453/// `cached_hash` (astronomically rare, but possible with a 64-bit hash) would
454/// otherwise compare equal and be appended in operand order, so `merge(a, b)` and
455/// `merge(b, a)` could produce arrays that are structurally unequal (Array `eq` is
456/// element-by-element) — breaking the "equal logical context ⇒ equal
457/// representation" invariant the merge/context caches rely on. Falling through to
458/// `parent.cmp` gives such entries a deterministic, order-independent position.
459/// On the common path (no hash collision) this is identical to comparing the old
460/// `(return_state, cached_hash)` key, so it is perf-neutral.
461///
462/// All three helpers must use this so every Array is built in this order; the
463/// 2-pointer sorted-merge in `merge_arrays` is only correct on inputs sorted by
464/// the same total order.
465fn compare_entries(
466    left_parent: &Rc<PredictionContext>,
467    left_return_state: usize,
468    right_parent: &Rc<PredictionContext>,
469    right_return_state: usize,
470) -> Ordering {
471    left_return_state
472        .cmp(&right_return_state)
473        .then_with(|| left_parent.cached_hash().cmp(&right_parent.cached_hash()))
474        .then_with(|| left_parent.cmp(right_parent))
475}
476
477impl PartialEq for PredictionContext {
478    fn eq(&self, other: &Self) -> bool {
479        if std::ptr::eq(self, other) {
480            return true;
481        }
482        if self.cached_hash() != other.cached_hash() {
483            return false;
484        }
485        match (self, other) {
486            (Self::Empty { .. }, Self::Empty { .. }) => true,
487            (
488                Self::Singleton {
489                    parent,
490                    return_state,
491                    ..
492                },
493                Self::Singleton {
494                    parent: other_parent,
495                    return_state: other_return_state,
496                    ..
497                },
498            ) => return_state == other_return_state && parent == other_parent,
499            (
500                Self::Array {
501                    parents,
502                    return_states,
503                    ..
504                },
505                Self::Array {
506                    parents: other_parents,
507                    return_states: other_return_states,
508                    ..
509                },
510            ) => return_states == other_return_states && parents == other_parents,
511            _ => false,
512        }
513    }
514}
515
516impl Eq for PredictionContext {}
517
518thread_local! {
519    static EMPTY_PREDICTION_CONTEXT: Rc<PredictionContext> = Rc::new(PredictionContext::Empty {
520        cached_hash: prediction_context_empty_hash(),
521    });
522}
523
524impl Hash for PredictionContext {
525    fn hash<H: Hasher>(&self, state: &mut H) {
526        state.write_u64(self.cached_hash());
527    }
528}
529
530impl Ord for PredictionContext {
531    fn cmp(&self, other: &Self) -> Ordering {
532        if std::ptr::eq(self, other) {
533            return Ordering::Equal;
534        }
535        self.cached_hash()
536            .cmp(&other.cached_hash())
537            .then_with(|| prediction_context_variant(self).cmp(&prediction_context_variant(other)))
538            .then_with(|| match (self, other) {
539                (Self::Empty { .. }, Self::Empty { .. }) => Ordering::Equal,
540                (
541                    Self::Singleton {
542                        parent,
543                        return_state,
544                        ..
545                    },
546                    Self::Singleton {
547                        parent: other_parent,
548                        return_state: other_return_state,
549                        ..
550                    },
551                ) => return_state
552                    .cmp(other_return_state)
553                    .then_with(|| parent.cmp(other_parent)),
554                (
555                    Self::Array {
556                        parents,
557                        return_states,
558                        ..
559                    },
560                    Self::Array {
561                        parents: other_parents,
562                        return_states: other_return_states,
563                        ..
564                    },
565                ) => return_states
566                    .cmp(other_return_states)
567                    .then_with(|| parents.cmp(other_parents)),
568                _ => Ordering::Equal,
569            })
570    }
571}
572
573impl PartialOrd for PredictionContext {
574    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
575        Some(self.cmp(other))
576    }
577}
578
579const fn prediction_context_variant(context: &PredictionContext) -> u8 {
580    match context {
581        PredictionContext::Empty { .. } => 0,
582        PredictionContext::Singleton { .. } => 1,
583        PredictionContext::Array { .. } => 2,
584    }
585}
586
587fn prediction_context_empty_hash() -> u64 {
588    let mut hasher = PredictionFxHasher::default();
589    hasher.write_u8(0);
590    hasher.finish()
591}
592
593fn prediction_context_singleton_hash(parent: &Rc<PredictionContext>, return_state: usize) -> u64 {
594    let mut hasher = PredictionFxHasher::default();
595    hasher.write_u8(1);
596    hasher.write_u64(parent.cached_hash());
597    hasher.write_usize(return_state);
598    hasher.finish()
599}
600
601fn prediction_context_array_hash(
602    parents: &[Rc<PredictionContext>],
603    return_states: &[usize],
604) -> u64 {
605    let mut hasher = PredictionFxHasher::default();
606    hasher.write_u8(2);
607    hasher.write_usize(parents.len());
608    for parent in parents {
609        hasher.write_u64(parent.cached_hash());
610    }
611    hasher.write_usize(return_states.len());
612    for return_state in return_states {
613        hasher.write_usize(*return_state);
614    }
615    hasher.finish()
616}
617
618/// Per-prediction memo for graph-structured stack merges.
619#[derive(Debug, Default)]
620pub struct PredictionContextMergeCache {
621    entries: FxHashMap<PredictionContextMergeKey, Rc<PredictionContext>>,
622}
623
624impl PredictionContextMergeCache {
625    pub fn new() -> Self {
626        Self::default()
627    }
628
629    fn get(
630        &self,
631        left: &Rc<PredictionContext>,
632        right: &Rc<PredictionContext>,
633    ) -> Option<Rc<PredictionContext>> {
634        let key = PredictionContextMergeKey::new(left, right);
635        self.entries.get(&key).cloned()
636    }
637
638    fn insert(
639        &mut self,
640        left: &Rc<PredictionContext>,
641        right: &Rc<PredictionContext>,
642        merged: &Rc<PredictionContext>,
643    ) {
644        self.entries.insert(
645            PredictionContextMergeKey::new(left, right),
646            Rc::clone(merged),
647        );
648    }
649}
650
651/// Shared canonical store for prediction-context graphs retained in DFA states.
652#[derive(Debug)]
653pub(crate) struct PredictionContextCache {
654    empty: Rc<PredictionContext>,
655    entries: FxHashMap<Rc<PredictionContext>, Rc<PredictionContext>>,
656}
657
658impl PredictionContextCache {
659    pub(crate) fn new() -> Self {
660        Self {
661            empty: PredictionContext::empty(),
662            entries: FxHashMap::default(),
663        }
664    }
665
666    pub(crate) fn get_cached_context(
667        &mut self,
668        context: &Rc<PredictionContext>,
669    ) -> Rc<PredictionContext> {
670        #[cfg(feature = "perf-counters")]
671        crate::perf::record_context_cache_call();
672        if context.is_empty() {
673            #[cfg(feature = "perf-counters")]
674            crate::perf::record_context_cache_empty();
675            return Rc::clone(&self.empty);
676        }
677        if let Some(existing) = self.entries.get(context) {
678            #[cfg(feature = "perf-counters")]
679            crate::perf::record_context_cache_hit();
680            return Rc::clone(existing);
681        }
682        #[cfg(feature = "perf-counters")]
683        crate::perf::record_context_cache_miss();
684        let mut visited = FxHashMap::default();
685        let cached = self.get_cached_context_inner(context, &mut visited);
686        #[cfg(feature = "perf-counters")]
687        crate::perf::record_context_cache_visited(visited.len());
688        cached
689    }
690
691    fn get_cached_context_inner(
692        &mut self,
693        context: &Rc<PredictionContext>,
694        visited: &mut FxHashMap<usize, Rc<PredictionContext>>,
695    ) -> Rc<PredictionContext> {
696        if context.is_empty() {
697            return Rc::clone(&self.empty);
698        }
699        // Key the per-traversal memo by pointer identity. We only need to detect
700        // the exact same node revisited within this canonicalization pass, so a
701        // pointer compare avoids recursive structural `PredictionContext::eq`.
702        let context_ptr = Rc::as_ptr(context) as usize;
703        if let Some(existing) = visited.get(&context_ptr) {
704            return Rc::clone(existing);
705        }
706        if let Some(existing) = self.entries.get(context) {
707            #[cfg(feature = "perf-counters")]
708            crate::perf::record_context_cache_hit();
709            let existing = Rc::clone(existing);
710            visited.insert(context_ptr, Rc::clone(&existing));
711            return existing;
712        }
713        let cached = match context.as_ref() {
714            PredictionContext::Empty { .. } => Rc::clone(&self.empty),
715            PredictionContext::Singleton {
716                parent,
717                return_state,
718                ..
719            } => {
720                let cached_parent = self.get_cached_context_inner(parent, visited);
721                if Rc::ptr_eq(parent, &cached_parent) {
722                    self.add(Rc::clone(context))
723                } else {
724                    self.add(PredictionContext::singleton(cached_parent, *return_state))
725                }
726            }
727            PredictionContext::Array {
728                parents,
729                return_states,
730                ..
731            } => {
732                let mut changed = false;
733                let mut cached_parents = Vec::with_capacity(parents.len());
734                for parent in parents {
735                    let cached_parent = self.get_cached_context_inner(parent, visited);
736                    changed |= !Rc::ptr_eq(parent, &cached_parent);
737                    cached_parents.push(cached_parent);
738                }
739                if changed {
740                    self.add(PredictionContext::array(
741                        cached_parents,
742                        return_states.clone(),
743                    ))
744                } else {
745                    self.add(Rc::clone(context))
746                }
747            }
748        };
749        visited.insert(context_ptr, Rc::clone(&cached));
750        cached
751    }
752
753    fn add(&mut self, context: Rc<PredictionContext>) -> Rc<PredictionContext> {
754        if context.is_empty() {
755            return Rc::clone(&self.empty);
756        }
757        if let Some(existing) = self.entries.get(&context) {
758            #[cfg(feature = "perf-counters")]
759            crate::perf::record_context_cache_hit();
760            return Rc::clone(existing);
761        }
762        #[cfg(feature = "perf-counters")]
763        crate::perf::record_context_cache_insert();
764        self.entries
765            .insert(Rc::clone(&context), Rc::clone(&context));
766        context
767    }
768}
769
770impl Default for PredictionContextCache {
771    fn default() -> Self {
772        Self::new()
773    }
774}
775
776#[derive(Clone, Debug)]
777struct PredictionContextMergeKey {
778    left: Rc<PredictionContext>,
779    right: Rc<PredictionContext>,
780    left_hash: u64,
781    right_hash: u64,
782}
783
784impl PredictionContextMergeKey {
785    fn new(left: &Rc<PredictionContext>, right: &Rc<PredictionContext>) -> Self {
786        let left_hash = prediction_context_hash(left);
787        let right_hash = prediction_context_hash(right);
788        if should_swap_merge_key(left, left_hash, right, right_hash) {
789            return Self {
790                left: Rc::clone(right),
791                right: Rc::clone(left),
792                left_hash: right_hash,
793                right_hash: left_hash,
794            };
795        }
796        Self {
797            left: Rc::clone(left),
798            right: Rc::clone(right),
799            left_hash,
800            right_hash,
801        }
802    }
803}
804
805fn should_swap_merge_key(
806    left: &Rc<PredictionContext>,
807    left_hash: u64,
808    right: &Rc<PredictionContext>,
809    right_hash: u64,
810) -> bool {
811    (right_hash, Rc::as_ptr(right) as usize) < (left_hash, Rc::as_ptr(left) as usize)
812}
813
814impl PartialEq for PredictionContextMergeKey {
815    fn eq(&self, other: &Self) -> bool {
816        self.left_hash == other.left_hash
817            && self.right_hash == other.right_hash
818            && self.left == other.left
819            && self.right == other.right
820    }
821}
822
823impl Eq for PredictionContextMergeKey {}
824
825impl Hash for PredictionContextMergeKey {
826    fn hash<H: Hasher>(&self, state: &mut H) {
827        state.write_u64(self.left_hash);
828        state.write_u64(self.right_hash);
829    }
830}
831
832fn prediction_context_hash(context: &Rc<PredictionContext>) -> u64 {
833    context.cached_hash()
834}
835
836#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
837pub enum SemanticContext {
838    None,
839    Predicate {
840        rule_index: usize,
841        pred_index: usize,
842        context_dependent: bool,
843    },
844    Precedence {
845        precedence: i32,
846    },
847    And(Vec<Self>),
848    Or(Vec<Self>),
849}
850
851impl SemanticContext {
852    pub const fn none() -> Self {
853        Self::None
854    }
855
856    pub fn and(left: Self, right: Self) -> Self {
857        combine_semantic_context(left, right, true)
858    }
859
860    pub fn or(left: Self, right: Self) -> Self {
861        combine_semantic_context(left, right, false)
862    }
863
864    pub const fn is_none(&self) -> bool {
865        matches!(self, Self::None)
866    }
867}
868
869fn combine_semantic_context(
870    left: SemanticContext,
871    right: SemanticContext,
872    and: bool,
873) -> SemanticContext {
874    if left == right {
875        return left;
876    }
877    if left.is_none() {
878        return right;
879    }
880    if right.is_none() {
881        return left;
882    }
883    let mut entries = Vec::new();
884    for context in [left, right] {
885        match (and, context) {
886            (true, SemanticContext::And(children)) | (false, SemanticContext::Or(children)) => {
887                entries.extend(children);
888            }
889            (_, other) => entries.push(other),
890        }
891    }
892    entries.sort();
893    entries.dedup();
894    if and {
895        SemanticContext::And(entries)
896    } else {
897        SemanticContext::Or(entries)
898    }
899}
900
901#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
902pub struct AtnConfig {
903    pub state: usize,
904    pub alt: usize,
905    pub context: Rc<PredictionContext>,
906    pub semantic_context: SemanticContext,
907    pub reaches_into_outer_context: usize,
908    pub precedence_filter_suppressed: bool,
909}
910
911impl AtnConfig {
912    pub const fn new(state: usize, alt: usize, context: Rc<PredictionContext>) -> Self {
913        Self {
914            state,
915            alt,
916            context,
917            semantic_context: SemanticContext::None,
918            reaches_into_outer_context: 0,
919            precedence_filter_suppressed: false,
920        }
921    }
922
923    #[must_use]
924    pub fn with_semantic_context(mut self, semantic_context: SemanticContext) -> Self {
925        self.semantic_context = semantic_context;
926        self
927    }
928
929    #[must_use]
930    pub const fn with_reaches_into_outer_context(mut self, reaches: usize) -> Self {
931        self.reaches_into_outer_context = reaches;
932        self
933    }
934}
935
936#[derive(Clone, Debug, Default)]
937pub struct AtnConfigSet {
938    configs: Vec<AtnConfig>,
939    config_index: FxHashMap<AtnConfigKey, usize>,
940    full_context: bool,
941    unique_alt: Option<usize>,
942    conflicting_alts: BTreeSet<usize>,
943    has_semantic_context: bool,
944    dips_into_outer_context: bool,
945    readonly: bool,
946}
947
948impl AtnConfigSet {
949    pub fn new() -> Self {
950        Self::default()
951    }
952
953    pub fn new_full_context(full_context: bool) -> Self {
954        Self {
955            configs: Vec::new(),
956            config_index: FxHashMap::default(),
957            full_context,
958            unique_alt: None,
959            conflicting_alts: BTreeSet::new(),
960            has_semantic_context: false,
961            dips_into_outer_context: false,
962            readonly: false,
963        }
964    }
965
966    pub fn add(&mut self, config: AtnConfig) -> bool {
967        self.add_with_merge_cache(config, None)
968    }
969
970    /// Adds a configuration, merging prediction contexts for equivalent
971    /// `(state, alt, semantic-context)` keys.
972    pub fn add_with_merge_cache(
973        &mut self,
974        config: AtnConfig,
975        cache: Option<&mut PredictionContextMergeCache>,
976    ) -> bool {
977        assert!(!self.readonly, "cannot mutate readonly ATN config set");
978        #[cfg(feature = "perf-counters")]
979        crate::perf::record_config_add_call();
980        if !config.semantic_context.is_none() {
981            self.has_semantic_context = true;
982        }
983        if config.reaches_into_outer_context > 0 {
984            self.dips_into_outer_context = true;
985        }
986        let key = AtnConfigKey::from(&config);
987        if let Some(existing_index) = self.config_index.get(&key).copied() {
988            #[cfg(feature = "perf-counters")]
989            crate::perf::record_config_merge();
990            let root_is_wildcard = !self.full_context;
991            let existing = &mut self.configs[existing_index];
992            existing.context = PredictionContext::merge_with_options(
993                Rc::clone(&existing.context),
994                config.context,
995                root_is_wildcard,
996                cache,
997            );
998            existing.reaches_into_outer_context = existing
999                .reaches_into_outer_context
1000                .max(config.reaches_into_outer_context);
1001            existing.precedence_filter_suppressed |= config.precedence_filter_suppressed;
1002            // Merging rewrites `existing.context`, which can change the
1003            // (state, context) groupings `conflicting_alts()` derives. Drop the
1004            // lazily-populated cache so a later call recomputes — mirroring the
1005            // insert branch's `self.conflicting_alts.clear()`. (All current callers
1006            // read `conflicting_alts()` only after the set is fully built, so this
1007            // is a no-op today; it keeps the two branches consistent and prevents a
1008            // stale read if a future caller interleaves reads with merges.)
1009            self.conflicting_alts.clear();
1010            false
1011        } else {
1012            let index = self.configs.len();
1013            self.config_index.insert(key, index);
1014            self.configs.push(config);
1015            #[cfg(feature = "perf-counters")]
1016            crate::perf::record_config_insert(self.configs.len());
1017            self.unique_alt = None;
1018            self.conflicting_alts.clear();
1019            true
1020        }
1021    }
1022
1023    pub fn configs(&self) -> &[AtnConfig] {
1024        &self.configs
1025    }
1026
1027    /// Consumes the set and returns its configs, letting callers drain configs
1028    /// by value (e.g. feeding `closure`, which takes `AtnConfig` by value)
1029    /// instead of cloning each one.
1030    pub(crate) fn into_configs(self) -> Vec<AtnConfig> {
1031        self.configs
1032    }
1033
1034    pub const fn is_empty(&self) -> bool {
1035        self.configs.is_empty()
1036    }
1037
1038    pub const fn len(&self) -> usize {
1039        self.configs.len()
1040    }
1041
1042    pub fn set_readonly(&mut self, readonly: bool) {
1043        self.readonly = readonly;
1044        if readonly {
1045            self.config_index.clear();
1046        }
1047    }
1048
1049    pub(crate) fn optimize_contexts(&mut self, cache: &mut PredictionContextCache) {
1050        assert!(!self.readonly, "cannot mutate readonly ATN config set");
1051        for config in &mut self.configs {
1052            config.context = cache.get_cached_context(&config.context);
1053        }
1054    }
1055
1056    pub const fn is_readonly(&self) -> bool {
1057        self.readonly
1058    }
1059
1060    pub const fn full_context(&self) -> bool {
1061        self.full_context
1062    }
1063
1064    pub const fn has_semantic_context(&self) -> bool {
1065        self.has_semantic_context
1066    }
1067
1068    pub const fn set_has_semantic_context(&mut self, value: bool) {
1069        self.has_semantic_context = value;
1070    }
1071
1072    pub const fn dips_into_outer_context(&self) -> bool {
1073        self.dips_into_outer_context
1074    }
1075
1076    pub fn unique_alt(&mut self) -> Option<usize> {
1077        if self.unique_alt.is_none() {
1078            self.unique_alt = unique_alt(self.configs());
1079        }
1080        self.unique_alt
1081    }
1082
1083    pub fn alts(&self) -> BTreeSet<usize> {
1084        self.configs.iter().map(|config| config.alt).collect()
1085    }
1086
1087    pub fn conflicting_alt_subsets(&self) -> Vec<BTreeSet<usize>> {
1088        conflicting_alt_subsets(self.configs())
1089    }
1090
1091    pub fn conflicting_alts(&mut self) -> BTreeSet<usize> {
1092        if self.conflicting_alts.is_empty() {
1093            self.conflicting_alts = self
1094                .conflicting_alt_subsets()
1095                .into_iter()
1096                .filter(|alts| alts.len() > 1)
1097                .flatten()
1098                .collect();
1099        }
1100        self.conflicting_alts.clone()
1101    }
1102}
1103
1104impl PartialEq for AtnConfigSet {
1105    fn eq(&self, other: &Self) -> bool {
1106        self.configs == other.configs
1107            && self.full_context == other.full_context
1108            && self.has_semantic_context == other.has_semantic_context
1109            && self.dips_into_outer_context == other.dips_into_outer_context
1110    }
1111}
1112
1113impl Eq for AtnConfigSet {}
1114
1115impl Ord for AtnConfigSet {
1116    fn cmp(&self, other: &Self) -> Ordering {
1117        self.configs
1118            .cmp(&other.configs)
1119            .then_with(|| self.full_context.cmp(&other.full_context))
1120            .then_with(|| self.has_semantic_context.cmp(&other.has_semantic_context))
1121            .then_with(|| {
1122                self.dips_into_outer_context
1123                    .cmp(&other.dips_into_outer_context)
1124            })
1125    }
1126}
1127
1128impl PartialOrd for AtnConfigSet {
1129    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1130        Some(self.cmp(other))
1131    }
1132}
1133
1134#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1135struct AtnConfigKey {
1136    state: usize,
1137    alt: usize,
1138    semantic_context: SemanticContext,
1139}
1140
1141impl From<&AtnConfig> for AtnConfigKey {
1142    fn from(config: &AtnConfig) -> Self {
1143        Self {
1144            state: config.state,
1145            alt: config.alt,
1146            semantic_context: config.semantic_context.clone(),
1147        }
1148    }
1149}
1150
1151pub fn unique_alt(configs: &[AtnConfig]) -> Option<usize> {
1152    let mut alt = None;
1153    for config in configs {
1154        match alt {
1155            None => alt = Some(config.alt),
1156            Some(existing) if existing == config.alt => {}
1157            Some(_) => return None,
1158        }
1159    }
1160    alt
1161}
1162
1163pub fn conflicting_alt_subsets(configs: &[AtnConfig]) -> Vec<BTreeSet<usize>> {
1164    // The subset order is irrelevant to every caller, so hash by the cheap
1165    // cached context hash instead of paying `BTreeMap`'s recursive
1166    // `PredictionContext::cmp` on every key comparison.
1167    let mut by_state_context =
1168        FxHashMap::<(usize, Rc<PredictionContext>), BTreeSet<usize>>::default();
1169    for config in configs {
1170        by_state_context
1171            .entry((config.state, Rc::clone(&config.context)))
1172            .or_default()
1173            .insert(config.alt);
1174    }
1175    by_state_context.into_values().collect()
1176}
1177
1178pub fn resolves_to_just_one_viable_alt(configs: &[AtnConfig]) -> Option<usize> {
1179    single_viable_alt(&conflicting_alt_subsets(configs))
1180}
1181
1182fn single_viable_alt(alt_subsets: &[BTreeSet<usize>]) -> Option<usize> {
1183    let mut result = None;
1184    for alts in alt_subsets {
1185        let min_alt = alts.iter().next().copied()?;
1186        match result {
1187            None => result = Some(min_alt),
1188            Some(existing) if existing == min_alt => {}
1189            Some(_) => return None,
1190        }
1191    }
1192    result
1193}
1194
1195pub fn has_sll_conflict_terminating_prediction(
1196    configs: &AtnConfigSet,
1197    is_rule_stop_state: impl Fn(usize) -> bool,
1198) -> bool {
1199    if configs
1200        .configs()
1201        .iter()
1202        .all(|config| is_rule_stop_state(config.state))
1203    {
1204        return true;
1205    }
1206    let alt_subsets = configs.conflicting_alt_subsets();
1207    alt_subsets.iter().any(|alts| alts.len() > 1)
1208        && !has_state_associated_with_one_alt(configs.configs())
1209}
1210
1211fn has_state_associated_with_one_alt(configs: &[AtnConfig]) -> bool {
1212    let mut by_state = BTreeMap::<usize, BTreeSet<usize>>::new();
1213    for config in configs {
1214        by_state.entry(config.state).or_default().insert(config.alt);
1215    }
1216    by_state.values().any(|alts| alts.len() == 1)
1217}
1218
1219#[cfg(test)]
1220mod tests {
1221    use super::*;
1222
1223    #[test]
1224    fn config_set_deduplicates_configs() {
1225        let empty = PredictionContext::empty();
1226        let mut set = AtnConfigSet::new();
1227        assert!(set.add(AtnConfig::new(1, 1, Rc::clone(&empty))));
1228        assert!(!set.add(AtnConfig::new(1, 1, Rc::clone(&empty))));
1229        assert_eq!(set.len(), 1);
1230    }
1231
1232    #[test]
1233    fn sll_conflict_does_not_stop_for_empty_contexts_alone() {
1234        let empty = PredictionContext::empty();
1235        let mut set = AtnConfigSet::new();
1236        set.add(AtnConfig::new(1, 1, Rc::clone(&empty)));
1237        set.add(AtnConfig::new(2, 2, empty));
1238
1239        assert!(!has_sll_conflict_terminating_prediction(&set, |_| false));
1240    }
1241
1242    #[test]
1243    fn sll_conflict_stops_when_all_configs_reached_rule_stop() {
1244        let empty = PredictionContext::empty();
1245        let mut set = AtnConfigSet::new();
1246        set.add(AtnConfig::new(10, 1, Rc::clone(&empty)));
1247        set.add(AtnConfig::new(11, 2, empty));
1248
1249        assert!(has_sll_conflict_terminating_prediction(&set, |state| {
1250            matches!(state, 10 | 11)
1251        }));
1252    }
1253
1254    #[test]
1255    fn viable_alt_resolves_to_shared_conflict_minimum() {
1256        let empty = PredictionContext::empty();
1257        let mut set = AtnConfigSet::new_full_context(true);
1258        set.add(AtnConfig::new(10, 1, Rc::clone(&empty)));
1259        set.add(AtnConfig::new(10, 2, Rc::clone(&empty)));
1260        set.add(AtnConfig::new(11, 1, empty));
1261
1262        assert_eq!(resolves_to_just_one_viable_alt(set.configs()), Some(1));
1263    }
1264
1265    #[test]
1266    fn viable_alt_keeps_looking_for_different_conflict_minimums() {
1267        let empty = PredictionContext::empty();
1268        let mut set = AtnConfigSet::new_full_context(true);
1269        set.add(AtnConfig::new(10, 1, Rc::clone(&empty)));
1270        set.add(AtnConfig::new(10, 2, Rc::clone(&empty)));
1271        set.add(AtnConfig::new(11, 2, Rc::clone(&empty)));
1272        set.add(AtnConfig::new(11, 3, empty));
1273
1274        assert_eq!(resolves_to_just_one_viable_alt(set.configs()), None);
1275    }
1276
1277    #[test]
1278    fn singleton_context_reports_parent_and_return_state() {
1279        let empty = PredictionContext::empty();
1280        let context = PredictionContext::singleton(Rc::clone(&empty), 42);
1281        assert_eq!(context.return_state(0), Some(42));
1282        assert_eq!(context.parent(0), Some(empty));
1283    }
1284
1285    #[test]
1286    fn merge_with_empty_preserves_non_empty_return_state() {
1287        let empty = PredictionContext::empty();
1288        let singleton = PredictionContext::singleton(Rc::clone(&empty), 42);
1289
1290        let merged = PredictionContext::merge(Rc::clone(&singleton), Rc::clone(&empty));
1291
1292        assert_eq!(merged.len(), 2);
1293        assert_eq!(merged.return_state(0), Some(42));
1294        assert_eq!(merged.parent(0), Some(empty.clone()));
1295        assert_eq!(merged.return_state(1), Some(EMPTY_RETURN_STATE));
1296        assert_eq!(merged.parent(1), Some(empty));
1297    }
1298
1299    #[test]
1300    fn merge_deduplicates_entries_with_same_parent_and_return_state() {
1301        let empty = PredictionContext::empty();
1302        let parent_one = PredictionContext::singleton(Rc::clone(&empty), 1);
1303        let parent_two = PredictionContext::singleton(Rc::clone(&empty), 2);
1304        let left = PredictionContext::array(vec![Rc::clone(&parent_one), parent_two], vec![42, 42]);
1305        let right = PredictionContext::singleton(Rc::clone(&parent_one), 42);
1306
1307        let merged = PredictionContext::merge(left, right);
1308
1309        assert_eq!(merged.len(), 2);
1310    }
1311
1312    #[test]
1313    fn merge_arrays_linearly_preserves_order_and_deduplicates_entries() {
1314        let empty = PredictionContext::empty();
1315        let parent_one = PredictionContext::singleton(Rc::clone(&empty), 1);
1316        let parent_two = PredictionContext::singleton(Rc::clone(&empty), 2);
1317        let parent_three = PredictionContext::singleton(Rc::clone(&empty), 3);
1318        let left = PredictionContext::array(
1319            vec![Rc::clone(&parent_one), Rc::clone(&parent_three)],
1320            vec![10, 30],
1321        );
1322        let right =
1323            PredictionContext::array(vec![parent_two, Rc::clone(&parent_three)], vec![20, 30]);
1324
1325        let merged = PredictionContext::merge(left, right);
1326
1327        assert_eq!(merged.len(), 3);
1328        assert_eq!(merged.return_state(0), Some(10));
1329        assert_eq!(merged.parent(0), Some(parent_one));
1330        assert_eq!(merged.return_state(1), Some(20));
1331        assert_eq!(merged.return_state(2), Some(30));
1332        assert_eq!(merged.parent(2), Some(parent_three));
1333    }
1334
1335    /// Builds a `Singleton` with a caller-chosen `cached_hash` so a test can force
1336    /// two structurally-distinct contexts to collide on their hash — the only way
1337    /// to reach the canonicalization hazard `compare_entries` guards against (a real
1338    /// 64-bit `cached_hash` collision is astronomically rare to produce organically).
1339    fn singleton_with_forced_hash(
1340        parent: Rc<PredictionContext>,
1341        return_state: usize,
1342        cached_hash: u64,
1343    ) -> Rc<PredictionContext> {
1344        Rc::new(PredictionContext::Singleton {
1345            parent,
1346            return_state,
1347            cached_hash,
1348        })
1349    }
1350
1351    #[test]
1352    fn merge_is_order_independent_under_hash_collision() {
1353        // Two structurally-different parents (return states 100 vs 200) forced to
1354        // share one `cached_hash`, both reached via the same outer return state 7.
1355        let empty = PredictionContext::empty();
1356        let parent_a = singleton_with_forced_hash(Rc::clone(&empty), 100, 0xDEAD_BEEF);
1357        let parent_b = singleton_with_forced_hash(Rc::clone(&empty), 200, 0xDEAD_BEEF);
1358        assert_ne!(parent_a, parent_b, "parents must be structurally distinct");
1359        assert_eq!(
1360            parent_a.cached_hash(),
1361            parent_b.cached_hash(),
1362            "test must force the hash collision"
1363        );
1364
1365        // singleton+singleton path (merge_two_context_entries): same return_state,
1366        // colliding-hash parents, merged in both orders.
1367        let left = PredictionContext::singleton(Rc::clone(&parent_a), 7);
1368        let right = PredictionContext::singleton(Rc::clone(&parent_b), 7);
1369        let merged_lr = PredictionContext::merge(Rc::clone(&left), Rc::clone(&right));
1370        let merged_rl = PredictionContext::merge(right, left);
1371        assert_eq!(
1372            merged_lr, merged_rl,
1373            "merge_two_context_entries must canonicalize regardless of operand order"
1374        );
1375
1376        // array+array path (merge_arrays): each operand carries one colliding-hash
1377        // entry at return_state 7 plus a shared non-colliding entry at 30, so the
1378        // merge walks the rs=7 group from both sides. The result must not depend on
1379        // operand order. (A shared trailing entry keeps both operands distinct so
1380        // `merge` does not short-circuit on `left == right`.)
1381        let shared = PredictionContext::singleton(Rc::clone(&empty), 30);
1382        let left_array = PredictionContext::array(
1383            vec![Rc::clone(&parent_a), Rc::clone(&shared)],
1384            vec![7, 30],
1385        );
1386        let right_array =
1387            PredictionContext::array(vec![Rc::clone(&parent_b), shared], vec![7, 30]);
1388        let merged_arrays_lr =
1389            PredictionContext::merge(Rc::clone(&left_array), Rc::clone(&right_array));
1390        let merged_arrays_rl = PredictionContext::merge(right_array, left_array);
1391        assert_eq!(
1392            merged_arrays_lr, merged_arrays_rl,
1393            "merge_arrays must canonicalize colliding-hash entries to one order"
1394        );
1395        assert_eq!(merged_arrays_lr.len(), 3, "two rs=7 entries + one rs=30");
1396    }
1397
1398    #[test]
1399    fn prediction_context_cache_reuses_equal_context_graphs() {
1400        let mut cache = PredictionContextCache::new();
1401        let left_parent = PredictionContext::singleton(PredictionContext::empty(), 1);
1402        let right_parent = PredictionContext::singleton(PredictionContext::empty(), 1);
1403        let left = PredictionContext::singleton(left_parent, 42);
1404        let right = PredictionContext::singleton(right_parent, 42);
1405
1406        let cached_left = cache.get_cached_context(&left);
1407        let cached_right = cache.get_cached_context(&right);
1408        let cached_left_parent = cached_left.parent(0).expect("singleton parent");
1409        let cached_right_parent = cached_right.parent(0).expect("singleton parent");
1410
1411        assert!(Rc::ptr_eq(&cached_left, &cached_right));
1412        assert!(Rc::ptr_eq(&cached_left_parent, &cached_right_parent));
1413    }
1414
1415    #[test]
1416    fn config_set_optimize_contexts_canonicalizes_contexts() {
1417        let mut cache = PredictionContextCache::new();
1418        let first = PredictionContext::singleton(PredictionContext::empty(), 7);
1419        let second = PredictionContext::singleton(PredictionContext::empty(), 7);
1420        let mut set = AtnConfigSet::new();
1421        set.add(AtnConfig::new(1, 1, first));
1422        set.add(AtnConfig::new(2, 2, second));
1423
1424        set.optimize_contexts(&mut cache);
1425
1426        assert!(Rc::ptr_eq(&set.configs[0].context, &set.configs[1].context));
1427    }
1428}