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, .. } => ¶meters[..],
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}