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.
3 use crate::disjointsets::DisjointSets;
4 use crate::error::{Error, Span};
5 use crate::lexer::Pos;
6 use crate::sema;
7 use crate::stablemapset::StableSet;
8 use std::collections::{HashMap, hash_map::Entry};
9 
10 /// A field index in a tuple or an enum variant.
11 #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
12 pub 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)]
15 pub struct BindingId(u16);
16 
17 impl std::convert::TryFrom<usize> for TupleIndex {
18     type Error = <u8 as std::convert::TryFrom<usize>>::Error;
19 
try_from(value: usize) -> Result<Self, Self::Error>20     fn try_from(value: usize) -> Result<Self, Self::Error> {
21         Ok(TupleIndex(value.try_into()?))
22     }
23 }
24 
25 impl std::convert::TryFrom<usize> for BindingId {
26     type Error = <u16 as std::convert::TryFrom<usize>>::Error;
27 
try_from(value: usize) -> Result<Self, Self::Error>28     fn try_from(value: usize) -> Result<Self, Self::Error> {
29         Ok(BindingId(value.try_into()?))
30     }
31 }
32 
33 impl TupleIndex {
34     /// Get the index of this field.
index(self) -> usize35     pub fn index(self) -> usize {
36         self.0.into()
37     }
38 }
39 
40 impl BindingId {
41     /// Get the index of this id.
index(self) -> usize42     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)]
50 pub 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)]
147 pub 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)]
185 pub 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)]
207 pub 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)]
222 pub 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.
build(termenv: &sema::TermEnv) -> (Vec<(sema::TermId, RuleSet)>, Vec<Error>)232 pub 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 
253 impl RuleSet {
254     /// Returns the [BindingId] corresponding to the given [Binding] within this rule-set, if any.
find_binding(&self, binding: &Binding) -> Option<BindingId>255     pub fn find_binding(&self, binding: &Binding) -> Option<BindingId> {
256         self.binding_map.get(binding).copied()
257     }
258 }
259 
260 impl Binding {
261     /// Returns the binding sites which must be evaluated before this binding.
sources(&self) -> &[BindingId]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 
280 impl Constraint {
281     /// Return the nested [Binding]s from matching the given [Constraint] against the given [BindingId].
bindings_for(self, source: BindingId) -> Vec<Binding>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 
303 impl 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.
may_overlap(&self, other: &Rule) -> Overlap308     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.
total_constraints(&self) -> usize354     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.
get_constraint(&self, source: BindingId) -> Option<Constraint>362     pub fn get_constraint(&self, source: BindingId) -> Option<Constraint> {
363         self.constraints.get(&source).copied()
364     }
365 
set_constraint( &mut self, source: BindingId, constraint: Constraint, ) -> Result<(), UnreachableError>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)]
390 struct UnreachableError {
391     pos: Pos,
392     constraint_a: Constraint,
393     constraint_b: Constraint,
394 }
395 
396 #[derive(Debug, Default)]
397 struct RuleSetBuilder {
398     current_rule: Rule,
399     impure_instance: u32,
400     unreachable: Vec<UnreachableError>,
401     rules: RuleSet,
402 }
403 
404 impl RuleSetBuilder {
add_rule(&mut self, rule: &sema::Rule, termenv: &sema::TermEnv, errors: &mut Vec<Error>)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.
normalize_equivalence_classes(&mut self)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 
dedup_binding(&mut self, binding: Binding) -> BindingId512     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 
set_constraint(&mut self, input: BindingId, constraint: Constraint) -> Vec<BindingId>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 
535 impl sema::PatternVisitor for RuleSetBuilder {
536     type PatternId = BindingId;
537 
add_match_equal(&mut self, a: BindingId, b: BindingId, _ty: sema::TypeId)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 
add_match_bool(&mut self, input: BindingId, ty: sema::TypeId, val: bool)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 
add_match_int(&mut self, input: BindingId, ty: sema::TypeId, val: i128)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 
add_match_prim(&mut self, input: BindingId, _ty: sema::TypeId, val: sema::Sym)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 
add_match_variant( &mut self, input: BindingId, input_ty: sema::TypeId, arg_tys: &[sema::TypeId], variant: sema::VariantId, ) -> Vec<BindingId>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 
add_extract( &mut self, input: BindingId, _input_ty: sema::TypeId, output_tys: Vec<sema::TypeId>, term: sema::TermId, infallible: bool, multi: bool, ) -> Vec<BindingId>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 
616 impl sema::ExprVisitor for RuleSetBuilder {
617     type ExprId = BindingId;
618 
add_const_bool(&mut self, ty: sema::TypeId, val: bool) -> BindingId619     fn add_const_bool(&mut self, ty: sema::TypeId, val: bool) -> BindingId {
620         self.dedup_binding(Binding::ConstBool { val, ty })
621     }
622 
add_const_int(&mut self, ty: sema::TypeId, val: i128) -> BindingId623     fn add_const_int(&mut self, ty: sema::TypeId, val: i128) -> BindingId {
624         self.dedup_binding(Binding::ConstInt { val, ty })
625     }
626 
add_const_prim(&mut self, _ty: sema::TypeId, val: sema::Sym) -> BindingId627     fn add_const_prim(&mut self, _ty: sema::TypeId, val: sema::Sym) -> BindingId {
628         self.dedup_binding(Binding::ConstPrim { val })
629     }
630 
add_create_variant( &mut self, inputs: Vec<(BindingId, sema::TypeId)>, ty: sema::TypeId, variant: sema::VariantId, ) -> BindingId631     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 
add_construct( &mut self, inputs: Vec<(BindingId, sema::TypeId)>, _ty: sema::TypeId, term: sema::TermId, pure: bool, infallible: bool, multi: bool, _rec: bool, ) -> BindingId644     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         _rec: bool,
653     ) -> BindingId {
654         let instance = if pure {
655             0
656         } else {
657             self.impure_instance += 1;
658             self.impure_instance
659         };
660         let source = self.dedup_binding(Binding::Constructor {
661             term,
662             parameters: inputs.into_iter().map(|(expr, _)| expr).collect(),
663             instance,
664         });
665 
666         // If the constructor is fallible, build a pattern for `Some`, but not a constraint. If the
667         // constructor is on the right-hand side of a rule then its failure is not considered when
668         // deciding which rule to evaluate. Corresponding constraints are only added if this
669         // expression is subsequently used as a pattern; see `expr_as_pattern`.
670         let source = if multi {
671             self.current_rule.iterators.insert(source);
672             self.dedup_binding(Binding::Iterator { source })
673         } else if infallible {
674             source
675         } else {
676             self.dedup_binding(Binding::MatchSome { source })
677         };
678 
679         if !pure {
680             self.current_rule.impure.push(source);
681         }
682 
683         source
684     }
685 }
686 
687 impl sema::RuleVisitor for RuleSetBuilder {
688     type PatternVisitor = Self;
689     type ExprVisitor = Self;
690     type Expr = BindingId;
691 
add_arg(&mut self, index: usize, _ty: sema::TypeId) -> BindingId692     fn add_arg(&mut self, index: usize, _ty: sema::TypeId) -> BindingId {
693         let index = TupleIndex(index.try_into().unwrap());
694         self.dedup_binding(Binding::Argument { index })
695     }
696 
add_pattern<F: FnOnce(&mut Self)>(&mut self, visitor: F)697     fn add_pattern<F: FnOnce(&mut Self)>(&mut self, visitor: F) {
698         visitor(self)
699     }
700 
add_expr<F>(&mut self, visitor: F) -> BindingId where F: FnOnce(&mut Self) -> sema::VisitedExpr<Self>,701     fn add_expr<F>(&mut self, visitor: F) -> BindingId
702     where
703         F: FnOnce(&mut Self) -> sema::VisitedExpr<Self>,
704     {
705         visitor(self).value
706     }
707 
expr_as_pattern(&mut self, expr: BindingId) -> BindingId708     fn expr_as_pattern(&mut self, expr: BindingId) -> BindingId {
709         let mut todo = vec![expr];
710         while let Some(expr) = todo.pop() {
711             let expr = &self.rules.bindings[expr.index()];
712             todo.extend_from_slice(expr.sources());
713             if let &Binding::MatchSome { source } = expr {
714                 let _ = self.set_constraint(source, Constraint::Some);
715             }
716         }
717         expr
718     }
719 
pattern_as_expr(&mut self, pattern: BindingId) -> BindingId720     fn pattern_as_expr(&mut self, pattern: BindingId) -> BindingId {
721         pattern
722     }
723 }
724