cranelift_isle/
trie_again.rs

1//! A strongly-normalizing intermediate representation for ISLE rules. This representation is chosen
2//! to closely reflect the operations we can implement in Rust, to make code generation easy.
3use crate::disjointsets::DisjointSets;
4use crate::error::{Error, Span};
5use crate::lexer::Pos;
6use crate::sema;
7use crate::stablemapset::StableSet;
8use std::collections::{hash_map::Entry, HashMap};
9
10/// A field index in a tuple or an enum variant.
11#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
12pub struct TupleIndex(u8);
13/// A hash-consed identifier for a binding, stored in a [RuleSet].
14#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub struct BindingId(u16);
16
17impl std::convert::TryFrom<usize> for TupleIndex {
18    type Error = <u8 as std::convert::TryFrom<usize>>::Error;
19
20    fn try_from(value: usize) -> Result<Self, Self::Error> {
21        Ok(TupleIndex(value.try_into()?))
22    }
23}
24
25impl std::convert::TryFrom<usize> for BindingId {
26    type Error = <u16 as std::convert::TryFrom<usize>>::Error;
27
28    fn try_from(value: usize) -> Result<Self, Self::Error> {
29        Ok(BindingId(value.try_into()?))
30    }
31}
32
33impl TupleIndex {
34    /// Get the index of this field.
35    pub fn index(self) -> usize {
36        self.0.into()
37    }
38}
39
40impl BindingId {
41    /// Get the index of this id.
42    pub fn index(self) -> usize {
43        self.0.into()
44    }
45}
46
47/// Bindings are anything which can be bound to a variable name in Rust. This includes expressions,
48/// such as constants or function calls; but it also includes names bound in pattern matches.
49#[derive(Clone, Debug, Eq, Hash, PartialEq)]
50pub enum Binding {
51    /// Evaluates to the given boolean literal.
52    ConstBool {
53        /// The constant value.
54        val: bool,
55        /// The constant's type.
56        ty: sema::TypeId,
57    },
58    /// Evaluates to the given integer literal.
59    ConstInt {
60        /// The constant value.
61        val: i128,
62        /// The constant's type. Unsigned types preserve the representation of `val`, not its value.
63        ty: sema::TypeId,
64    },
65    /// Evaluates to the given primitive Rust value.
66    ConstPrim {
67        /// The constant value.
68        val: sema::Sym,
69    },
70    /// One of the arguments to the top-level function.
71    Argument {
72        /// Which of the function's arguments is this?
73        index: TupleIndex,
74    },
75    /// The result of calling an external extractor.
76    Extractor {
77        /// Which extractor should be called?
78        term: sema::TermId,
79        /// What expression should be passed to the extractor?
80        parameter: BindingId,
81    },
82    /// The result of calling an external constructor.
83    Constructor {
84        /// Which constructor should be called?
85        term: sema::TermId,
86        /// What expressions should be passed to the constructor?
87        parameters: Box<[BindingId]>,
88        /// For impure constructors, a unique number for each use of this term. Always 0 for pure
89        /// constructors.
90        instance: u32,
91    },
92    /// The result of getting one value from a multi-constructor or multi-extractor.
93    Iterator {
94        /// Which expression produced the iterator that this consumes?
95        source: BindingId,
96    },
97    /// The result of constructing an enum variant.
98    MakeVariant {
99        /// Which enum type should be constructed?
100        ty: sema::TypeId,
101        /// Which variant of that enum should be constructed?
102        variant: sema::VariantId,
103        /// What expressions should be provided for this variant's fields?
104        fields: Box<[BindingId]>,
105    },
106    /// Pattern-match one of the previous bindings against an enum variant and produce a new binding
107    /// from one of its fields. There must be a corresponding [Constraint::Variant] for each
108    /// `source`/`variant` pair that appears in some `MatchVariant` binding.
109    MatchVariant {
110        /// Which binding is being matched?
111        source: BindingId,
112        /// Which enum variant are we pulling binding sites from? This is somewhat redundant with
113        /// information in a corresponding [Constraint]. However, it must be here so that different
114        /// enum variants aren't hash-consed into the same binding site.
115        variant: sema::VariantId,
116        /// Which field of this enum variant are we projecting out? Although ISLE uses named fields,
117        /// we track them by index for constant-time comparisons. The [sema::TypeEnv] can be used to
118        /// get the field names.
119        field: TupleIndex,
120    },
121    /// The result of constructing an Option::Some variant.
122    MakeSome {
123        /// Contained expression.
124        inner: BindingId,
125    },
126    /// Pattern-match one of the previous bindings against `Option::Some` and produce a new binding
127    /// from its contents. There must be a corresponding [Constraint::Some] for each `source` that
128    /// appears in a `MatchSome` binding. (This currently only happens with external extractors.)
129    MatchSome {
130        /// Which binding is being matched?
131        source: BindingId,
132    },
133    /// Pattern-match one of the previous bindings against a tuple and produce a new binding from
134    /// one of its fields. This is an irrefutable pattern match so there is no corresponding
135    /// [Constraint]. (This currently only happens with external extractors.)
136    MatchTuple {
137        /// Which binding is being matched?
138        source: BindingId,
139        /// Which tuple field are we projecting out?
140        field: TupleIndex,
141    },
142}
143
144/// Pattern matches which can fail. Some binding sites are the result of successfully matching a
145/// constraint. A rule applies constraints to binding sites to determine whether the rule matches.
146#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
147pub enum Constraint {
148    /// The value must match this enum variant.
149    Variant {
150        /// Which enum type is being matched? This is implied by the binding where the constraint is
151        /// applied, but recorded here for convenience.
152        ty: sema::TypeId,
153        /// Which enum variant must this binding site match to satisfy the rule?
154        variant: sema::VariantId,
155        /// Number of fields in this variant of this enum. This is recorded in the constraint for
156        /// convenience, to avoid needing to look up the variant in a [sema::TypeEnv].
157        fields: TupleIndex,
158    },
159    /// The value must equal this boolean literal.
160    ConstBool {
161        /// The constant value.
162        val: bool,
163        /// The constant's type.
164        ty: sema::TypeId,
165    },
166    /// The value must equal this integer literal.
167    ConstInt {
168        /// The constant value.
169        val: i128,
170        /// The constant's type. Unsigned types preserve the representation of `val`, not its value.
171        ty: sema::TypeId,
172    },
173    /// The value must equal this Rust primitive value.
174    ConstPrim {
175        /// The constant value.
176        val: sema::Sym,
177    },
178    /// The value must be an `Option::Some`, from a fallible extractor.
179    Some,
180}
181
182/// A term-rewriting rule. All [BindingId]s are only meaningful in the context of the [RuleSet] that
183/// contains this rule.
184#[derive(Debug, Default)]
185pub struct Rule {
186    /// Where was this rule defined?
187    pub pos: Pos,
188    /// All of these bindings must match the given constraints for this rule to apply. Note that
189    /// within a single rule, if a binding site must match two different constraints, then the rule
190    /// can never match.
191    constraints: HashMap<BindingId, Constraint>,
192    /// Sets of bindings which must be equal for this rule to match.
193    pub equals: DisjointSets<BindingId>,
194    /// These bindings are from multi-terms which need to be evaluated in this rule.
195    pub iterators: StableSet<BindingId>,
196    /// If other rules apply along with this one, the one with the highest numeric priority is
197    /// evaluated. If multiple applicable rules have the same priority, that's an overlap error.
198    pub prio: i64,
199    /// If this rule applies, these side effects should be evaluated before returning.
200    pub impure: Vec<BindingId>,
201    /// If this rule applies, the top-level term should evaluate to this expression.
202    pub result: BindingId,
203}
204
205/// Records whether a given pair of rules can both match on some input.
206#[derive(Debug, Eq, PartialEq)]
207pub enum Overlap {
208    /// There is no input on which this pair of rules can both match.
209    No,
210    /// There is at least one input on which this pair of rules can both match.
211    Yes {
212        /// True if every input accepted by one rule is also accepted by the other. This does not
213        /// indicate which rule is more general and in fact the rules could match exactly the same
214        /// set of inputs. You can work out which by comparing `total_constraints()` in both rules:
215        /// The more general rule has fewer constraints.
216        subset: bool,
217    },
218}
219
220/// A collection of [Rule]s, along with hash-consed [Binding]s for all of them.
221#[derive(Debug, Default)]
222pub struct RuleSet {
223    /// The [Rule]s for a single [sema::Term].
224    pub rules: Vec<Rule>,
225    /// The bindings identified by [BindingId]s within rules.
226    pub bindings: Vec<Binding>,
227    /// Intern table for de-duplicating [Binding]s.
228    binding_map: HashMap<Binding, BindingId>,
229}
230
231/// Construct a [RuleSet] for each term in `termenv` that has rules.
232pub fn build(termenv: &sema::TermEnv) -> (Vec<(sema::TermId, RuleSet)>, Vec<Error>) {
233    let mut errors = Vec::new();
234    let mut term = HashMap::new();
235    for rule in termenv.rules.iter() {
236        term.entry(rule.root_term)
237            .or_insert_with(RuleSetBuilder::default)
238            .add_rule(rule, termenv, &mut errors);
239    }
240
241    // The `term` hash map may return terms in any order. Sort them to ensure that we produce the
242    // same output every time when given the same ISLE source. Rules are added to terms in `RuleId`
243    // order, so it's not necessary to sort within a `RuleSet`.
244    let mut result: Vec<_> = term
245        .into_iter()
246        .map(|(term, builder)| (term, builder.rules))
247        .collect();
248    result.sort_unstable_by_key(|(term, _)| *term);
249
250    (result, errors)
251}
252
253impl RuleSet {
254    /// Returns the [BindingId] corresponding to the given [Binding] within this rule-set, if any.
255    pub fn find_binding(&self, binding: &Binding) -> Option<BindingId> {
256        self.binding_map.get(binding).copied()
257    }
258}
259
260impl Binding {
261    /// Returns the binding sites which must be evaluated before this binding.
262    pub fn sources(&self) -> &[BindingId] {
263        match self {
264            Binding::ConstBool { .. } => &[][..],
265            Binding::ConstInt { .. } => &[][..],
266            Binding::ConstPrim { .. } => &[][..],
267            Binding::Argument { .. } => &[][..],
268            Binding::Extractor { parameter, .. } => std::slice::from_ref(parameter),
269            Binding::Constructor { parameters, .. } => &parameters[..],
270            Binding::Iterator { source } => std::slice::from_ref(source),
271            Binding::MakeVariant { fields, .. } => &fields[..],
272            Binding::MatchVariant { source, .. } => std::slice::from_ref(source),
273            Binding::MakeSome { inner } => std::slice::from_ref(inner),
274            Binding::MatchSome { source } => std::slice::from_ref(source),
275            Binding::MatchTuple { source, .. } => std::slice::from_ref(source),
276        }
277    }
278}
279
280impl Constraint {
281    /// Return the nested [Binding]s from matching the given [Constraint] against the given [BindingId].
282    pub fn bindings_for(self, source: BindingId) -> Vec<Binding> {
283        match self {
284            // These constraints never introduce any bindings.
285            Constraint::ConstBool { .. }
286            | Constraint::ConstInt { .. }
287            | Constraint::ConstPrim { .. } => vec![],
288            Constraint::Some => vec![Binding::MatchSome { source }],
289            Constraint::Variant {
290                variant, fields, ..
291            } => (0..fields.0)
292                .map(TupleIndex)
293                .map(|field| Binding::MatchVariant {
294                    source,
295                    variant,
296                    field,
297                })
298                .collect(),
299        }
300    }
301}
302
303impl Rule {
304    /// Returns whether a given pair of rules can both match on some input, and if so, whether
305    /// either matches a subset of the other's inputs. If this function returns `No`, then the two
306    /// rules definitely do not overlap. However, it may return `Yes` in cases where the rules can't
307    /// overlap in practice, or where this analysis is not yet precise enough to decide.
308    pub fn may_overlap(&self, other: &Rule) -> Overlap {
309        // Two rules can't overlap if, for some binding site in the intersection of their
310        // constraints, the rules have different constraints: an input can't possibly match both
311        // rules then. If the rules do overlap, and one has a subset of the constraints of the
312        // other, then the less-constrained rule matches every input that the more-constrained rule
313        // matches, and possibly more. We test for both conditions at once, with the observation
314        // that if the intersection of two sets is equal to the smaller set, then it's a subset. So
315        // the outer loop needs to go over the rule with fewer constraints in order to correctly
316        // identify if it's a subset of the other rule. Also, that way around is faster.
317        let (small, big) = if self.constraints.len() <= other.constraints.len() {
318            (self, other)
319        } else {
320            (other, self)
321        };
322
323        // TODO: nonlinear constraints complicate the subset check
324        // For the purpose of overlap checking, equality constraints act like other constraints, in
325        // that they can cause rules to not overlap. However, because we don't have a concrete
326        // pattern to compare, the analysis to prove that is complicated. For now, we approximate
327        // the result. If either rule has nonlinear constraints, conservatively report that neither
328        // is a subset of the other. Note that this does not disagree with the doc comment for
329        // `Overlap::Yes { subset }` which says to use `total_constraints` to disambiguate, since if
330        // we return `subset: true` here, `equals` is empty for both rules, so `total_constraints()`
331        // equals `constraints.len()`.
332        let mut subset = small.equals.is_empty() && big.equals.is_empty();
333
334        for (binding, a) in small.constraints.iter() {
335            if let Some(b) = big.constraints.get(binding) {
336                if a != b {
337                    // If any binding site is constrained differently by both rules then there is
338                    // no input where both rules can match.
339                    return Overlap::No;
340                }
341                // Otherwise both are constrained in the same way at this binding site. That doesn't
342                // rule out any possibilities for what inputs the rules accept.
343            } else {
344                // The `big` rule's inputs are a subset of the `small` rule's inputs if every
345                // constraint in `small` is exactly matched in `big`. But we found a counterexample.
346                subset = false;
347            }
348        }
349        Overlap::Yes { subset }
350    }
351
352    /// Returns the total number of binding sites which this rule constrains, with either a concrete
353    /// pattern or an equality constraint.
354    pub fn total_constraints(&self) -> usize {
355        // Because of `normalize_equivalence_classes`, these two sets don't overlap, so the size of
356        // the union is the sum of their sizes.
357        self.constraints.len() + self.equals.len()
358    }
359
360    /// Returns the constraint that the given binding site must satisfy for this rule to match, if
361    /// there is one.
362    pub fn get_constraint(&self, source: BindingId) -> Option<Constraint> {
363        self.constraints.get(&source).copied()
364    }
365
366    fn set_constraint(
367        &mut self,
368        source: BindingId,
369        constraint: Constraint,
370    ) -> Result<(), UnreachableError> {
371        match self.constraints.entry(source) {
372            Entry::Occupied(entry) => {
373                if entry.get() != &constraint {
374                    return Err(UnreachableError {
375                        pos: self.pos,
376                        constraint_a: *entry.get(),
377                        constraint_b: constraint,
378                    });
379                }
380            }
381            Entry::Vacant(entry) => {
382                entry.insert(constraint);
383            }
384        }
385        Ok(())
386    }
387}
388
389#[derive(Debug)]
390struct UnreachableError {
391    pos: Pos,
392    constraint_a: Constraint,
393    constraint_b: Constraint,
394}
395
396#[derive(Debug, Default)]
397struct RuleSetBuilder {
398    current_rule: Rule,
399    impure_instance: u32,
400    unreachable: Vec<UnreachableError>,
401    rules: RuleSet,
402}
403
404impl RuleSetBuilder {
405    fn add_rule(&mut self, rule: &sema::Rule, termenv: &sema::TermEnv, errors: &mut Vec<Error>) {
406        self.impure_instance = 0;
407        self.current_rule.pos = rule.pos;
408        self.current_rule.prio = rule.prio;
409        self.current_rule.result = rule.visit(self, termenv);
410        if termenv.terms[rule.root_term.index()].is_partial() {
411            self.current_rule.result = self.dedup_binding(Binding::MakeSome {
412                inner: self.current_rule.result,
413            });
414        }
415        self.normalize_equivalence_classes();
416        let rule = std::mem::take(&mut self.current_rule);
417
418        if self.unreachable.is_empty() {
419            self.rules.rules.push(rule);
420        } else {
421            // If this rule can never match, drop it so it doesn't affect overlap checking.
422            errors.extend(
423                self.unreachable
424                    .drain(..)
425                    .map(|err| Error::UnreachableError {
426                        msg: format!(
427                            "rule requires binding to match both {:?} and {:?}",
428                            err.constraint_a, err.constraint_b
429                        ),
430                        span: Span::new_single(err.pos),
431                    }),
432            )
433        }
434    }
435
436    /// Establish the invariant that a binding site can have a concrete constraint in `constraints`,
437    /// or an equality constraint in `equals`, but not both. This is useful because overlap checking
438    /// is most effective on concrete constraints, and also because it exposes more rule structure
439    /// for codegen.
440    ///
441    /// If a binding site is constrained and also required to be equal to another binding site, then
442    /// copy the constraint and push the equality inside it. For example:
443    /// - `(term x @ 2 x)` is rewritten to `(term 2 2)`
444    /// - `(term x @ (T.A _ _) x)` is rewritten to `(term (T.A y z) (T.A y z))`
445    ///
446    /// In the latter case, note that every field of `T.A` has been replaced with a fresh variable
447    /// and each of the copies are set equal.
448    ///
449    /// If several binding sites are supposed to be equal but they each have conflicting constraints
450    /// then this rule is unreachable. For example, `(term x @ 2 (and x 3))` requires both arguments
451    /// to be equal but also requires them to match both 2 and 3, which can't happen for any input.
452    ///
453    /// We could do this incrementally, while building the rule. The implementation is nearly
454    /// identical but, having tried both ways, it's slightly easier to think about this as a
455    /// separate pass. Also, batching up this work should be slightly faster if there are multiple
456    /// binding sites set equal to each other.
457    fn normalize_equivalence_classes(&mut self) {
458        // First, find all the constraints that need to be copied to other binding sites in their
459        // respective equivalence classes. Note: do not remove these constraints here! Yes, we'll
460        // put them back later, but we rely on still having them around so that
461        // `set_constraint` can detect conflicting constraints.
462        let mut deferred_constraints = Vec::new();
463        for (&binding, &constraint) in self.current_rule.constraints.iter() {
464            if let Some(root) = self.current_rule.equals.find_mut(binding) {
465                deferred_constraints.push((root, constraint));
466            }
467        }
468
469        // Pick one constraint and propagate it through its equivalence class. If there are no
470        // errors then it doesn't matter what order we do this in, because that means that any
471        // redundant constraints on an equivalence class were equal. We can write equal values into
472        // the constraint map in any order and get the same result. If there were errors, we aren't
473        // going to generate code from this rule, so order only affects how conflicts are reported.
474        while let Some((current, constraint)) = deferred_constraints.pop() {
475            // Remove the entire equivalence class and instead add copies of this constraint to
476            // every binding site in the class. If there are constraints on other binding sites in
477            // this class, then when we try to copy this constraint to those binding sites,
478            // `set_constraint` will check that the constraints are equal and record an appropriate
479            // error otherwise.
480            //
481            // Later, we'll re-visit those other binding sites because they're still in
482            // `deferred_constraints`, but `set` will be empty because we already deleted the
483            // equivalence class the first time we encountered it.
484            let set = self.current_rule.equals.remove_set_of(current);
485            if let Some((&base, rest)) = set.split_first() {
486                let mut defer = |this: &Self, binding| {
487                    // We're adding equality constraints to binding sites that may not have had
488                    // one already. If that binding site already had a concrete constraint, then
489                    // we need to "recursively" propagate that constraint through the new
490                    // equivalence class too.
491                    if let Some(constraint) = this.current_rule.get_constraint(binding) {
492                        deferred_constraints.push((binding, constraint));
493                    }
494                };
495
496                // If this constraint introduces nested binding sites, make the fields of those
497                // binding sites equal instead. Arbitrarily pick one member of `set` to set all the
498                // others equal to. If there are existing constraints on the new binding sites, copy
499                // those around the new equivalence classes too.
500                let base_fields = self.set_constraint(base, constraint);
501                base_fields.iter().for_each(|&x| defer(self, x));
502                for &b in rest {
503                    for (&x, y) in base_fields.iter().zip(self.set_constraint(b, constraint)) {
504                        defer(self, y);
505                        self.current_rule.equals.merge(x, y);
506                    }
507                }
508            }
509        }
510    }
511
512    fn dedup_binding(&mut self, binding: Binding) -> BindingId {
513        if let Some(binding) = self.rules.binding_map.get(&binding) {
514            *binding
515        } else {
516            let id = BindingId(self.rules.bindings.len().try_into().unwrap());
517            self.rules.bindings.push(binding.clone());
518            self.rules.binding_map.insert(binding, id);
519            id
520        }
521    }
522
523    fn set_constraint(&mut self, input: BindingId, constraint: Constraint) -> Vec<BindingId> {
524        if let Err(e) = self.current_rule.set_constraint(input, constraint) {
525            self.unreachable.push(e);
526        }
527        constraint
528            .bindings_for(input)
529            .into_iter()
530            .map(|binding| self.dedup_binding(binding))
531            .collect()
532    }
533}
534
535impl sema::PatternVisitor for RuleSetBuilder {
536    type PatternId = BindingId;
537
538    fn add_match_equal(&mut self, a: BindingId, b: BindingId, _ty: sema::TypeId) {
539        // If both bindings represent the same binding site, they're implicitly equal.
540        if a != b {
541            self.current_rule.equals.merge(a, b);
542        }
543    }
544
545    fn add_match_bool(&mut self, input: BindingId, ty: sema::TypeId, val: bool) {
546        let bindings = self.set_constraint(input, Constraint::ConstBool { val, ty });
547        debug_assert_eq!(bindings, &[]);
548    }
549
550    fn add_match_int(&mut self, input: BindingId, ty: sema::TypeId, val: i128) {
551        let bindings = self.set_constraint(input, Constraint::ConstInt { val, ty });
552        debug_assert_eq!(bindings, &[]);
553    }
554
555    fn add_match_prim(&mut self, input: BindingId, _ty: sema::TypeId, val: sema::Sym) {
556        let bindings = self.set_constraint(input, Constraint::ConstPrim { val });
557        debug_assert_eq!(bindings, &[]);
558    }
559
560    fn add_match_variant(
561        &mut self,
562        input: BindingId,
563        input_ty: sema::TypeId,
564        arg_tys: &[sema::TypeId],
565        variant: sema::VariantId,
566    ) -> Vec<BindingId> {
567        let fields = TupleIndex(arg_tys.len().try_into().unwrap());
568        self.set_constraint(
569            input,
570            Constraint::Variant {
571                fields,
572                ty: input_ty,
573                variant,
574            },
575        )
576    }
577
578    fn add_extract(
579        &mut self,
580        input: BindingId,
581        _input_ty: sema::TypeId,
582        output_tys: Vec<sema::TypeId>,
583        term: sema::TermId,
584        infallible: bool,
585        multi: bool,
586    ) -> Vec<BindingId> {
587        let source = self.dedup_binding(Binding::Extractor {
588            term,
589            parameter: input,
590        });
591
592        // If the extractor is fallible, build a pattern and constraint for `Some`
593        let source = if multi {
594            self.current_rule.iterators.insert(source);
595            self.dedup_binding(Binding::Iterator { source })
596        } else if infallible {
597            source
598        } else {
599            let bindings = self.set_constraint(source, Constraint::Some);
600            debug_assert_eq!(bindings.len(), 1);
601            bindings[0]
602        };
603
604        // If the extractor has multiple outputs, create a separate binding for each
605        match output_tys.len().try_into().unwrap() {
606            0 => vec![],
607            1 => vec![source],
608            outputs => (0..outputs)
609                .map(TupleIndex)
610                .map(|field| self.dedup_binding(Binding::MatchTuple { source, field }))
611                .collect(),
612        }
613    }
614}
615
616impl sema::ExprVisitor for RuleSetBuilder {
617    type ExprId = BindingId;
618
619    fn add_const_bool(&mut self, ty: sema::TypeId, val: bool) -> BindingId {
620        self.dedup_binding(Binding::ConstBool { val, ty })
621    }
622
623    fn add_const_int(&mut self, ty: sema::TypeId, val: i128) -> BindingId {
624        self.dedup_binding(Binding::ConstInt { val, ty })
625    }
626
627    fn add_const_prim(&mut self, _ty: sema::TypeId, val: sema::Sym) -> BindingId {
628        self.dedup_binding(Binding::ConstPrim { val })
629    }
630
631    fn add_create_variant(
632        &mut self,
633        inputs: Vec<(BindingId, sema::TypeId)>,
634        ty: sema::TypeId,
635        variant: sema::VariantId,
636    ) -> BindingId {
637        self.dedup_binding(Binding::MakeVariant {
638            ty,
639            variant,
640            fields: inputs.into_iter().map(|(expr, _)| expr).collect(),
641        })
642    }
643
644    fn add_construct(
645        &mut self,
646        inputs: Vec<(BindingId, sema::TypeId)>,
647        _ty: sema::TypeId,
648        term: sema::TermId,
649        pure: bool,
650        infallible: bool,
651        multi: bool,
652    ) -> BindingId {
653        let instance = if pure {
654            0
655        } else {
656            self.impure_instance += 1;
657            self.impure_instance
658        };
659        let source = self.dedup_binding(Binding::Constructor {
660            term,
661            parameters: inputs.into_iter().map(|(expr, _)| expr).collect(),
662            instance,
663        });
664
665        // If the constructor is fallible, build a pattern for `Some`, but not a constraint. If the
666        // constructor is on the right-hand side of a rule then its failure is not considered when
667        // deciding which rule to evaluate. Corresponding constraints are only added if this
668        // expression is subsequently used as a pattern; see `expr_as_pattern`.
669        let source = if multi {
670            self.current_rule.iterators.insert(source);
671            self.dedup_binding(Binding::Iterator { source })
672        } else if infallible {
673            source
674        } else {
675            self.dedup_binding(Binding::MatchSome { source })
676        };
677
678        if !pure {
679            self.current_rule.impure.push(source);
680        }
681
682        source
683    }
684}
685
686impl sema::RuleVisitor for RuleSetBuilder {
687    type PatternVisitor = Self;
688    type ExprVisitor = Self;
689    type Expr = BindingId;
690
691    fn add_arg(&mut self, index: usize, _ty: sema::TypeId) -> BindingId {
692        let index = TupleIndex(index.try_into().unwrap());
693        self.dedup_binding(Binding::Argument { index })
694    }
695
696    fn add_pattern<F: FnOnce(&mut Self)>(&mut self, visitor: F) {
697        visitor(self)
698    }
699
700    fn add_expr<F>(&mut self, visitor: F) -> BindingId
701    where
702        F: FnOnce(&mut Self) -> sema::VisitedExpr<Self>,
703    {
704        visitor(self).value
705    }
706
707    fn expr_as_pattern(&mut self, expr: BindingId) -> BindingId {
708        let mut todo = vec![expr];
709        while let Some(expr) = todo.pop() {
710            let expr = &self.rules.bindings[expr.index()];
711            todo.extend_from_slice(expr.sources());
712            if let &Binding::MatchSome { source } = expr {
713                let _ = self.set_constraint(source, Constraint::Some);
714            }
715        }
716        expr
717    }
718
719    fn pattern_as_expr(&mut self, pattern: BindingId) -> BindingId {
720        pattern
721    }
722}