stack_graphs/
partial.rs

1// -*- coding: utf-8 -*-
2// ------------------------------------------------------------------------------------------------
3// Copyright © 2021, stack-graphs authors.
4// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6// ------------------------------------------------------------------------------------------------
7
8//! Partial paths are "snippets" of paths that we can precalculate for each file that we analyze.
9//!
10//! Stack graphs are _incremental_, since we can produce a subgraph for each file without having
11//! to look at the contents of any other file in the repo, or in any upstream or downstream
12//! dependencies.
13//!
14//! This is great, because it means that when we receive a new commit for a repository, we only
15//! have to examine, and generate new stack subgraphs for, the files that are changed as part of
16//! that commit.
17//!
18//! Having done that, one possible way to find name binding paths would be to load in all of the
19//! subgraphs for the files that belong to the current commit, union them together into the
20//! combined graph for that commit, and run the [path-finding algorithm][] on that combined graph.
21//! However, we think that this will require too much computation at query time.
22//!
23//! [path-finding algorithm]: ../paths/index.html
24//!
25//! Instead, we want to precompute parts of the path-finding algorithm, by calculating _partial
26//! paths_ for each file.  Because stack graphs have limited places where a path can cross from one
27//! file into another, we can calculate all of the possible partial paths that reach those
28//! “import/export” points.
29//!
30//! At query time, we can then load in the _partial paths_ for each file, instead of the files'
31//! full stack graph structure.  We can efficiently [concatenate][] partial paths together,
32//! producing the original "full" path that represents a name binding.
33//!
34//! [concatenate]: struct.PartialPath.html#method.concatenate
35
36use std::convert::TryFrom;
37use std::fmt::Display;
38use std::num::NonZeroU32;
39
40use controlled_option::ControlledOption;
41use controlled_option::Niche;
42use enumset::EnumSetType;
43use smallvec::SmallVec;
44
45use crate::arena::Deque;
46use crate::arena::DequeArena;
47use crate::arena::Handle;
48use crate::graph::Edge;
49use crate::graph::Node;
50use crate::graph::NodeID;
51use crate::graph::StackGraph;
52use crate::graph::Symbol;
53use crate::paths::PathResolutionError;
54use crate::utils::cmp_option;
55use crate::utils::equals_option;
56
57//-------------------------------------------------------------------------------------------------
58// Displaying stuff
59
60/// This trait only exists because:
61///
62///   - we need `Display` implementations that dereference arena handles from our `StackGraph` and
63///     `PartialPaths` bags o' crap,
64///   - many of our arena-managed types can handles to _other_ arena-managed data, which we need to
65///     recursively display as part of displaying the "outer" instance, and
66///   - in particular, we sometimes need `&mut` access to the `PartialPaths` arenas.
67///
68/// The borrow checker is not very happy with us having all of these constraints at the same time —
69/// in particular, the last one.
70///
71/// This trait gets around the problem by breaking up the display operation into two steps:
72///
73///   - First, each data instance has a chance to "prepare" itself with `&mut` access to whatever
74///     arenas it needs.  (Anything containing a `Deque`, for instance, uses this step to ensure
75///     that our copy of the deque is pointed in the right direction, since reversing requires
76///     `&mut` access to the arena.)
77///
78///   - Once everything has been prepared, we return a value that implements `Display`, and
79///     contains _non-mutable_ references to the arena.  Because our arena references are
80///     non-mutable, we don't run into any problems with the borrow checker while recursively
81///     displaying the contents of the data instance.
82trait DisplayWithPartialPaths {
83    fn prepare(&mut self, _graph: &StackGraph, _partials: &mut PartialPaths) {}
84
85    fn display_with(
86        &self,
87        graph: &StackGraph,
88        partials: &PartialPaths,
89        f: &mut std::fmt::Formatter,
90    ) -> std::fmt::Result;
91}
92
93/// Prepares and returns a `Display` implementation for a type `D` that implements
94/// `DisplayWithPartialPaths`.  We only require `&mut` access to the `PartialPath` arenas while
95/// creating the `Display` instance; the `Display` instance itself will only retain shared access
96/// to the arenas.
97fn display_with<'a, D>(
98    mut value: D,
99    graph: &'a StackGraph,
100    partials: &'a mut PartialPaths,
101) -> impl Display + 'a
102where
103    D: DisplayWithPartialPaths + 'a,
104{
105    value.prepare(graph, partials);
106    DisplayWithPartialPathsWrapper {
107        value,
108        graph,
109        partials,
110    }
111}
112
113/// Returns a `Display` implementation that you can use inside of your `display_with` method to
114/// display any recursive fields.  This assumes that the recursive fields have already been
115/// prepared.
116fn display_prepared<'a, D>(
117    value: D,
118    graph: &'a StackGraph,
119    partials: &'a PartialPaths,
120) -> impl Display + 'a
121where
122    D: DisplayWithPartialPaths + 'a,
123{
124    DisplayWithPartialPathsWrapper {
125        value,
126        graph,
127        partials,
128    }
129}
130
131#[doc(hidden)]
132struct DisplayWithPartialPathsWrapper<'a, D> {
133    value: D,
134    graph: &'a StackGraph,
135    partials: &'a PartialPaths,
136}
137
138impl<'a, D> Display for DisplayWithPartialPathsWrapper<'a, D>
139where
140    D: DisplayWithPartialPaths,
141{
142    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
143        self.value.display_with(self.graph, self.partials, f)
144    }
145}
146
147//-------------------------------------------------------------------------------------------------
148// Symbol stack variables
149
150/// Represents an unknown list of scoped symbols.
151#[repr(transparent)]
152#[derive(Clone, Copy, Debug, Eq, Hash, Niche, Ord, PartialEq, PartialOrd)]
153pub struct SymbolStackVariable(#[niche] NonZeroU32);
154
155impl SymbolStackVariable {
156    pub fn new(variable: u32) -> Option<SymbolStackVariable> {
157        NonZeroU32::new(variable).map(SymbolStackVariable)
158    }
159
160    /// Creates a new symbol stack variable.  This constructor is used when creating a new, empty
161    /// partial path, since there aren't any other variables that we need to be fresher than.
162    pub(crate) fn initial() -> SymbolStackVariable {
163        SymbolStackVariable(unsafe { NonZeroU32::new_unchecked(1) })
164    }
165
166    /// Applies an offset to this variable.
167    ///
168    /// When concatenating partial paths, we have to ensure that the left- and right-hand sides
169    /// have non-overlapping sets of variables.  To do this, we find the maximum value of any
170    /// variable on the left-hand side, and add this “offset” to the values of all of the variables
171    /// on the right-hand side.
172    pub fn with_offset(self, symbol_variable_offset: u32) -> SymbolStackVariable {
173        let offset_value = self.0.get() + symbol_variable_offset;
174        SymbolStackVariable(unsafe { NonZeroU32::new_unchecked(offset_value) })
175    }
176
177    pub(crate) fn as_u32(self) -> u32 {
178        self.0.get()
179    }
180
181    fn as_usize(self) -> usize {
182        self.0.get() as usize
183    }
184}
185
186impl Display for SymbolStackVariable {
187    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
188        write!(f, "%{}", self.0.get())
189    }
190}
191
192impl From<NonZeroU32> for SymbolStackVariable {
193    fn from(value: NonZeroU32) -> SymbolStackVariable {
194        SymbolStackVariable(value)
195    }
196}
197
198impl Into<u32> for SymbolStackVariable {
199    fn into(self) -> u32 {
200        self.0.get()
201    }
202}
203
204impl TryFrom<u32> for SymbolStackVariable {
205    type Error = ();
206    fn try_from(value: u32) -> Result<SymbolStackVariable, ()> {
207        let value = NonZeroU32::new(value).ok_or(())?;
208        Ok(SymbolStackVariable(value))
209    }
210}
211
212//-------------------------------------------------------------------------------------------------
213// Scope stack variables
214
215/// Represents an unknown list of exported scopes.
216#[repr(transparent)]
217#[derive(Clone, Copy, Debug, Eq, Hash, Niche, Ord, PartialEq, PartialOrd)]
218pub struct ScopeStackVariable(#[niche] NonZeroU32);
219
220impl ScopeStackVariable {
221    pub fn new(variable: u32) -> Option<ScopeStackVariable> {
222        NonZeroU32::new(variable).map(ScopeStackVariable)
223    }
224
225    /// Creates a new scope stack variable.  This constructor is used when creating a new, empty
226    /// partial path, since there aren't any other variables that we need to be fresher than.
227    fn initial() -> ScopeStackVariable {
228        ScopeStackVariable(unsafe { NonZeroU32::new_unchecked(1) })
229    }
230
231    /// Creates a new scope stack variable that is fresher than all other variables in a partial
232    /// path.  (You must calculate the maximum variable number already in use.)
233    fn fresher_than(max_used: u32) -> ScopeStackVariable {
234        ScopeStackVariable(unsafe { NonZeroU32::new_unchecked(max_used + 1) })
235    }
236
237    /// Applies an offset to this variable.
238    ///
239    /// When concatenating partial paths, we have to ensure that the left- and right-hand sides
240    /// have non-overlapping sets of variables.  To do this, we find the maximum value of any
241    /// variable on the left-hand side, and add this “offset” to the values of all of the variables
242    /// on the right-hand side.
243    pub fn with_offset(self, scope_variable_offset: u32) -> ScopeStackVariable {
244        let offset_value = self.0.get() + scope_variable_offset;
245        ScopeStackVariable(unsafe { NonZeroU32::new_unchecked(offset_value) })
246    }
247
248    pub(crate) fn as_u32(self) -> u32 {
249        self.0.get()
250    }
251
252    fn as_usize(self) -> usize {
253        self.0.get() as usize
254    }
255}
256
257impl Display for ScopeStackVariable {
258    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
259        write!(f, "${}", self.0.get())
260    }
261}
262
263impl From<NonZeroU32> for ScopeStackVariable {
264    fn from(value: NonZeroU32) -> ScopeStackVariable {
265        ScopeStackVariable(value)
266    }
267}
268
269impl Into<u32> for ScopeStackVariable {
270    fn into(self) -> u32 {
271        self.0.get()
272    }
273}
274
275impl TryFrom<u32> for ScopeStackVariable {
276    type Error = ();
277    fn try_from(value: u32) -> Result<ScopeStackVariable, ()> {
278        let value = NonZeroU32::new(value).ok_or(())?;
279        Ok(ScopeStackVariable(value))
280    }
281}
282
283//-------------------------------------------------------------------------------------------------
284// Partial symbol stacks
285
286/// A symbol with an unknown, but possibly empty, list of exported scopes attached to it.
287#[repr(C)]
288#[derive(Clone, Copy)]
289pub struct PartialScopedSymbol {
290    pub symbol: Handle<Symbol>,
291    // Note that not having an attached scope list is _different_ than having an empty attached
292    // scope list.
293    pub scopes: ControlledOption<PartialScopeStack>,
294}
295
296impl PartialScopedSymbol {
297    /// Applies an offset to this scoped symbol.
298    ///
299    /// When concatenating partial paths, we have to ensure that the left- and right-hand sides
300    /// have non-overlapping sets of variables.  To do this, we find the maximum value of any
301    /// variable on the left-hand side, and add this “offset” to the values of all of the variables
302    /// on the right-hand side.
303    pub fn with_offset(mut self, scope_variable_offset: u32) -> PartialScopedSymbol {
304        let scopes = self
305            .scopes
306            .into_option()
307            .map(|stack| stack.with_offset(scope_variable_offset));
308        self.scopes = ControlledOption::from_option(scopes);
309        self
310    }
311
312    /// Matches this precondition symbol against another, unifying its contents with an existing
313    /// set of bindings.
314    pub fn unify(
315        &mut self,
316        partials: &mut PartialPaths,
317        rhs: PartialScopedSymbol,
318        scope_bindings: &mut PartialScopeStackBindings,
319    ) -> Result<(), PathResolutionError> {
320        if self.symbol != rhs.symbol {
321            return Err(PathResolutionError::SymbolStackUnsatisfied);
322        }
323        match (self.scopes.into_option(), rhs.scopes.into_option()) {
324            (Some(lhs), Some(rhs)) => {
325                let unified = lhs.unify(partials, rhs, scope_bindings)?;
326                self.scopes = ControlledOption::some(unified);
327            }
328            (None, None) => {}
329            _ => return Err(PathResolutionError::SymbolStackUnsatisfied),
330        }
331        Ok(())
332    }
333
334    /// Returns whether two partial scoped symbols "match".  The symbols must be identical, and any
335    /// attached scopes must also match.
336    pub fn matches(self, partials: &mut PartialPaths, postcondition: PartialScopedSymbol) -> bool {
337        if self.symbol != postcondition.symbol {
338            return false;
339        }
340
341        // If one side has an attached scope but the other doesn't, then the scoped symbols don't
342        // match.
343        if self.scopes.is_none() != postcondition.scopes.is_none() {
344            return false;
345        }
346
347        // Otherwise, if both sides have an attached scope, they have to be compatible.
348        if let Some(precondition_scopes) = self.scopes.into_option() {
349            if let Some(postcondition_scopes) = postcondition.scopes.into_option() {
350                return precondition_scopes.matches(partials, postcondition_scopes);
351            }
352        }
353
354        true
355    }
356
357    /// Applies a set of bindings to this partial scoped symbol, producing a new scoped symbol.
358    pub fn apply_partial_bindings(
359        mut self,
360        partials: &mut PartialPaths,
361        scope_bindings: &PartialScopeStackBindings,
362    ) -> Result<PartialScopedSymbol, PathResolutionError> {
363        let scopes = match self.scopes.into_option() {
364            Some(scopes) => Some(scopes.apply_partial_bindings(partials, scope_bindings)?),
365            None => None,
366        };
367        self.scopes = scopes.into();
368        Ok(self)
369    }
370
371    pub fn equals(&self, partials: &mut PartialPaths, other: &PartialScopedSymbol) -> bool {
372        self.symbol == other.symbol
373            && equals_option(
374                self.scopes.into_option(),
375                other.scopes.into_option(),
376                |a, b| a.equals(partials, b),
377            )
378    }
379
380    pub fn cmp(
381        &self,
382        graph: &StackGraph,
383        partials: &mut PartialPaths,
384        other: &PartialScopedSymbol,
385    ) -> std::cmp::Ordering {
386        std::cmp::Ordering::Equal
387            .then_with(|| graph[self.symbol].cmp(&graph[other.symbol]))
388            .then_with(|| {
389                cmp_option(
390                    self.scopes.into_option(),
391                    other.scopes.into_option(),
392                    |a, b| a.cmp(partials, b),
393                )
394            })
395    }
396
397    pub fn display<'a>(
398        self,
399        graph: &'a StackGraph,
400        partials: &'a mut PartialPaths,
401    ) -> impl Display + 'a {
402        display_with(self, graph, partials)
403    }
404}
405
406impl DisplayWithPartialPaths for PartialScopedSymbol {
407    fn prepare(&mut self, graph: &StackGraph, partials: &mut PartialPaths) {
408        if let Some(mut scopes) = self.scopes.into_option() {
409            scopes.prepare(graph, partials);
410            self.scopes = scopes.into();
411        }
412    }
413
414    fn display_with(
415        &self,
416        graph: &StackGraph,
417        partials: &PartialPaths,
418        f: &mut std::fmt::Formatter,
419    ) -> std::fmt::Result {
420        if let Some(scopes) = self.scopes.into_option() {
421            write!(
422                f,
423                "{}/({})",
424                self.symbol.display(graph),
425                display_prepared(scopes, graph, partials)
426            )
427        } else {
428            write!(f, "{}", self.symbol.display(graph))
429        }
430    }
431}
432
433/// A pattern that might match against a symbol stack.  Consists of a (possibly empty) list of
434/// partial scoped symbols, along with an optional symbol stack variable.
435#[repr(C)]
436#[derive(Clone, Copy, Niche)]
437pub struct PartialSymbolStack {
438    #[niche]
439    symbols: Deque<PartialScopedSymbol>,
440    length: u32,
441    variable: ControlledOption<SymbolStackVariable>,
442}
443
444impl PartialSymbolStack {
445    /// Returns whether this partial symbol stack can match the empty symbol stack.
446    #[inline(always)]
447    pub fn can_match_empty(&self) -> bool {
448        self.symbols.is_empty()
449    }
450
451    /// Returns whether this partial symbol stack can _only_ match the empty symbol stack.
452    #[inline(always)]
453    pub fn can_only_match_empty(&self) -> bool {
454        self.symbols.is_empty() && self.variable.is_none()
455    }
456
457    /// Returns whether this partial symbol stack contains any symbols.
458    #[inline(always)]
459    pub fn contains_symbols(&self) -> bool {
460        !self.symbols.is_empty()
461    }
462
463    /// Returns whether this partial symbol stack has a symbol stack variable.
464    #[inline(always)]
465    pub fn has_variable(&self) -> bool {
466        self.variable.is_some()
467    }
468
469    #[inline(always)]
470    pub fn len(&self) -> usize {
471        self.length as usize
472    }
473
474    /// Returns an empty partial symbol stack.
475    pub fn empty() -> PartialSymbolStack {
476        PartialSymbolStack {
477            symbols: Deque::empty(),
478            length: 0,
479            variable: ControlledOption::none(),
480        }
481    }
482
483    /// Returns a partial symbol stack containing only a symbol stack variable.
484    pub fn from_variable(variable: SymbolStackVariable) -> PartialSymbolStack {
485        PartialSymbolStack {
486            symbols: Deque::empty(),
487            length: 0,
488            variable: ControlledOption::some(variable),
489        }
490    }
491
492    /// Returns whether this partial symbol stack is iterable in both directions without needing
493    /// mutable access to the arena.
494    pub fn have_reversal(&self, partials: &PartialPaths) -> bool {
495        self.symbols.have_reversal(&partials.partial_symbol_stacks)
496    }
497
498    /// Applies an offset to this partial symbol stack.
499    ///
500    /// When concatenating partial paths, we have to ensure that the left- and right-hand sides
501    /// have non-overlapping sets of variables.  To do this, we find the maximum value of any
502    /// variable on the left-hand side, and add this “offset” to the values of all of the variables
503    /// on the right-hand side.
504    pub fn with_offset(
505        mut self,
506        partials: &mut PartialPaths,
507        symbol_variable_offset: u32,
508        scope_variable_offset: u32,
509    ) -> PartialSymbolStack {
510        let mut result = match self.variable.into_option() {
511            Some(variable) => Self::from_variable(variable.with_offset(symbol_variable_offset)),
512            None => Self::empty(),
513        };
514        while let Some(symbol) = self.pop_front(partials) {
515            result.push_back(partials, symbol.with_offset(scope_variable_offset));
516        }
517        result
518    }
519
520    fn prepend(&mut self, partials: &mut PartialPaths, mut head: Deque<PartialScopedSymbol>) {
521        while let Some(head) = head.pop_back(&mut partials.partial_symbol_stacks).copied() {
522            self.push_front(partials, head);
523        }
524    }
525
526    /// Pushes a new [`PartialScopedSymbol`][] onto the front of this partial symbol stack.
527    pub fn push_front(&mut self, partials: &mut PartialPaths, symbol: PartialScopedSymbol) {
528        self.length += 1;
529        self.symbols
530            .push_front(&mut partials.partial_symbol_stacks, symbol);
531    }
532
533    /// Pushes a new [`PartialScopedSymbol`][] onto the back of this partial symbol stack.
534    pub fn push_back(&mut self, partials: &mut PartialPaths, symbol: PartialScopedSymbol) {
535        self.length += 1;
536        self.symbols
537            .push_back(&mut partials.partial_symbol_stacks, symbol);
538    }
539
540    /// Removes and returns the [`PartialScopedSymbol`][] at the front of this partial symbol
541    /// stack.  If the stack is empty, returns `None`.
542    pub fn pop_front(&mut self, partials: &mut PartialPaths) -> Option<PartialScopedSymbol> {
543        let result = self
544            .symbols
545            .pop_front(&mut partials.partial_symbol_stacks)
546            .copied();
547        if result.is_some() {
548            self.length -= 1;
549        }
550        result
551    }
552
553    /// Removes and returns the [`PartialScopedSymbol`][] at the back of this partial symbol stack.
554    /// If the stack is empty, returns `None`.
555    pub fn pop_back(&mut self, partials: &mut PartialPaths) -> Option<PartialScopedSymbol> {
556        let result = self
557            .symbols
558            .pop_back(&mut partials.partial_symbol_stacks)
559            .copied();
560        if result.is_some() {
561            self.length -= 1;
562        }
563        result
564    }
565
566    pub fn display<'a>(
567        self,
568        graph: &'a StackGraph,
569        partials: &'a mut PartialPaths,
570    ) -> impl Display + 'a {
571        display_with(self, graph, partials)
572    }
573
574    /// Returns whether two partial symbol stacks "match".  They must be the same length, and each
575    /// respective partial scoped symbol must match.
576    pub fn matches(mut self, partials: &mut PartialPaths, mut other: PartialSymbolStack) -> bool {
577        while let Some(self_element) = self.pop_front(partials) {
578            if let Some(other_element) = other.pop_front(partials) {
579                if !self_element.matches(partials, other_element) {
580                    return false;
581                }
582            } else {
583                // Stacks aren't the same length.
584                return false;
585            }
586        }
587        if other.contains_symbols() {
588            // Stacks aren't the same length.
589            return false;
590        }
591        self.variable.into_option() == other.variable.into_option()
592    }
593
594    /// Applies a set of bindings to this partial symbol stack, producing a new partial symbol
595    /// stack.
596    pub fn apply_partial_bindings(
597        mut self,
598        partials: &mut PartialPaths,
599        symbol_bindings: &PartialSymbolStackBindings,
600        scope_bindings: &PartialScopeStackBindings,
601    ) -> Result<PartialSymbolStack, PathResolutionError> {
602        // If this partial symbol stack ends in a variable, see if we have a binding for it.  If
603        // so, substitute that binding in.  If not, leave the variable as-is.
604        let mut result = match self.variable.into_option() {
605            Some(variable) => match symbol_bindings.get(variable) {
606                Some(bound) => bound,
607                None => PartialSymbolStack::from_variable(variable),
608            },
609            None => PartialSymbolStack::empty(),
610        };
611
612        // Then prepend all of the scoped symbols that appear at the beginning of this stack,
613        // applying the bindings to any attached scopes as well.
614        while let Some(partial_symbol) = self.pop_back(partials) {
615            let partial_symbol = partial_symbol.apply_partial_bindings(partials, scope_bindings)?;
616            result.push_front(partials, partial_symbol);
617        }
618        Ok(result)
619    }
620
621    /// Given two partial symbol stacks, returns the largest possible partial symbol stack such that
622    /// any symbol stack that satisfies the result also satisfies both inputs.  This takes into
623    /// account any existing variable assignments, and updates those variable assignments with
624    /// whatever constraints are necessary to produce a correct result.
625    ///
626    /// Note that this operation is commutative.  (Concatenating partial paths, defined in
627    /// [`PartialPath::concatenate`][], is not.)
628    pub fn unify(
629        self,
630        partials: &mut PartialPaths,
631        mut rhs: PartialSymbolStack,
632        symbol_bindings: &mut PartialSymbolStackBindings,
633        scope_bindings: &mut PartialScopeStackBindings,
634    ) -> Result<PartialSymbolStack, PathResolutionError> {
635        let mut lhs = self;
636
637        // First, look at the shortest common prefix of lhs and rhs, and verify that they match.
638        let mut head = Deque::empty();
639        while lhs.contains_symbols() && rhs.contains_symbols() {
640            let mut lhs_front = lhs.pop_front(partials).unwrap();
641            let rhs_front = rhs.pop_front(partials).unwrap();
642            lhs_front.unify(partials, rhs_front, scope_bindings)?;
643            head.push_back(&mut partials.partial_symbol_stacks, lhs_front);
644        }
645
646        // Now at most one stack still has symbols.  Zero, one, or both of them have variables.
647        // Let's do a case analysis on all of those possibilities.
648
649        // CASE 1:
650        // Both lhs and rhs have no more symbols.  The answer is always yes, and any variables that
651        // are present get bound.  (If both sides have variables, then one variable gets bound to
652        // the other, since both lhs and rhs will match _any other symbol stack_ at this point.  If
653        // only one side has a variable, then the variable gets bound to the empty stack.)
654        //
655        //     lhs           rhs
656        // ============  ============
657        //  ()            ()            => yes either
658        //  ()            () $2         => yes rhs, $2 => ()
659        //  () $1         ()            => yes lhs, $1 => ()
660        //  () $1         () $2         => yes lhs, $2 => $1
661        if !lhs.contains_symbols() && !rhs.contains_symbols() {
662            match (lhs.variable.into_option(), rhs.variable.into_option()) {
663                (None, None) => {
664                    lhs.prepend(partials, head);
665                    return Ok(lhs);
666                }
667                (None, Some(var)) => {
668                    symbol_bindings.add(
669                        partials,
670                        var,
671                        PartialSymbolStack::empty(),
672                        scope_bindings,
673                    )?;
674                    rhs.prepend(partials, head);
675                    return Ok(rhs);
676                }
677                (Some(var), None) => {
678                    symbol_bindings.add(
679                        partials,
680                        var,
681                        PartialSymbolStack::empty(),
682                        scope_bindings,
683                    )?;
684                    lhs.prepend(partials, head);
685                    return Ok(lhs);
686                }
687                (Some(lhs_var), Some(rhs_var)) => {
688                    symbol_bindings.add(
689                        partials,
690                        rhs_var,
691                        PartialSymbolStack::from_variable(lhs_var),
692                        scope_bindings,
693                    )?;
694                    lhs.prepend(partials, head);
695                    return Ok(lhs);
696                }
697            }
698        }
699
700        // CASE 2:
701        // One of the stacks contains symbols and the other doesn't, and the “empty” stack doesn't
702        // have a variable.  Since there's no variable on the empty side to capture the remaining
703        // content on the non-empty side, the answer is always no.
704        //
705        //     lhs           rhs
706        // ============  ============
707        //  ()            (stuff)       => NO
708        //  ()            (stuff) $2    => NO
709        //  (stuff)       ()            => NO
710        //  (stuff) $1    ()            => NO
711        if !lhs.contains_symbols() && lhs.variable.is_none() {
712            return Err(PathResolutionError::SymbolStackUnsatisfied);
713        }
714        if !rhs.contains_symbols() && rhs.variable.is_none() {
715            return Err(PathResolutionError::SymbolStackUnsatisfied);
716        }
717
718        // CASE 3:
719        // One of the stacks contains symbols and the other doesn't, and the “empty” stack _does_
720        // have a variable.  If both sides have the same variable, the answer is NO. Otherwise,
721        // the answer is YES, and the “empty” side's variable needs to capture the entirety of the
722        // non-empty side.
723        //
724        //     lhs           rhs
725        // ============  ============
726        //  (...) $1      (...) $1      => no
727        //  () $1         (stuff)       => yes rhs,  $1 => rhs
728        //  () $1         (stuff) $2    => yes rhs,  $1 => rhs
729        //  (stuff)       () $2         => yes lhs,  $2 => lhs
730        //  (stuff) $1    () $2         => yes lhs,  $2 => lhs
731        match (lhs.variable.into_option(), rhs.variable.into_option()) {
732            (Some(v1), Some(v2)) if v1 == v2 => {
733                return Err(PathResolutionError::ScopeStackUnsatisfied)
734            }
735            _ => {}
736        }
737        if lhs.contains_symbols() {
738            let rhs_variable = rhs.variable.into_option().unwrap();
739            symbol_bindings.add(partials, rhs_variable, lhs, scope_bindings)?;
740            lhs.prepend(partials, head);
741            return Ok(lhs);
742        }
743        if rhs.contains_symbols() {
744            let lhs_variable = lhs.variable.into_option().unwrap();
745            symbol_bindings.add(partials, lhs_variable, rhs, scope_bindings)?;
746            rhs.prepend(partials, head);
747            return Ok(rhs);
748        }
749
750        unreachable!();
751    }
752
753    pub fn equals(mut self, partials: &mut PartialPaths, mut other: PartialSymbolStack) -> bool {
754        while let Some(self_symbol) = self.pop_front(partials) {
755            if let Some(other_symbol) = other.pop_front(partials) {
756                if !self_symbol.equals(partials, &other_symbol) {
757                    return false;
758                }
759            } else {
760                return false;
761            }
762        }
763        if !other.symbols.is_empty() {
764            return false;
765        }
766        equals_option(
767            self.variable.into_option(),
768            other.variable.into_option(),
769            |a, b| a == b,
770        )
771    }
772
773    pub fn cmp(
774        mut self,
775        graph: &StackGraph,
776        partials: &mut PartialPaths,
777        mut other: PartialSymbolStack,
778    ) -> std::cmp::Ordering {
779        use std::cmp::Ordering;
780        while let Some(self_symbol) = self.pop_front(partials) {
781            if let Some(other_symbol) = other.pop_front(partials) {
782                match self_symbol.cmp(graph, partials, &other_symbol) {
783                    Ordering::Equal => continue,
784                    result @ _ => return result,
785                }
786            } else {
787                return Ordering::Greater;
788            }
789        }
790        if !other.symbols.is_empty() {
791            return Ordering::Less;
792        }
793        cmp_option(
794            self.variable.into_option(),
795            other.variable.into_option(),
796            |a, b| a.cmp(&b),
797        )
798    }
799
800    /// Returns an iterator over the contents of this partial symbol stack.
801    pub fn iter<'a>(
802        &self,
803        partials: &'a mut PartialPaths,
804    ) -> impl Iterator<Item = PartialScopedSymbol> + 'a {
805        self.symbols
806            .iter(&mut partials.partial_symbol_stacks)
807            .copied()
808    }
809
810    /// Returns an iterator over the contents of this partial symbol stack, with no guarantee
811    /// about the ordering of the elements.
812    pub fn iter_unordered<'a>(
813        &self,
814        partials: &'a PartialPaths,
815    ) -> impl Iterator<Item = PartialScopedSymbol> + 'a {
816        self.symbols
817            .iter_unordered(&partials.partial_symbol_stacks)
818            .copied()
819    }
820
821    pub fn variable(&self) -> Option<SymbolStackVariable> {
822        self.variable.clone().into_option()
823    }
824
825    fn ensure_both_directions(&mut self, partials: &mut PartialPaths) {
826        self.symbols
827            .ensure_backwards(&mut partials.partial_symbol_stacks);
828        self.symbols
829            .ensure_forwards(&mut partials.partial_symbol_stacks);
830    }
831
832    fn ensure_forwards(&mut self, partials: &mut PartialPaths) {
833        self.symbols
834            .ensure_forwards(&mut partials.partial_symbol_stacks);
835    }
836
837    /// Returns the largest value of any symbol stack variable in this partial symbol stack.
838    pub fn largest_symbol_stack_variable(&self) -> u32 {
839        self.variable
840            .into_option()
841            .map(SymbolStackVariable::as_u32)
842            .unwrap_or(0)
843    }
844
845    /// Returns the largest value of any scope stack variable in this partial symbol stack.
846    pub fn largest_scope_stack_variable(&self, partials: &PartialPaths) -> u32 {
847        // We don't have to check the postconditions, because it's not valid for a postcondition to
848        // refer to a variable that doesn't exist in the precondition.
849        self.iter_unordered(partials)
850            .filter_map(|symbol| symbol.scopes.into_option())
851            .filter_map(|scopes| scopes.variable.into_option())
852            .map(ScopeStackVariable::as_u32)
853            .max()
854            .unwrap_or(0)
855    }
856}
857
858impl DisplayWithPartialPaths for PartialSymbolStack {
859    fn prepare(&mut self, graph: &StackGraph, partials: &mut PartialPaths) {
860        // Ensure that our deque is pointed forwards while we still have a mutable reference to the
861        // arena.
862        self.symbols
863            .ensure_forwards(&mut partials.partial_symbol_stacks);
864        // And then prepare each symbol in the stack.
865        let mut symbols = self.symbols;
866        while let Some(mut symbol) = symbols
867            .pop_front(&mut partials.partial_symbol_stacks)
868            .copied()
869        {
870            symbol.prepare(graph, partials);
871        }
872    }
873
874    fn display_with(
875        &self,
876        graph: &StackGraph,
877        partials: &PartialPaths,
878        f: &mut std::fmt::Formatter,
879    ) -> std::fmt::Result {
880        for symbol in self.symbols.iter_reused(&partials.partial_symbol_stacks) {
881            symbol.display_with(graph, partials, f)?;
882        }
883        if let Some(variable) = self.variable.into_option() {
884            if self.symbols.is_empty() {
885                write!(f, "{}", variable)?;
886            } else {
887                write!(f, ",{}", variable)?;
888            }
889        }
890        Ok(())
891    }
892}
893
894//-------------------------------------------------------------------------------------------------
895// Partial scope stacks
896
897/// A pattern that might match against a scope stack.  Consists of a (possibly empty) list of
898/// exported scopes, along with an optional scope stack variable.
899#[repr(C)]
900#[derive(Clone, Copy, Niche)]
901pub struct PartialScopeStack {
902    #[niche]
903    scopes: Deque<Handle<Node>>,
904    length: u32,
905    variable: ControlledOption<ScopeStackVariable>,
906}
907
908impl PartialScopeStack {
909    /// Returns whether this partial scope stack can match the empty scope stack.
910    #[inline(always)]
911    pub fn can_match_empty(&self) -> bool {
912        self.scopes.is_empty()
913    }
914
915    /// Returns whether this partial scope stack can _only_ match the empty scope stack.
916    #[inline(always)]
917    pub fn can_only_match_empty(&self) -> bool {
918        self.scopes.is_empty() && self.variable.is_none()
919    }
920
921    /// Returns whether this partial scope stack contains any scopes.
922    #[inline(always)]
923    pub fn contains_scopes(&self) -> bool {
924        !self.scopes.is_empty()
925    }
926
927    /// Returns whether this partial scope stack has a scope stack variable.
928    #[inline(always)]
929    pub fn has_variable(&self) -> bool {
930        self.variable.is_some()
931    }
932
933    #[inline(always)]
934    pub fn len(&self) -> usize {
935        self.length as usize
936    }
937
938    /// Returns an empty partial scope stack.
939    pub fn empty() -> PartialScopeStack {
940        PartialScopeStack {
941            scopes: Deque::empty(),
942            length: 0,
943            variable: ControlledOption::none(),
944        }
945    }
946
947    /// Returns a partial scope stack containing only a scope stack variable.
948    pub fn from_variable(variable: ScopeStackVariable) -> PartialScopeStack {
949        PartialScopeStack {
950            scopes: Deque::empty(),
951            length: 0,
952            variable: ControlledOption::some(variable),
953        }
954    }
955
956    /// Returns whether this partial scope stack is iterable in both directions without needing
957    /// mutable access to the arena.
958    pub fn have_reversal(&self, partials: &PartialPaths) -> bool {
959        self.scopes.have_reversal(&partials.partial_scope_stacks)
960    }
961
962    /// Applies an offset to this partial scope stack.
963    ///
964    /// When concatenating partial paths, we have to ensure that the left- and right-hand sides
965    /// have non-overlapping sets of variables.  To do this, we find the maximum value of any
966    /// variable on the left-hand side, and add this “offset” to the values of all of the variables
967    /// on the right-hand side.
968    pub fn with_offset(mut self, scope_variable_offset: u32) -> PartialScopeStack {
969        match self.variable.into_option() {
970            Some(variable) => {
971                self.variable = ControlledOption::some(variable.with_offset(scope_variable_offset));
972            }
973            None => {}
974        };
975        self
976    }
977
978    /// Returns whether two partial scope stacks match exactly the same set of scope stacks.
979    pub fn matches(mut self, partials: &mut PartialPaths, mut other: PartialScopeStack) -> bool {
980        while let Some(self_element) = self.pop_front(partials) {
981            if let Some(other_element) = other.pop_front(partials) {
982                if self_element != other_element {
983                    return false;
984                }
985            } else {
986                // Stacks aren't the same length.
987                return false;
988            }
989        }
990        if other.contains_scopes() {
991            // Stacks aren't the same length.
992            return false;
993        }
994        self.variable.into_option() == other.variable.into_option()
995    }
996
997    /// Applies a set of partial scope stack bindings to this partial scope stack, producing a new
998    /// partial scope stack.
999    pub fn apply_partial_bindings(
1000        mut self,
1001        partials: &mut PartialPaths,
1002        scope_bindings: &PartialScopeStackBindings,
1003    ) -> Result<PartialScopeStack, PathResolutionError> {
1004        // If this partial scope stack ends in a variable, see if we have a binding for it.  If so,
1005        // substitute that binding in.  If not, leave the variable as-is.
1006        let mut result = match self.variable.into_option() {
1007            Some(variable) => match scope_bindings.get(variable) {
1008                Some(bound) => bound,
1009                None => PartialScopeStack::from_variable(variable),
1010            },
1011            None => PartialScopeStack::empty(),
1012        };
1013
1014        // Then prepend all of the scopes that appear at the beginning of this stack.
1015        while let Some(scope) = self.pop_back(partials) {
1016            result.push_front(partials, scope);
1017        }
1018        Ok(result)
1019    }
1020
1021    /// Given two partial scope stacks, returns the largest possible partial scope stack such that
1022    /// any scope stack that satisfies the result also satisfies both inputs.  This takes into
1023    /// account any existing variable assignments, and updates those variable assignments with
1024    /// whatever constraints are necessary to produce a correct result.
1025    ///
1026    /// Note that this operation is commutative.  (Concatenating partial paths, defined in
1027    /// [`PartialPath::concatenate`][], is not.)
1028    pub fn unify(
1029        self,
1030        partials: &mut PartialPaths,
1031        mut rhs: PartialScopeStack,
1032        bindings: &mut PartialScopeStackBindings,
1033    ) -> Result<PartialScopeStack, PathResolutionError> {
1034        let mut lhs = self;
1035        let original_rhs = rhs;
1036
1037        // First, look at the shortest common prefix of lhs and rhs, and verify that they match.
1038        while lhs.contains_scopes() && rhs.contains_scopes() {
1039            let lhs_front = lhs.pop_front(partials).unwrap();
1040            let rhs_front = rhs.pop_front(partials).unwrap();
1041            if lhs_front != rhs_front {
1042                return Err(PathResolutionError::ScopeStackUnsatisfied);
1043            }
1044        }
1045
1046        // Now at most one stack still has scopes.  Zero, one, or both of them have variables.
1047        // Let's do a case analysis on all of those possibilities.
1048
1049        // CASE 1:
1050        // Both lhs and rhs have no more scopes.  The answer is always yes, and any variables that
1051        // are present get bound.  (If both sides have variables, then one variable gets bound to
1052        // the other, since both lhs and rhs will match _any other scope stack_ at this point.  If
1053        // only one side has a variable, then the variable gets bound to the empty stack.)
1054        //
1055        //     lhs           rhs
1056        // ============  ============
1057        //  ()            ()            => yes either
1058        //  ()            () $2         => yes rhs, $2 => ()
1059        //  () $1         ()            => yes lhs, $1 => ()
1060        //  () $1         () $2         => yes lhs, $2 => $1
1061        if !lhs.contains_scopes() && !rhs.contains_scopes() {
1062            match (lhs.variable.into_option(), rhs.variable.into_option()) {
1063                (None, None) => return Ok(self),
1064                (None, Some(var)) => {
1065                    bindings.add(partials, var, PartialScopeStack::empty())?;
1066                    return Ok(original_rhs);
1067                }
1068                (Some(var), None) => {
1069                    bindings.add(partials, var, PartialScopeStack::empty())?;
1070                    return Ok(self);
1071                }
1072                (Some(lhs_var), Some(rhs_var)) => {
1073                    bindings.add(partials, rhs_var, PartialScopeStack::from_variable(lhs_var))?;
1074                    return Ok(self);
1075                }
1076            }
1077        }
1078
1079        // CASE 2:
1080        // One of the stacks contains scopes and the other doesn't, and the “empty” stack doesn't
1081        // have a variable.  Since there's no variable on the empty side to capture the remaining
1082        // content on the non-empty side, the answer is always no.
1083        //
1084        //     lhs           rhs
1085        // ============  ============
1086        //  ()            (stuff)       => NO
1087        //  ()            (stuff) $2    => NO
1088        //  (stuff)       ()            => NO
1089        //  (stuff) $1    ()            => NO
1090        if !lhs.contains_scopes() && lhs.variable.is_none() {
1091            return Err(PathResolutionError::ScopeStackUnsatisfied);
1092        }
1093        if !rhs.contains_scopes() && rhs.variable.is_none() {
1094            return Err(PathResolutionError::ScopeStackUnsatisfied);
1095        }
1096
1097        // CASE 3:
1098        // One of the stacks contains scopes and the other doesn't, and the “empty” stack _does_
1099        // have a variable.  If both sides have the same variable, the answer is NO. Otherwise,
1100        // the answer is YES, and the “empty” side's variable needs to capture the entirety of the
1101        // non-empty side.
1102        //
1103        //     lhs           rhs
1104        // ============  ============
1105        //  (...) $1      (...) $1      => no
1106        //  () $1         (stuff)       => yes rhs,  $1 => rhs
1107        //  () $1         (stuff) $2    => yes rhs,  $1 => rhs
1108        //  (stuff)       () $2         => yes lhs,  $2 => lhs
1109        //  (stuff) $1    () $2         => yes lhs,  $2 => lhs
1110        match (lhs.variable.into_option(), rhs.variable.into_option()) {
1111            (Some(v1), Some(v2)) if v1 == v2 => {
1112                return Err(PathResolutionError::ScopeStackUnsatisfied)
1113            }
1114            _ => {}
1115        }
1116        if lhs.contains_scopes() {
1117            let rhs_variable = rhs.variable.into_option().unwrap();
1118            bindings.add(partials, rhs_variable, lhs)?;
1119            return Ok(self);
1120        }
1121        if rhs.contains_scopes() {
1122            let lhs_variable = lhs.variable.into_option().unwrap();
1123            bindings.add(partials, lhs_variable, rhs)?;
1124            return Ok(original_rhs);
1125        }
1126
1127        unreachable!();
1128    }
1129
1130    /// Pushes a new [`Node`][] onto the front of this partial scope stack.  The node must be an
1131    /// _exported scope node_.
1132    ///
1133    /// [`Node`]: ../graph/enum.Node.html
1134    pub fn push_front(&mut self, partials: &mut PartialPaths, node: Handle<Node>) {
1135        self.length += 1;
1136        self.scopes
1137            .push_front(&mut partials.partial_scope_stacks, node);
1138    }
1139
1140    /// Pushes a new [`Node`][] onto the back of this partial scope stack.  The node must be an
1141    /// _exported scope node_.
1142    ///
1143    /// [`Node`]: ../graph/enum.Node.html
1144    pub fn push_back(&mut self, partials: &mut PartialPaths, node: Handle<Node>) {
1145        self.length += 1;
1146        self.scopes
1147            .push_back(&mut partials.partial_scope_stacks, node);
1148    }
1149
1150    /// Removes and returns the [`Node`][] at the front of this partial scope stack.  If the stack
1151    /// does not contain any exported scope nodes, returns `None`.
1152    pub fn pop_front(&mut self, partials: &mut PartialPaths) -> Option<Handle<Node>> {
1153        let result = self
1154            .scopes
1155            .pop_front(&mut partials.partial_scope_stacks)
1156            .copied();
1157        if result.is_some() {
1158            self.length -= 1;
1159        }
1160        result
1161    }
1162
1163    /// Removes and returns the [`Node`][] at the back of this partial scope stack.  If the stack
1164    /// does not contain any exported scope nodes, returns `None`.
1165    pub fn pop_back(&mut self, partials: &mut PartialPaths) -> Option<Handle<Node>> {
1166        let result = self
1167            .scopes
1168            .pop_back(&mut partials.partial_scope_stacks)
1169            .copied();
1170        if result.is_some() {
1171            self.length -= 1;
1172        }
1173        result
1174    }
1175
1176    /// Returns the scope stack variable at the end of this partial scope stack.  If the stack does
1177    /// not contain a scope stack variable, returns `None`.
1178    pub fn variable(&self) -> Option<ScopeStackVariable> {
1179        self.variable.into_option()
1180    }
1181
1182    pub fn equals(self, partials: &mut PartialPaths, other: PartialScopeStack) -> bool {
1183        self.scopes
1184            .equals_with(&mut partials.partial_scope_stacks, other.scopes, |a, b| {
1185                *a == *b
1186            })
1187            && equals_option(
1188                self.variable.into_option(),
1189                other.variable.into_option(),
1190                |a, b| a == b,
1191            )
1192    }
1193
1194    pub fn cmp(self, partials: &mut PartialPaths, other: PartialScopeStack) -> std::cmp::Ordering {
1195        std::cmp::Ordering::Equal
1196            .then_with(|| {
1197                self.scopes
1198                    .cmp_with(&mut partials.partial_scope_stacks, other.scopes, |a, b| {
1199                        a.cmp(b)
1200                    })
1201            })
1202            .then_with(|| {
1203                cmp_option(
1204                    self.variable.into_option(),
1205                    other.variable.into_option(),
1206                    |a, b| a.cmp(&b),
1207                )
1208            })
1209    }
1210
1211    /// Returns an iterator over the scopes in this partial scope stack.
1212    pub fn iter_scopes<'a>(
1213        &self,
1214        partials: &'a mut PartialPaths,
1215    ) -> impl Iterator<Item = Handle<Node>> + 'a {
1216        self.scopes
1217            .iter(&mut partials.partial_scope_stacks)
1218            .copied()
1219    }
1220
1221    /// Returns an iterator over the contents of this partial scope stack, with no guarantee
1222    /// about the ordering of the elements.
1223    pub fn iter_unordered<'a>(
1224        &self,
1225        partials: &'a PartialPaths,
1226    ) -> impl Iterator<Item = Handle<Node>> + 'a {
1227        self.scopes
1228            .iter_unordered(&partials.partial_scope_stacks)
1229            .copied()
1230    }
1231
1232    pub fn display<'a>(
1233        self,
1234        graph: &'a StackGraph,
1235        partials: &'a mut PartialPaths,
1236    ) -> impl Display + 'a {
1237        display_with(self, graph, partials)
1238    }
1239
1240    fn ensure_both_directions(&mut self, partials: &mut PartialPaths) {
1241        self.scopes
1242            .ensure_backwards(&mut partials.partial_scope_stacks);
1243        self.scopes
1244            .ensure_forwards(&mut partials.partial_scope_stacks);
1245    }
1246
1247    fn ensure_forwards(&mut self, partials: &mut PartialPaths) {
1248        self.scopes
1249            .ensure_forwards(&mut partials.partial_scope_stacks);
1250    }
1251
1252    /// Returns the largest value of any scope stack variable in this partial scope stack.
1253    pub fn largest_scope_stack_variable(&self) -> u32 {
1254        self.variable
1255            .into_option()
1256            .map(ScopeStackVariable::as_u32)
1257            .unwrap_or(0)
1258    }
1259}
1260
1261impl DisplayWithPartialPaths for PartialScopeStack {
1262    fn prepare(&mut self, _graph: &StackGraph, partials: &mut PartialPaths) {
1263        self.scopes
1264            .ensure_forwards(&mut partials.partial_scope_stacks);
1265    }
1266
1267    fn display_with(
1268        &self,
1269        graph: &StackGraph,
1270        partials: &PartialPaths,
1271        f: &mut std::fmt::Formatter,
1272    ) -> std::fmt::Result {
1273        let mut first = true;
1274        for scope in self.scopes.iter_reused(&partials.partial_scope_stacks) {
1275            if first {
1276                first = false;
1277            } else {
1278                write!(f, ",")?;
1279            }
1280            write!(f, "{:#}", scope.display(graph))?;
1281        }
1282        if let Some(variable) = self.variable.into_option() {
1283            if self.scopes.is_empty() {
1284                write!(f, "{}", variable)?;
1285            } else {
1286                write!(f, ",{}", variable)?;
1287            }
1288        }
1289        Ok(())
1290    }
1291}
1292
1293//-------------------------------------------------------------------------------------------------
1294// Partial symbol bindings
1295
1296pub struct PartialSymbolStackBindings {
1297    bindings: SmallVec<[Option<PartialSymbolStack>; 4]>,
1298}
1299
1300impl PartialSymbolStackBindings {
1301    /// Creates a new, empty set of partial symbol stack bindings.
1302    pub fn new() -> PartialSymbolStackBindings {
1303        PartialSymbolStackBindings {
1304            bindings: SmallVec::new(),
1305        }
1306    }
1307
1308    /// Returns the partial symbol stack that a particular symbol stack variable matched.  Returns an
1309    /// error if that variable didn't match anything.
1310    pub fn get(&self, variable: SymbolStackVariable) -> Option<PartialSymbolStack> {
1311        let index = variable.as_usize();
1312        if self.bindings.len() < index {
1313            return None;
1314        }
1315        self.bindings[index - 1]
1316    }
1317
1318    /// Adds a new binding from a symbol stack variable to the partial symbol stack that it
1319    /// matched.  Returns an error if you try to bind a particular variable more than once.
1320    pub fn add(
1321        &mut self,
1322        partials: &mut PartialPaths,
1323        variable: SymbolStackVariable,
1324        mut symbols: PartialSymbolStack,
1325        scope_bindings: &mut PartialScopeStackBindings,
1326    ) -> Result<(), PathResolutionError> {
1327        let index = variable.as_usize();
1328        if self.bindings.len() < index {
1329            self.bindings.resize_with(index, || None);
1330        }
1331        if let Some(old_binding) = self.bindings[index - 1] {
1332            symbols = symbols.unify(partials, old_binding, self, scope_bindings)?;
1333        }
1334        self.bindings[index - 1] = Some(symbols);
1335        Ok(())
1336    }
1337
1338    pub fn display<'a>(
1339        &'a mut self,
1340        graph: &'a StackGraph,
1341        partials: &'a mut PartialPaths,
1342    ) -> impl Display + 'a {
1343        display_with(self, graph, partials)
1344    }
1345}
1346
1347impl<'a> DisplayWithPartialPaths for &'a mut PartialSymbolStackBindings {
1348    fn prepare(&mut self, graph: &StackGraph, partials: &mut PartialPaths) {
1349        for binding in &mut self.bindings {
1350            if let Some(binding) = binding.as_mut() {
1351                binding.prepare(graph, partials);
1352            }
1353        }
1354    }
1355
1356    fn display_with(
1357        &self,
1358        graph: &StackGraph,
1359        partials: &PartialPaths,
1360        f: &mut std::fmt::Formatter,
1361    ) -> std::fmt::Result {
1362        write!(f, "{{")?;
1363        let mut first = true;
1364        for (idx, binding) in self.bindings.iter().enumerate() {
1365            if let Some(binding) = binding.as_ref() {
1366                if first {
1367                    first = false;
1368                } else {
1369                    write!(f, ", ")?;
1370                }
1371                write!(
1372                    f,
1373                    "%{} => <{}>",
1374                    idx + 1,
1375                    display_prepared(*binding, graph, partials)
1376                )?;
1377            }
1378        }
1379        write!(f, "}}")
1380    }
1381}
1382
1383//-------------------------------------------------------------------------------------------------
1384// Partial scope bindings
1385
1386pub struct PartialScopeStackBindings {
1387    bindings: SmallVec<[Option<PartialScopeStack>; 4]>,
1388}
1389
1390impl PartialScopeStackBindings {
1391    /// Creates a new, empty set of partial scope stack bindings.
1392    pub fn new() -> PartialScopeStackBindings {
1393        PartialScopeStackBindings {
1394            bindings: SmallVec::new(),
1395        }
1396    }
1397
1398    /// Returns the partial scope stack that a particular scope stack variable matched.  Returns an error
1399    /// if that variable didn't match anything.
1400    pub fn get(&self, variable: ScopeStackVariable) -> Option<PartialScopeStack> {
1401        let index = variable.as_usize();
1402        if self.bindings.len() < index {
1403            return None;
1404        }
1405        self.bindings[index - 1]
1406    }
1407
1408    /// Adds a new binding from a scope stack variable to the partial scope stack that it matched.  Returns
1409    /// an error if you try to bind a particular variable more than once.
1410    pub fn add(
1411        &mut self,
1412        partials: &mut PartialPaths,
1413        variable: ScopeStackVariable,
1414        mut scopes: PartialScopeStack,
1415    ) -> Result<(), PathResolutionError> {
1416        let index = variable.as_usize();
1417        if self.bindings.len() < index {
1418            self.bindings.resize_with(index, || None);
1419        }
1420        if let Some(old_binding) = self.bindings[index - 1] {
1421            scopes = scopes.unify(partials, old_binding, self)?;
1422        }
1423        self.bindings[index - 1] = Some(scopes);
1424        Ok(())
1425    }
1426
1427    pub fn display<'a>(
1428        &'a mut self,
1429        graph: &'a StackGraph,
1430        partials: &'a mut PartialPaths,
1431    ) -> impl Display + 'a {
1432        display_with(self, graph, partials)
1433    }
1434}
1435
1436impl<'a> DisplayWithPartialPaths for &'a mut PartialScopeStackBindings {
1437    fn prepare(&mut self, graph: &StackGraph, partials: &mut PartialPaths) {
1438        for binding in &mut self.bindings {
1439            if let Some(binding) = binding.as_mut() {
1440                binding.prepare(graph, partials);
1441            }
1442        }
1443    }
1444
1445    fn display_with(
1446        &self,
1447        graph: &StackGraph,
1448        partials: &PartialPaths,
1449        f: &mut std::fmt::Formatter,
1450    ) -> std::fmt::Result {
1451        write!(f, "{{")?;
1452        let mut first = true;
1453        for (idx, binding) in self.bindings.iter().enumerate() {
1454            if let Some(binding) = binding.as_ref() {
1455                if first {
1456                    first = false;
1457                } else {
1458                    write!(f, ", ")?;
1459                }
1460                write!(
1461                    f,
1462                    "${} => ({})",
1463                    idx + 1,
1464                    display_prepared(*binding, graph, partials)
1465                )?;
1466            }
1467        }
1468        write!(f, "}}")
1469    }
1470}
1471
1472//-------------------------------------------------------------------------------------------------
1473// Edge lists
1474
1475#[repr(C)]
1476#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
1477pub struct PartialPathEdge {
1478    pub source_node_id: NodeID,
1479    pub precedence: i32,
1480}
1481
1482impl PartialPathEdge {
1483    /// Returns whether one edge shadows another.  Note that shadowing is not commutative — if path
1484    /// A shadows path B, the reverse is not true.
1485    pub fn shadows(self, other: PartialPathEdge) -> bool {
1486        self.source_node_id == other.source_node_id && self.precedence > other.precedence
1487    }
1488
1489    pub fn display<'a>(
1490        self,
1491        graph: &'a StackGraph,
1492        partials: &'a mut PartialPaths,
1493    ) -> impl Display + 'a {
1494        display_with(self, graph, partials)
1495    }
1496}
1497
1498impl DisplayWithPartialPaths for PartialPathEdge {
1499    fn display_with(
1500        &self,
1501        graph: &StackGraph,
1502        _partials: &PartialPaths,
1503        f: &mut std::fmt::Formatter,
1504    ) -> std::fmt::Result {
1505        match graph.node_for_id(self.source_node_id) {
1506            Some(node) => write!(f, "{:#}", node.display(graph))?,
1507            None => write!(f, "[missing]")?,
1508        }
1509        if self.precedence != 0 {
1510            write!(f, "({})", self.precedence)?;
1511        }
1512        Ok(())
1513    }
1514}
1515
1516/// The edges in a path keep track of precedence information so that we can correctly handle
1517/// shadowed definitions.
1518#[repr(C)]
1519#[derive(Clone, Copy, Niche)]
1520pub struct PartialPathEdgeList {
1521    #[niche]
1522    edges: Deque<PartialPathEdge>,
1523    length: u32,
1524}
1525
1526impl PartialPathEdgeList {
1527    /// Returns whether this edge list is empty.
1528    #[inline(always)]
1529    pub fn is_empty(&self) -> bool {
1530        self.edges.is_empty()
1531    }
1532
1533    #[inline(always)]
1534    pub fn len(&self) -> usize {
1535        self.length as usize
1536    }
1537
1538    /// Returns whether this edge list is iterable in both directions without needing mutable
1539    /// access to the arena.
1540    pub fn have_reversal(&self, partials: &PartialPaths) -> bool {
1541        self.edges.have_reversal(&partials.partial_path_edges)
1542    }
1543
1544    /// Returns an empty edge list.
1545    pub fn empty() -> PartialPathEdgeList {
1546        PartialPathEdgeList {
1547            edges: Deque::empty(),
1548            length: 0,
1549        }
1550    }
1551
1552    /// Pushes a new edge onto the front of this edge list.
1553    pub fn push_front(&mut self, partials: &mut PartialPaths, edge: PartialPathEdge) {
1554        self.length += 1;
1555        self.edges
1556            .push_front(&mut partials.partial_path_edges, edge);
1557    }
1558
1559    /// Pushes a new edge onto the back of this edge list.
1560    pub fn push_back(&mut self, partials: &mut PartialPaths, edge: PartialPathEdge) {
1561        self.length += 1;
1562        self.edges.push_back(&mut partials.partial_path_edges, edge);
1563    }
1564
1565    /// Removes and returns the edge at the front of this edge list.  If the list is empty, returns
1566    /// `None`.
1567    pub fn pop_front(&mut self, partials: &mut PartialPaths) -> Option<PartialPathEdge> {
1568        let result = self.edges.pop_front(&mut partials.partial_path_edges);
1569        if result.is_some() {
1570            self.length -= 1;
1571        }
1572        result.copied()
1573    }
1574
1575    /// Removes and returns the edge at the back of this edge list.  If the list is empty, returns
1576    /// `None`.
1577    pub fn pop_back(&mut self, partials: &mut PartialPaths) -> Option<PartialPathEdge> {
1578        let result = self.edges.pop_back(&mut partials.partial_path_edges);
1579        if result.is_some() {
1580            self.length -= 1;
1581        }
1582        result.copied()
1583    }
1584
1585    pub fn display<'a>(
1586        self,
1587        graph: &'a StackGraph,
1588        partials: &'a mut PartialPaths,
1589    ) -> impl Display + 'a {
1590        display_with(self, graph, partials)
1591    }
1592
1593    /// Returns whether one edge list shadows another.  Note that shadowing is not commutative — if
1594    /// path A shadows path B, the reverse is not true.
1595    pub fn shadows(mut self, partials: &mut PartialPaths, mut other: PartialPathEdgeList) -> bool {
1596        while let Some(self_edge) = self.pop_front(partials) {
1597            if let Some(other_edge) = other.pop_front(partials) {
1598                if self_edge.source_node_id != other_edge.source_node_id {
1599                    return false;
1600                } else if self_edge.shadows(other_edge) {
1601                    return true;
1602                }
1603            } else {
1604                return false;
1605            }
1606        }
1607        false
1608    }
1609
1610    pub fn equals(mut self, partials: &mut PartialPaths, mut other: PartialPathEdgeList) -> bool {
1611        while let Some(self_edge) = self.pop_front(partials) {
1612            if let Some(other_edge) = other.pop_front(partials) {
1613                if self_edge != other_edge {
1614                    return false;
1615                }
1616            } else {
1617                return false;
1618            }
1619        }
1620        other.edges.is_empty()
1621    }
1622
1623    pub fn cmp(
1624        mut self,
1625        partials: &mut PartialPaths,
1626        mut other: PartialPathEdgeList,
1627    ) -> std::cmp::Ordering {
1628        use std::cmp::Ordering;
1629        while let Some(self_edge) = self.pop_front(partials) {
1630            if let Some(other_edge) = other.pop_front(partials) {
1631                match self_edge.cmp(&other_edge) {
1632                    Ordering::Equal => continue,
1633                    result @ _ => return result,
1634                }
1635            } else {
1636                return Ordering::Greater;
1637            }
1638        }
1639        if other.edges.is_empty() {
1640            Ordering::Equal
1641        } else {
1642            Ordering::Less
1643        }
1644    }
1645
1646    /// Returns an iterator over the contents of this edge list.
1647    pub fn iter<'a>(
1648        &self,
1649        partials: &'a mut PartialPaths,
1650    ) -> impl Iterator<Item = PartialPathEdge> + 'a {
1651        self.edges.iter(&mut partials.partial_path_edges).copied()
1652    }
1653
1654    /// Returns an iterator over the contents of this edge list, with no guarantee about the
1655    /// ordering of the elements.
1656    pub fn iter_unordered<'a>(
1657        &self,
1658        partials: &'a PartialPaths,
1659    ) -> impl Iterator<Item = PartialPathEdge> + 'a {
1660        self.edges
1661            .iter_unordered(&partials.partial_path_edges)
1662            .copied()
1663    }
1664
1665    fn ensure_both_directions(&mut self, partials: &mut PartialPaths) {
1666        self.edges
1667            .ensure_backwards(&mut partials.partial_path_edges);
1668        self.edges.ensure_forwards(&mut partials.partial_path_edges);
1669    }
1670
1671    fn ensure_forwards(&mut self, partials: &mut PartialPaths) {
1672        self.edges.ensure_forwards(&mut partials.partial_path_edges);
1673    }
1674}
1675
1676impl DisplayWithPartialPaths for PartialPathEdgeList {
1677    fn prepare(&mut self, graph: &StackGraph, partials: &mut PartialPaths) {
1678        self.edges.ensure_forwards(&mut partials.partial_path_edges);
1679        let mut edges = self.edges;
1680        while let Some(mut edge) = edges.pop_front(&mut partials.partial_path_edges).copied() {
1681            edge.prepare(graph, partials);
1682        }
1683    }
1684
1685    fn display_with(
1686        &self,
1687        graph: &StackGraph,
1688        partials: &PartialPaths,
1689        f: &mut std::fmt::Formatter,
1690    ) -> std::fmt::Result {
1691        for edge in self.edges.iter_reused(&partials.partial_path_edges) {
1692            edge.display_with(graph, partials, f)?;
1693        }
1694        Ok(())
1695    }
1696}
1697
1698//-------------------------------------------------------------------------------------------------
1699// Partial paths
1700
1701/// A portion of a name-binding path.
1702///
1703/// Partial paths can be computed _incrementally_, in which case all of the edges in the partial
1704/// path belong to a single file.  At query time, we can efficiently concatenate partial paths to
1705/// yield a name-binding path.
1706///
1707/// Paths describe the contents of the symbol stack and scope stack at the end of the path.
1708/// Partial paths, on the other hand, have _preconditions_ and _postconditions_ for each stack.
1709/// The precondition describes what the stack must look like for us to be able to concatenate this
1710/// partial path onto the end of a path.  The postcondition describes what the resulting stack
1711/// looks like after doing so.
1712///
1713/// The preconditions can contain _scope stack variables_, which describe parts of the scope stack
1714/// (or parts of a scope symbol's attached scope list) whose contents we don't care about.  The
1715/// postconditions can _also_ refer to those variables, and describe how those variable parts of
1716/// the input scope stacks are carried through unmodified into the resulting scope stack.
1717#[repr(C)]
1718#[derive(Clone)]
1719pub struct PartialPath {
1720    pub start_node: Handle<Node>,
1721    pub end_node: Handle<Node>,
1722    pub symbol_stack_precondition: PartialSymbolStack,
1723    pub symbol_stack_postcondition: PartialSymbolStack,
1724    pub scope_stack_precondition: PartialScopeStack,
1725    pub scope_stack_postcondition: PartialScopeStack,
1726    pub edges: PartialPathEdgeList,
1727}
1728
1729impl PartialPath {
1730    /// Creates a new empty partial path starting at a stack graph node.
1731    pub fn from_node(
1732        graph: &StackGraph,
1733        partials: &mut PartialPaths,
1734        node: Handle<Node>,
1735    ) -> PartialPath {
1736        let initial_symbol_stack = SymbolStackVariable::initial();
1737        let initial_scope_stack = ScopeStackVariable::initial();
1738        let mut symbol_stack_precondition = PartialSymbolStack::from_variable(initial_symbol_stack);
1739        let mut symbol_stack_postcondition =
1740            PartialSymbolStack::from_variable(initial_symbol_stack);
1741        let mut scope_stack_precondition = PartialScopeStack::from_variable(initial_scope_stack);
1742        let mut scope_stack_postcondition = PartialScopeStack::from_variable(initial_scope_stack);
1743
1744        graph[node]
1745            .append_to_partial_stacks(
1746                graph,
1747                partials,
1748                &mut symbol_stack_precondition,
1749                &mut scope_stack_precondition,
1750                &mut symbol_stack_postcondition,
1751                &mut scope_stack_postcondition,
1752            )
1753            .expect("lifting single nodes to partial paths should not fail");
1754
1755        PartialPath {
1756            start_node: node,
1757            end_node: node,
1758            symbol_stack_precondition,
1759            symbol_stack_postcondition,
1760            scope_stack_precondition,
1761            scope_stack_postcondition,
1762            edges: PartialPathEdgeList::empty(),
1763        }
1764    }
1765
1766    /// Returns whether one path shadows another.  Note that shadowing is not commutative — if path
1767    /// A shadows path B, the reverse is not true.
1768    pub fn shadows(&self, partials: &mut PartialPaths, other: &PartialPath) -> bool {
1769        self.edges.shadows(partials, other.edges)
1770    }
1771
1772    pub fn equals(&self, partials: &mut PartialPaths, other: &PartialPath) -> bool {
1773        self.start_node == other.start_node
1774            && self.end_node == other.end_node
1775            && self
1776                .symbol_stack_precondition
1777                .equals(partials, other.symbol_stack_precondition)
1778            && self
1779                .symbol_stack_postcondition
1780                .equals(partials, other.symbol_stack_postcondition)
1781            && self
1782                .scope_stack_precondition
1783                .equals(partials, other.scope_stack_precondition)
1784            && self
1785                .scope_stack_postcondition
1786                .equals(partials, other.scope_stack_postcondition)
1787    }
1788
1789    pub fn cmp(
1790        &self,
1791        graph: &StackGraph,
1792        partials: &mut PartialPaths,
1793        other: &PartialPath,
1794    ) -> std::cmp::Ordering {
1795        std::cmp::Ordering::Equal
1796            .then_with(|| self.start_node.cmp(&other.start_node))
1797            .then_with(|| self.end_node.cmp(&other.end_node))
1798            .then_with(|| {
1799                self.symbol_stack_precondition
1800                    .cmp(graph, partials, other.symbol_stack_precondition)
1801            })
1802            .then_with(|| {
1803                self.symbol_stack_postcondition.cmp(
1804                    graph,
1805                    partials,
1806                    other.symbol_stack_postcondition,
1807                )
1808            })
1809            .then_with(|| {
1810                self.scope_stack_precondition
1811                    .cmp(partials, other.scope_stack_precondition)
1812            })
1813            .then_with(|| {
1814                self.scope_stack_postcondition
1815                    .cmp(partials, other.scope_stack_postcondition)
1816            })
1817    }
1818
1819    /// Returns whether a partial path represents the start of a name binding from a reference to a
1820    /// definition.
1821    pub fn starts_at_reference(&self, graph: &StackGraph) -> bool {
1822        graph[self.start_node].is_reference()
1823            && self.symbol_stack_precondition.can_match_empty()
1824            && self.scope_stack_precondition.can_match_empty()
1825    }
1826
1827    /// Returns whether a partial path represents the end of a name binding from a reference to a
1828    /// definition.
1829    pub fn ends_at_definition(&self, graph: &StackGraph) -> bool {
1830        graph[self.end_node].is_definition() && self.symbol_stack_postcondition.can_match_empty()
1831    }
1832
1833    /// A _complete_ partial path represents a full name binding that resolves a reference to a
1834    /// definition.
1835    pub fn is_complete(&self, graph: &StackGraph) -> bool {
1836        self.starts_at_reference(graph) && self.ends_at_definition(graph)
1837    }
1838
1839    pub fn starts_at_endpoint(&self, graph: &StackGraph) -> bool {
1840        graph[self.start_node].is_endpoint()
1841    }
1842
1843    pub fn ends_at_endpoint(&self, graph: &StackGraph) -> bool {
1844        graph[self.end_node].is_endpoint()
1845    }
1846
1847    pub fn ends_in_jump(&self, graph: &StackGraph) -> bool {
1848        graph[self.end_node].is_jump_to()
1849    }
1850
1851    /// Returns whether a partial path is cyclic---that is, it starts and ends at the same node,
1852    /// and its postcondition is compatible with its precondition.  If the path is cyclic, a
1853    /// tuple is returned indicating whether cycle requires strengthening the pre- or postcondition.
1854    pub fn is_cyclic(&self, graph: &StackGraph, partials: &mut PartialPaths) -> Option<Cyclicity> {
1855        // StackGraph ensures that there are no nodes with duplicate IDs, so we can do a simple
1856        // comparison of node handles here.
1857        if self.start_node != self.end_node {
1858            return None;
1859        }
1860
1861        let lhs = self;
1862        let mut rhs = self.clone();
1863        rhs.ensure_no_overlapping_variables(partials, lhs);
1864
1865        let join = match Self::compute_join(graph, partials, lhs, &rhs) {
1866            Ok(join) => join,
1867            Err(_) => return None,
1868        };
1869
1870        if lhs
1871            .symbol_stack_precondition
1872            .variable
1873            .into_option()
1874            .map_or(false, |v| join.symbol_bindings.get(v).iter().len() > 0)
1875            || lhs
1876                .scope_stack_precondition
1877                .variable
1878                .into_option()
1879                .map_or(false, |v| join.scope_bindings.get(v).iter().len() > 0)
1880        {
1881            Some(Cyclicity::StrengthensPrecondition)
1882        } else if rhs
1883            .symbol_stack_postcondition
1884            .variable
1885            .into_option()
1886            .map_or(false, |v| join.symbol_bindings.get(v).iter().len() > 0)
1887            || rhs
1888                .scope_stack_postcondition
1889                .variable
1890                .into_option()
1891                .map_or(false, |v| join.scope_bindings.get(v).iter().len() > 0)
1892        {
1893            Some(Cyclicity::StrengthensPostcondition)
1894        } else {
1895            Some(Cyclicity::Free)
1896        }
1897    }
1898
1899    /// Ensures that the content of this partial path is available in both forwards and backwards
1900    /// directions.
1901    pub fn ensure_both_directions(&mut self, partials: &mut PartialPaths) {
1902        self.symbol_stack_precondition
1903            .ensure_both_directions(partials);
1904        self.symbol_stack_postcondition
1905            .ensure_both_directions(partials);
1906        self.scope_stack_precondition
1907            .ensure_both_directions(partials);
1908        self.scope_stack_postcondition
1909            .ensure_both_directions(partials);
1910        self.edges.ensure_both_directions(partials);
1911
1912        let mut stack = self.symbol_stack_precondition;
1913        while let Some(symbol) = stack.pop_front(partials) {
1914            if let Some(mut scopes) = symbol.scopes.into_option() {
1915                scopes.ensure_both_directions(partials);
1916            }
1917        }
1918
1919        let mut stack = self.symbol_stack_postcondition;
1920        while let Some(symbol) = stack.pop_front(partials) {
1921            if let Some(mut scopes) = symbol.scopes.into_option() {
1922                scopes.ensure_both_directions(partials);
1923            }
1924        }
1925    }
1926
1927    /// Ensures that the content of this partial path is in forwards direction.
1928    pub fn ensure_forwards(&mut self, partials: &mut PartialPaths) {
1929        self.symbol_stack_precondition.ensure_forwards(partials);
1930        self.symbol_stack_postcondition.ensure_forwards(partials);
1931        self.scope_stack_precondition.ensure_forwards(partials);
1932        self.scope_stack_postcondition.ensure_forwards(partials);
1933        self.edges.ensure_forwards(partials);
1934
1935        let mut stack = self.symbol_stack_precondition;
1936        while let Some(symbol) = stack.pop_front(partials) {
1937            if let Some(mut scopes) = symbol.scopes.into_option() {
1938                scopes.ensure_forwards(partials);
1939            }
1940        }
1941
1942        let mut stack = self.symbol_stack_postcondition;
1943        while let Some(symbol) = stack.pop_front(partials) {
1944            if let Some(mut scopes) = symbol.scopes.into_option() {
1945                scopes.ensure_forwards(partials);
1946            }
1947        }
1948    }
1949
1950    /// Returns the largest value of any symbol stack variable in this partial path.
1951    pub fn largest_symbol_stack_variable(&self) -> u32 {
1952        // We don't have to check the postconditions, because it's not valid for a postcondition to
1953        // refer to a variable that doesn't exist in the precondition.
1954        self.symbol_stack_precondition
1955            .largest_symbol_stack_variable()
1956    }
1957
1958    /// Returns the largest value of any scope stack variable in this partial path.
1959    pub fn largest_scope_stack_variable(&self, partials: &PartialPaths) -> u32 {
1960        Self::largest_scope_stack_variable_for_partial_stacks(
1961            partials,
1962            &self.symbol_stack_precondition,
1963            &self.scope_stack_precondition,
1964        )
1965    }
1966
1967    fn largest_scope_stack_variable_for_partial_stacks(
1968        partials: &PartialPaths,
1969        symbol_stack_precondition: &PartialSymbolStack,
1970        scope_stack_precondition: &PartialScopeStack,
1971    ) -> u32 {
1972        // We don't have to check the postconditions, because it's not valid for a postcondition to
1973        // refer to a variable that doesn't exist in the precondition.
1974        std::cmp::max(
1975            symbol_stack_precondition.largest_scope_stack_variable(partials),
1976            scope_stack_precondition.largest_scope_stack_variable(),
1977        )
1978    }
1979
1980    /// Returns a fresh scope stack variable that is not already used anywhere in this partial
1981    /// path.
1982    pub fn fresh_scope_stack_variable(&self, partials: &PartialPaths) -> ScopeStackVariable {
1983        Self::fresh_scope_stack_variable_for_partial_stack(
1984            partials,
1985            &self.symbol_stack_precondition,
1986            &self.scope_stack_precondition,
1987        )
1988    }
1989
1990    fn fresh_scope_stack_variable_for_partial_stack(
1991        partials: &PartialPaths,
1992        symbol_stack_precondition: &PartialSymbolStack,
1993        scope_stack_precondition: &PartialScopeStack,
1994    ) -> ScopeStackVariable {
1995        ScopeStackVariable::fresher_than(Self::largest_scope_stack_variable_for_partial_stacks(
1996            partials,
1997            symbol_stack_precondition,
1998            scope_stack_precondition,
1999        ))
2000    }
2001
2002    pub fn display<'a>(
2003        &'a self,
2004        graph: &'a StackGraph,
2005        partials: &'a mut PartialPaths,
2006    ) -> impl Display + 'a {
2007        display_with(self, graph, partials)
2008    }
2009}
2010
2011#[derive(Debug, EnumSetType)]
2012pub enum Cyclicity {
2013    /// The path can be freely concatenated to itself.
2014    Free,
2015    /// Concatenating the path to itself strengthens the precondition---symbols are eliminated from the stack.
2016    StrengthensPrecondition,
2017    /// Concatenating the path to itself strengthens the postcondition---symbols are introduced on the stack.
2018    StrengthensPostcondition,
2019}
2020
2021impl<'a> DisplayWithPartialPaths for &'a PartialPath {
2022    fn prepare(&mut self, graph: &StackGraph, partials: &mut PartialPaths) {
2023        self.symbol_stack_precondition
2024            .clone()
2025            .prepare(graph, partials);
2026        self.symbol_stack_postcondition
2027            .clone()
2028            .prepare(graph, partials);
2029        self.scope_stack_precondition
2030            .clone()
2031            .prepare(graph, partials);
2032        self.scope_stack_postcondition
2033            .clone()
2034            .prepare(graph, partials);
2035    }
2036
2037    fn display_with(
2038        &self,
2039        graph: &StackGraph,
2040        partials: &PartialPaths,
2041        f: &mut std::fmt::Formatter,
2042    ) -> std::fmt::Result {
2043        write!(
2044            f,
2045            "<{}> ({}) {} -> {} <{}> ({})",
2046            display_prepared(self.symbol_stack_precondition, graph, partials),
2047            display_prepared(self.scope_stack_precondition, graph, partials),
2048            self.start_node.display(graph),
2049            self.end_node.display(graph),
2050            display_prepared(self.symbol_stack_postcondition, graph, partials),
2051            display_prepared(self.scope_stack_postcondition, graph, partials),
2052        )
2053    }
2054}
2055
2056impl PartialPath {
2057    /// Modifies this partial path so that it has no symbol or scope stack variables in common with
2058    /// another partial path.
2059    pub fn ensure_no_overlapping_variables(
2060        &mut self,
2061        partials: &mut PartialPaths,
2062        other: &PartialPath,
2063    ) {
2064        let symbol_variable_offset = other.largest_symbol_stack_variable();
2065        let scope_variable_offset = other.largest_scope_stack_variable(partials);
2066        self.symbol_stack_precondition = self.symbol_stack_precondition.with_offset(
2067            partials,
2068            symbol_variable_offset,
2069            scope_variable_offset,
2070        );
2071        self.symbol_stack_postcondition = self.symbol_stack_postcondition.with_offset(
2072            partials,
2073            symbol_variable_offset,
2074            scope_variable_offset,
2075        );
2076        self.scope_stack_precondition = self
2077            .scope_stack_precondition
2078            .with_offset(scope_variable_offset);
2079        self.scope_stack_postcondition = self
2080            .scope_stack_postcondition
2081            .with_offset(scope_variable_offset);
2082    }
2083
2084    /// Replaces stack variables in the precondition with empty stacks.
2085    pub fn eliminate_precondition_stack_variables(&mut self, partials: &mut PartialPaths) {
2086        let mut symbol_bindings = PartialSymbolStackBindings::new();
2087        let mut scope_bindings = PartialScopeStackBindings::new();
2088        if let Some(symbol_variable) = self.symbol_stack_precondition.variable() {
2089            symbol_bindings
2090                .add(
2091                    partials,
2092                    symbol_variable,
2093                    PartialSymbolStack::empty(),
2094                    &mut scope_bindings,
2095                )
2096                .unwrap();
2097        }
2098        if let Some(scope_variable) = self.scope_stack_precondition.variable() {
2099            scope_bindings
2100                .add(partials, scope_variable, PartialScopeStack::empty())
2101                .unwrap();
2102        }
2103
2104        self.symbol_stack_precondition = self
2105            .symbol_stack_precondition
2106            .apply_partial_bindings(partials, &symbol_bindings, &scope_bindings)
2107            .unwrap();
2108        self.scope_stack_precondition = self
2109            .scope_stack_precondition
2110            .apply_partial_bindings(partials, &scope_bindings)
2111            .unwrap();
2112
2113        self.symbol_stack_postcondition = self
2114            .symbol_stack_postcondition
2115            .apply_partial_bindings(partials, &symbol_bindings, &scope_bindings)
2116            .unwrap();
2117        self.scope_stack_postcondition = self
2118            .scope_stack_postcondition
2119            .apply_partial_bindings(partials, &scope_bindings)
2120            .unwrap();
2121    }
2122
2123    /// Attempts to append an edge to the end of a partial path.  If the edge is not a valid
2124    /// extension of this partial path, we return an error describing why.
2125    pub fn append(
2126        &mut self,
2127        graph: &StackGraph,
2128        partials: &mut PartialPaths,
2129        edge: Edge,
2130    ) -> Result<(), PathResolutionError> {
2131        if edge.source != self.end_node {
2132            return Err(PathResolutionError::IncorrectSourceNode);
2133        }
2134
2135        graph[edge.sink].append_to_partial_stacks(
2136            graph,
2137            partials,
2138            &mut self.symbol_stack_precondition,
2139            &mut self.scope_stack_precondition,
2140            &mut self.symbol_stack_postcondition,
2141            &mut self.scope_stack_postcondition,
2142        )?;
2143
2144        self.end_node = edge.sink;
2145        self.edges.push_back(
2146            partials,
2147            PartialPathEdge {
2148                source_node_id: graph[edge.source].id(),
2149                precedence: edge.precedence,
2150            },
2151        );
2152
2153        self.resolve_from_postcondition(graph, partials)?;
2154
2155        Ok(())
2156    }
2157
2158    /// Attempts to resolve any _jump to scope_ node at the end of a partial path from the postcondition
2159    /// scope stack.  If the partial path does not end in a _jump to scope_ node, we do nothing.  If it
2160    /// does, and we cannot resolve it, then we return an error describing why.
2161    pub fn resolve_from_postcondition(
2162        &mut self,
2163        graph: &StackGraph,
2164        partials: &mut PartialPaths,
2165    ) -> Result<(), PathResolutionError> {
2166        if !graph[self.end_node].is_jump_to() {
2167            return Ok(());
2168        }
2169        if self.scope_stack_postcondition.can_only_match_empty() {
2170            return Err(PathResolutionError::EmptyScopeStack);
2171        }
2172        if !self.scope_stack_postcondition.contains_scopes() {
2173            return Ok(());
2174        }
2175        let top_scope = self.scope_stack_postcondition.pop_front(partials).unwrap();
2176        self.edges.push_back(
2177            partials,
2178            PartialPathEdge {
2179                source_node_id: graph[self.end_node].id(),
2180                precedence: 0,
2181            },
2182        );
2183        self.end_node = top_scope;
2184        Ok(())
2185    }
2186
2187    /// Resolve any _jump to scope_ node at the end of a partial path to the given node, updating the
2188    /// precondition to include the given node.  If the partial path does not end in a _jump to scope_
2189    /// node, we do nothing.  If it does, and we cannot resolve it, then we return an error describing
2190    /// why.
2191    pub fn resolve_to_node(
2192        &mut self,
2193        graph: &StackGraph,
2194        partials: &mut PartialPaths,
2195        node: Handle<Node>,
2196    ) -> Result<(), PathResolutionError> {
2197        if !graph[self.end_node].is_jump_to() {
2198            return Ok(());
2199        }
2200
2201        let scope_variable = match self.scope_stack_postcondition.variable() {
2202            Some(scope_variable) => scope_variable,
2203            None => return Err(PathResolutionError::ScopeStackUnsatisfied),
2204        };
2205        let mut scope_stack = PartialScopeStack::from_variable(scope_variable);
2206        scope_stack.push_front(partials, node);
2207
2208        let symbol_bindings = PartialSymbolStackBindings::new();
2209        let mut scope_bindings = PartialScopeStackBindings::new();
2210        scope_bindings
2211            .add(partials, scope_variable, scope_stack)
2212            .unwrap();
2213
2214        self.symbol_stack_precondition = self
2215            .symbol_stack_precondition
2216            .apply_partial_bindings(partials, &symbol_bindings, &scope_bindings)
2217            .unwrap();
2218        self.scope_stack_precondition = self
2219            .scope_stack_precondition
2220            .apply_partial_bindings(partials, &scope_bindings)
2221            .unwrap();
2222
2223        self.end_node = node;
2224
2225        Ok(())
2226    }
2227}
2228
2229impl Node {
2230    /// Update the given partial path pre- and postconditions with the effect of
2231    /// appending this node to that partial path.
2232    pub(crate) fn append_to_partial_stacks(
2233        &self,
2234        graph: &StackGraph,
2235        partials: &mut PartialPaths,
2236        symbol_stack_precondition: &mut PartialSymbolStack,
2237        scope_stack_precondition: &mut PartialScopeStack,
2238        symbol_stack_postcondition: &mut PartialSymbolStack,
2239        scope_stack_postcondition: &mut PartialScopeStack,
2240    ) -> Result<(), PathResolutionError> {
2241        match self {
2242            Self::DropScopes(_) => {
2243                *scope_stack_postcondition = PartialScopeStack::empty();
2244            }
2245            Self::JumpTo(_) => {}
2246            Self::PopScopedSymbol(sink) => {
2247                // Ideally we want to pop sink's scoped symbol off from top of the symbol stack
2248                // postcondition.
2249                if let Some(top) = symbol_stack_postcondition.pop_front(partials) {
2250                    if top.symbol != sink.symbol {
2251                        return Err(PathResolutionError::IncorrectPoppedSymbol);
2252                    }
2253                    let new_scope_stack = match top.scopes.into_option() {
2254                        Some(scopes) => scopes,
2255                        None => return Err(PathResolutionError::MissingAttachedScopeList),
2256                    };
2257                    *scope_stack_postcondition = new_scope_stack;
2258                } else if symbol_stack_postcondition.has_variable() {
2259                    // If the symbol stack postcondition is empty but has a variable, then we can update
2260                    // the _precondition_ to indicate that the symbol stack needs to contain this scoped
2261                    // symbol in order to successfully use this partial path.
2262                    let scope_stack_variable =
2263                        PartialPath::fresh_scope_stack_variable_for_partial_stack(
2264                            partials,
2265                            symbol_stack_precondition,
2266                            scope_stack_precondition,
2267                        );
2268                    let precondition_symbol = PartialScopedSymbol {
2269                        symbol: sink.symbol,
2270                        scopes: ControlledOption::some(PartialScopeStack::from_variable(
2271                            scope_stack_variable,
2272                        )),
2273                    };
2274                    // We simply push to the precondition. The official procedure here
2275                    // is to bind the postcondition symbol stack variable to the symbol
2276                    // and a fresh variable, and apply that. However, because the variable
2277                    // can only be bound in the precondition symbol stack, this amounts to
2278                    // pushing the symbol there directly.
2279                    symbol_stack_precondition.push_back(partials, precondition_symbol);
2280                    *scope_stack_postcondition =
2281                        PartialScopeStack::from_variable(scope_stack_variable);
2282                } else {
2283                    // The symbol stack postcondition is empty and has no variable, so we cannot
2284                    // perform the operation.
2285                    return Err(PathResolutionError::SymbolStackUnsatisfied);
2286                }
2287            }
2288            Self::PopSymbol(sink) => {
2289                // Ideally we want to pop sink's symbol off from top of the symbol stack postcondition.
2290                if let Some(top) = symbol_stack_postcondition.pop_front(partials) {
2291                    if top.symbol != sink.symbol {
2292                        return Err(PathResolutionError::IncorrectPoppedSymbol);
2293                    }
2294                    if top.scopes.is_some() {
2295                        return Err(PathResolutionError::UnexpectedAttachedScopeList);
2296                    }
2297                } else if symbol_stack_postcondition.has_variable() {
2298                    // If the symbol stack postcondition is empty but has a variable, then we can update
2299                    // the _precondition_ to indicate that the symbol stack needs to contain this symbol
2300                    // in order to successfully use this partial path.
2301                    let precondition_symbol = PartialScopedSymbol {
2302                        symbol: sink.symbol,
2303                        scopes: ControlledOption::none(),
2304                    };
2305                    // We simply push to the precondition. The official procedure here
2306                    // is to bind the postcondition symbol stack variable to the symbol
2307                    // and a fresh variable, and apply that. However, because the variable
2308                    // can only be bound in the precondition symbol stack, this amounts to
2309                    // pushing the symbol there directly.
2310                    symbol_stack_precondition.push_back(partials, precondition_symbol);
2311                } else {
2312                    // The symbol stack postcondition is empty and has no variable, so we cannot
2313                    // perform the operation.
2314                    return Err(PathResolutionError::SymbolStackUnsatisfied);
2315                }
2316            }
2317            Self::PushScopedSymbol(sink) => {
2318                // The symbol stack postcondition is our representation of the path's symbol stack.
2319                // Pushing the scoped symbol onto our postcondition indicates that using this partial
2320                // path would push the scoped symbol onto the path's symbol stack.
2321                let sink_symbol = sink.symbol;
2322                let sink_scope = graph
2323                    .node_for_id(sink.scope)
2324                    .ok_or(PathResolutionError::UnknownAttachedScope)?;
2325                let mut attached_scopes = scope_stack_postcondition.clone();
2326                attached_scopes.push_front(partials, sink_scope);
2327                let postcondition_symbol = PartialScopedSymbol {
2328                    symbol: sink_symbol,
2329                    scopes: ControlledOption::some(attached_scopes),
2330                };
2331                symbol_stack_postcondition.push_front(partials, postcondition_symbol);
2332            }
2333            Self::PushSymbol(sink) => {
2334                // The symbol stack postcondition is our representation of the path's symbol stack.
2335                // Pushing the symbol onto our postcondition indicates that using this partial path
2336                // would push the symbol onto the path's symbol stack.
2337                let sink_symbol = sink.symbol;
2338                let postcondition_symbol = PartialScopedSymbol {
2339                    symbol: sink_symbol,
2340                    scopes: ControlledOption::none(),
2341                };
2342                symbol_stack_postcondition.push_front(partials, postcondition_symbol);
2343            }
2344            Self::Root(_) => {}
2345            Self::Scope(_) => {}
2346        }
2347        Ok(())
2348    }
2349
2350    /// Ensure the given closed precondition stacks are half-open for this end node.
2351    ///
2352    /// Partial paths have closed (cf. a closed interval) pre- and postconditions, which means
2353    /// the start node is reflected in the precondition, and the end node is reflected in the
2354    /// postcondition. For example, a path starting with a pop node, has a precondition starting
2355    /// with the popped symbol. Similarly, a ending with a push node, has a postcondition ending
2356    /// with the pushed symbol.
2357    ///
2358    /// When concatenating two partial paths, their closed pre- and postconditions are not compatible,
2359    /// because the effect of the join node (i.e., the node shared between the two paths) is present
2360    /// in both the right and the left path. If two paths join at a push node, the right postcondition
2361    /// contains the pushed symbol, while the left precondition does not contain it, behaving as if the
2362    /// symbol was pushed twice. Similarly, when joining at a pop node, the right precondition contains
2363    /// the popped symbol, while the right postcondition will not anymore, because it was already popped.
2364    /// Unifying closed pre- and postconditions can result in incorrect concatenation results.
2365    ///
2366    /// We can make pre- and postconditions compatible again by making them half-open (cf. open intervals,
2367    /// but half because we only undo the effect of some node types). A precondition is half-open if it
2368    /// does not reflect the effect if a start pop node, Similarly, a postcondition is half-open if it
2369    /// does not reflect the effect of an end push node. Unifying half-open pre- and postconditions results
2370    /// in the correct behavior for path concatenation.
2371    fn halfopen_closed_partial_precondition(
2372        &self,
2373        partials: &mut PartialPaths,
2374        symbol_stack: &mut PartialSymbolStack,
2375        scope_stack: &mut PartialScopeStack,
2376    ) -> Result<(), PathResolutionError> {
2377        match self {
2378            Node::DropScopes(_) => {}
2379            Node::JumpTo(_) => {}
2380            Node::PopScopedSymbol(node) => {
2381                let symbol = symbol_stack
2382                    .pop_front(partials)
2383                    .ok_or(PathResolutionError::EmptySymbolStack)?;
2384                if symbol.symbol != node.symbol {
2385                    return Err(PathResolutionError::IncorrectPoppedSymbol);
2386                }
2387                *scope_stack = symbol.scopes.into_option().unwrap();
2388            }
2389            Node::PopSymbol(node) => {
2390                let symbol = symbol_stack
2391                    .pop_front(partials)
2392                    .ok_or(PathResolutionError::EmptySymbolStack)?;
2393                if symbol.symbol != node.symbol {
2394                    return Err(PathResolutionError::IncorrectPoppedSymbol);
2395                }
2396            }
2397            Node::PushScopedSymbol(_) => {}
2398            Node::PushSymbol(_) => {}
2399            Node::Root(_) => {}
2400            Node::Scope(_) => {}
2401        };
2402        Ok(())
2403    }
2404
2405    /// Ensure the given closed postcondition stacks are half-open for this start node.
2406    ///
2407    /// Partial paths have closed (cf. a closed interval) pre- and postconditions, which means
2408    /// the start node is reflected in the precondition, and the end node is reflected in the
2409    /// postcondition. For example, a path starting with a pop node, has a precondition starting
2410    /// with the popped symbol. Similarly, a ending with a push node, has a postcondition ending
2411    /// with the pushed symbol.
2412    ///
2413    /// When concatenating two partial paths, their closed pre- and postconditions are not compatible,
2414    /// because the effect of the join node (i.e., the node shared between the two paths) is present
2415    /// in both the right and the left path. If two paths join at a push node, the right postcondition
2416    /// contains the pushed symbol, while the left precondition does not contain it, behaving as if the
2417    /// symbol was pushed twice. Similarly, when joining at a pop node, the right precondition contains
2418    /// the popped symbol, while the right postcondition will not anymore, because it was already popped.
2419    /// Unifying closed pre- and postconditions can result in incorrect concatenation results.
2420    ///
2421    /// We can make pre- and postconditions compatible again by making them half-open (cf. open intervals,
2422    /// but half because we only undo the effect of some node types). A precondition is half-open if it
2423    /// does not reflect the effect if a start pop node, Similarly, a postcondition is half-open if it
2424    /// does not reflect the effect of an end push node. Unifying half-open pre- and postconditions results
2425    /// in the correct behavior for path concatenation.
2426    fn halfopen_closed_partial_postcondition(
2427        &self,
2428        partials: &mut PartialPaths,
2429        symbol_stack: &mut PartialSymbolStack,
2430        _scope_stack: &mut PartialScopeStack,
2431    ) -> Result<(), PathResolutionError> {
2432        match self {
2433            Self::DropScopes(_) => {}
2434            Self::JumpTo(_) => {}
2435            Self::PopScopedSymbol(_) => {}
2436            Self::PopSymbol(_) => {}
2437            Self::PushScopedSymbol(node) => {
2438                let symbol = symbol_stack
2439                    .pop_front(partials)
2440                    .ok_or(PathResolutionError::EmptySymbolStack)?;
2441                if symbol.symbol != node.symbol {
2442                    return Err(PathResolutionError::IncorrectPoppedSymbol);
2443                }
2444            }
2445            Self::PushSymbol(node) => {
2446                let symbol = symbol_stack
2447                    .pop_front(partials)
2448                    .ok_or(PathResolutionError::EmptySymbolStack)?;
2449                if symbol.symbol != node.symbol {
2450                    return Err(PathResolutionError::IncorrectPoppedSymbol);
2451                }
2452            }
2453            Self::Root(_) => {}
2454            Self::Scope(_) => {}
2455        };
2456        Ok(())
2457    }
2458}
2459
2460//-------------------------------------------------------------------------------------------------
2461// Extending partial paths with partial paths
2462
2463impl PartialPath {
2464    /// Attempts to append a partial path to this one.  If the postcondition of the “left” partial path
2465    /// is not compatible with the precondition of the “right” path, we return an error describing why.
2466    ///
2467    /// If the left- and right-hand partial paths have any symbol or scope stack variables in
2468    /// common, then we ensure that the variables bind to the same values on both sides.  It's your
2469    /// responsibility to update the two partial paths so that they have no variables in common, if
2470    /// that's needed for your use case.
2471    #[cfg_attr(not(feature = "copious-debugging"), allow(unused_variables))]
2472    pub fn concatenate(
2473        &mut self,
2474        graph: &StackGraph,
2475        partials: &mut PartialPaths,
2476        rhs: &PartialPath,
2477    ) -> Result<(), PathResolutionError> {
2478        let lhs = self;
2479
2480        #[cfg_attr(not(feature = "copious-debugging"), allow(unused_mut))]
2481        let mut join = Self::compute_join(graph, partials, lhs, rhs)?;
2482        #[cfg(feature = "copious-debugging")]
2483        {
2484            let unified_symbol_stack = join
2485                .unified_symbol_stack
2486                .display(graph, partials)
2487                .to_string();
2488            let unified_scope_stack = join
2489                .unified_scope_stack
2490                .display(graph, partials)
2491                .to_string();
2492            let symbol_bindings = join.symbol_bindings.display(graph, partials).to_string();
2493            let scope_bindings = join.scope_bindings.display(graph, partials).to_string();
2494            copious_debugging!(
2495                "       via <{}> ({}) {} {}",
2496                unified_symbol_stack,
2497                unified_scope_stack,
2498                symbol_bindings,
2499                scope_bindings,
2500            );
2501        }
2502
2503        lhs.symbol_stack_precondition = lhs.symbol_stack_precondition.apply_partial_bindings(
2504            partials,
2505            &join.symbol_bindings,
2506            &join.scope_bindings,
2507        )?;
2508        lhs.symbol_stack_postcondition = rhs.symbol_stack_postcondition.apply_partial_bindings(
2509            partials,
2510            &join.symbol_bindings,
2511            &join.scope_bindings,
2512        )?;
2513
2514        lhs.scope_stack_precondition = lhs
2515            .scope_stack_precondition
2516            .apply_partial_bindings(partials, &join.scope_bindings)?;
2517        lhs.scope_stack_postcondition = rhs
2518            .scope_stack_postcondition
2519            .apply_partial_bindings(partials, &join.scope_bindings)?;
2520
2521        let mut edges = rhs.edges;
2522        while let Some(edge) = edges.pop_front(partials) {
2523            lhs.edges.push_back(partials, edge);
2524        }
2525        lhs.end_node = rhs.end_node;
2526
2527        lhs.resolve_from_postcondition(graph, partials)?;
2528
2529        Ok(())
2530    }
2531
2532    /// Compute the bindings to join to partial paths. It is the caller's responsibility
2533    /// to ensure non-overlapping variables, if that is required.
2534    fn compute_join(
2535        graph: &StackGraph,
2536        partials: &mut PartialPaths,
2537        lhs: &PartialPath,
2538        rhs: &PartialPath,
2539    ) -> Result<Join, PathResolutionError> {
2540        if lhs.end_node != rhs.start_node {
2541            return Err(PathResolutionError::IncorrectSourceNode);
2542        }
2543
2544        // Ensure the right post- and left precondition are half-open, so we can unify them.
2545        let mut lhs_symbol_stack_postcondition = lhs.symbol_stack_postcondition;
2546        let mut lhs_scope_stack_postcondition = lhs.scope_stack_postcondition;
2547        let mut rhs_symbol_stack_precondition = rhs.symbol_stack_precondition;
2548        let mut rhs_scope_stack_precondition = rhs.scope_stack_precondition;
2549        graph[lhs.end_node]
2550            .halfopen_closed_partial_postcondition(
2551                partials,
2552                &mut lhs_symbol_stack_postcondition,
2553                &mut lhs_scope_stack_postcondition,
2554            )
2555            .unwrap_or_else(|e| {
2556                panic!(
2557                    "failed to halfopen postcondition of {}: {:?}",
2558                    lhs.display(graph, partials),
2559                    e
2560                );
2561            });
2562        graph[rhs.start_node]
2563            .halfopen_closed_partial_precondition(
2564                partials,
2565                &mut rhs_symbol_stack_precondition,
2566                &mut rhs_scope_stack_precondition,
2567            )
2568            .unwrap_or_else(|e| {
2569                panic!(
2570                    "failed to halfopen postcondition of {}: {:?}",
2571                    rhs.display(graph, partials),
2572                    e
2573                );
2574            });
2575
2576        let mut symbol_bindings = PartialSymbolStackBindings::new();
2577        let mut scope_bindings = PartialScopeStackBindings::new();
2578        let unified_symbol_stack = lhs_symbol_stack_postcondition.unify(
2579            partials,
2580            rhs_symbol_stack_precondition,
2581            &mut symbol_bindings,
2582            &mut scope_bindings,
2583        )?;
2584        let unified_scope_stack = lhs_scope_stack_postcondition.unify(
2585            partials,
2586            rhs_scope_stack_precondition,
2587            &mut scope_bindings,
2588        )?;
2589
2590        Ok(Join {
2591            unified_symbol_stack,
2592            unified_scope_stack,
2593            symbol_bindings,
2594            scope_bindings,
2595        })
2596    }
2597}
2598
2599struct Join {
2600    #[cfg_attr(not(feature = "copious-debugging"), allow(dead_code))]
2601    pub unified_symbol_stack: PartialSymbolStack,
2602    #[cfg_attr(not(feature = "copious-debugging"), allow(dead_code))]
2603    pub unified_scope_stack: PartialScopeStack,
2604    pub symbol_bindings: PartialSymbolStackBindings,
2605    pub scope_bindings: PartialScopeStackBindings,
2606}
2607
2608//-------------------------------------------------------------------------------------------------
2609// Partial path resolution state
2610
2611/// Manages the state of a collection of partial paths built up as part of the partial-path-finding
2612/// algorithm or path-stitching algorithm.
2613pub struct PartialPaths {
2614    pub(crate) partial_symbol_stacks: DequeArena<PartialScopedSymbol>,
2615    pub(crate) partial_scope_stacks: DequeArena<Handle<Node>>,
2616    pub(crate) partial_path_edges: DequeArena<PartialPathEdge>,
2617}
2618
2619impl PartialPaths {
2620    pub fn new() -> PartialPaths {
2621        PartialPaths {
2622            partial_symbol_stacks: Deque::new_arena(),
2623            partial_scope_stacks: Deque::new_arena(),
2624            partial_path_edges: Deque::new_arena(),
2625        }
2626    }
2627
2628    #[cfg_attr(not(feature = "storage"), allow(dead_code))]
2629    pub(crate) fn clear(&mut self) {
2630        self.partial_symbol_stacks.clear();
2631        self.partial_scope_stacks.clear();
2632        self.partial_path_edges.clear();
2633    }
2634}