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