1use cairo_lang_debug::DebugWithDb;
11use cairo_lang_defs::ids::{ExternFunctionId, NamedLanguageElementId};
12use cairo_lang_semantic::corelib::option_some_variant;
13use cairo_lang_semantic::helper::ModuleHelper;
14use cairo_lang_semantic::{ConcreteVariant, GenericArgumentId, MatchArmSelector, TypeId};
15use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
16use salsa::Database;
17
18use crate::analysis::core::Edge;
19use crate::analysis::{DataflowAnalyzer, Direction, ForwardDataflowAnalysis};
20use crate::{
21 BlockEnd, BlockId, Lowered, MatchArm, MatchExternInfo, MatchInfo, Statement, VariableId,
22};
23
24#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
27enum FieldVar {
28 Var(VariableId),
29 Placeholder(usize),
31}
32
33impl FieldVar {
34 fn as_var(self) -> Option<VariableId> {
36 match self {
37 FieldVar::Var(v) => Some(v),
38 FieldVar::Placeholder(_) => None,
39 }
40 }
41
42 fn find_rep(self, info: &mut EqualityState<'_>) -> Self {
44 match self {
45 FieldVar::Var(v) => FieldVar::Var(info.find(v)),
46 p @ FieldVar::Placeholder(_) => p,
47 }
48 }
49}
50
51#[derive(Clone, Debug, Hash, PartialEq, Eq)]
54enum Relation<'db> {
55 Box(VariableId),
56 Snapshot(VariableId),
57 EnumConstruct(ConcreteVariant<'db>, VariableId),
58 StructConstruct(TypeId<'db>, Vec<FieldVar>),
59 Array(TypeId<'db>, Vec<FieldVar>),
60}
61
62impl<'db> Relation<'db> {
63 fn referenced_vars(&self) -> impl Iterator<Item = VariableId> + '_ {
65 let (single, fields): (Option<VariableId>, &[FieldVar]) = match self {
66 Relation::Box(v) | Relation::Snapshot(v) | Relation::EnumConstruct(_, v) => {
67 (Some(*v), &[])
68 }
69 Relation::StructConstruct(_, vs) | Relation::Array(_, vs) => (None, vs),
70 };
71 single.into_iter().chain(fields.iter().filter_map(|f| f.as_var()))
72 }
73
74 fn with_fresh_reps(self, state: &mut EqualityState<'_>) -> Self {
76 match self {
77 Relation::Box(v) => Relation::Box(state.find(v)),
78 Relation::Snapshot(v) => Relation::Snapshot(state.find(v)),
79 Relation::EnumConstruct(variant, v) => Relation::EnumConstruct(variant, state.find(v)),
80 Relation::StructConstruct(ty, fields) => Relation::StructConstruct(
81 ty,
82 fields.into_iter().map(|f| f.find_rep(state)).collect(),
83 ),
84 Relation::Array(ty, fields) => {
85 Relation::Array(ty, fields.into_iter().map(|f| f.find_rep(state)).collect())
86 }
87 }
88 }
89}
90
91#[derive(Clone, Debug, Default)]
99pub struct EqualityState<'db> {
100 union_find: OrderedHashMap<VariableId, VariableId>,
102
103 forward: OrderedHashMap<Relation<'db>, VariableId>,
111
112 reverse: OrderedHashMap<VariableId, Relation<'db>>,
115}
116
117impl<'db> EqualityState<'db> {
118 fn get_parent(&self, var: VariableId) -> VariableId {
120 self.union_find.get(&var).copied().unwrap_or(var)
121 }
122
123 fn find(&mut self, mut var: VariableId) -> VariableId {
126 let mut parent = self.get_parent(var);
127 while parent != var {
128 let grandparent = self.get_parent(parent);
129 self.union_find.insert(var, grandparent);
130 var = parent;
131 parent = grandparent;
132 }
133 var
134 }
135
136 pub(crate) fn find_immut(&self, mut var: VariableId) -> VariableId {
138 let mut parent = self.get_parent(var);
139 while parent != var {
140 var = parent;
141 parent = self.get_parent(var);
142 }
143 var
144 }
145
146 fn add_equality(&mut self, old_var: VariableId, union_var: VariableId) -> VariableId {
151 let old_var = self.find(old_var);
152 let new_var = self.find(union_var);
153 if new_var == old_var {
154 return old_var;
155 }
156 assert!(union_var == new_var, "Expected variables to always be 'new' or equal to old");
157 self.union_find.insert(new_var, old_var);
158 old_var
159 }
160
161 fn set_relation(&mut self, relation: Relation<'db>, output: VariableId) {
164 let relation = relation.with_fresh_reps(self);
166
167 let existing_output = if let Some(&existing_output) = self.forward.get(&relation) {
169 self.add_equality(existing_output, output)
170 } else {
171 output
172 };
173
174 self.forward.insert(relation.clone(), existing_output);
176 self.reverse.insert(existing_output, relation);
177 }
178
179 fn get_struct_construct_immut(&self, rep: VariableId) -> Option<(TypeId<'db>, Vec<FieldVar>)> {
181 match self.reverse.get(&rep)? {
182 Relation::StructConstruct(ty, fields) => Some((*ty, fields.clone())),
183 _ => None,
184 }
185 }
186
187 fn get_struct_construct(&mut self, var: VariableId) -> Option<(TypeId<'db>, Vec<FieldVar>)> {
189 let rep = self.find(var);
190 self.get_struct_construct_immut(rep)
191 }
192
193 fn get_array_construct_immut(&self, rep: VariableId) -> Option<(TypeId<'db>, Vec<FieldVar>)> {
195 match self.reverse.get(&rep)? {
196 Relation::Array(ty, fields) => Some((*ty, fields.clone())),
197 _ => None,
198 }
199 }
200
201 fn get_array_construct(&mut self, var: VariableId) -> Option<(TypeId<'db>, Vec<FieldVar>)> {
203 let rep = self.find(var);
204 self.get_array_construct_immut(rep)
205 }
206
207 pub(crate) fn get_enum_construct(
209 &self,
210 var: VariableId,
211 ) -> Option<(ConcreteVariant<'db>, VariableId)> {
212 let rep = self.find_immut(var);
213 self.get_enum_construct_immut(rep)
214 }
215
216 fn get_enum_construct_immut(
218 &self,
219 rep: VariableId,
220 ) -> Option<(ConcreteVariant<'db>, VariableId)> {
221 match self.reverse.get(&rep)? {
222 Relation::EnumConstruct(variant, input) => Some((*variant, *input)),
223 _ => None,
224 }
225 }
226}
227
228impl<'db> DebugWithDb<'db> for EqualityState<'db> {
229 type Db = dyn Database;
230
231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
232 let v = |id: VariableId| format!("v{}", self.find_immut(id).index());
233 let mut lines = Vec::<String>::new();
234 for (relation, &output) in self.forward.iter() {
235 match relation {
236 Relation::Snapshot(source) => {
237 lines.push(format!("@{} = {}", v(*source), v(output)));
238 }
239 Relation::Box(source) => {
240 lines.push(format!("Box({}) = {}", v(*source), v(output)));
241 }
242 Relation::EnumConstruct(variant, input) => {
243 let name = variant.id.name(db).to_string(db);
244 lines.push(format!("{name}({}) = {}", v(*input), v(output)));
245 }
246 Relation::StructConstruct(ty, inputs) | Relation::Array(ty, inputs) => {
247 let type_name = ty.format(db);
248 let fields = inputs
249 .iter()
250 .map(|f| match f {
251 FieldVar::Var(id) => v(*id),
252 FieldVar::Placeholder(_) => "?".to_string(),
253 })
254 .collect::<Vec<_>>()
255 .join(", ");
256 let (open, close) = match relation {
259 Relation::Array(..) => ('[', ']'),
260 _ => ('(', ')'),
261 };
262 lines.push(format!("{type_name}{open}{fields}{close} = {}", v(output)));
263 }
264 }
265 }
266 for &var in self.union_find.keys() {
267 let rep = self.find_immut(var);
268 if var != rep {
269 lines.push(format!("v{} = v{}", rep.index(), var.index()));
270 }
271 }
272 lines.sort();
273 if lines.is_empty() { write!(f, "(empty)") } else { write!(f, "{}", lines.join(", ")) }
274 }
275}
276
277pub struct EqualityAnalysis<'a, 'db> {
286 db: &'db dyn Database,
287 lowered: &'a Lowered<'db>,
288 next_placeholder: usize,
291 array_new: ExternFunctionId<'db>,
293 array_append: ExternFunctionId<'db>,
295 array_pop_front: ExternFunctionId<'db>,
297 array_pop_front_consume: ExternFunctionId<'db>,
299 array_snapshot_pop_front: ExternFunctionId<'db>,
301 array_snapshot_pop_back: ExternFunctionId<'db>,
303}
304
305impl<'a, 'db> EqualityAnalysis<'a, 'db> {
306 pub fn new(db: &'db dyn Database, lowered: &'a Lowered<'db>) -> Self {
308 let array_module = ModuleHelper::core(db).submodule("array");
309 Self {
310 db,
311 lowered,
312 next_placeholder: 0,
313 array_new: array_module.extern_function_id("array_new"),
314 array_append: array_module.extern_function_id("array_append"),
315 array_pop_front: array_module.extern_function_id("array_pop_front"),
316 array_pop_front_consume: array_module.extern_function_id("array_pop_front_consume"),
317 array_snapshot_pop_front: array_module.extern_function_id("array_snapshot_pop_front"),
318 array_snapshot_pop_back: array_module.extern_function_id("array_snapshot_pop_back"),
319 }
320 }
321
322 pub fn analyze(
325 db: &'db dyn Database,
326 lowered: &'a Lowered<'db>,
327 ) -> Vec<Option<EqualityState<'db>>> {
328 ForwardDataflowAnalysis::new(lowered, EqualityAnalysis::new(db, lowered)).run()
329 }
330
331 fn transfer_extern_match_arm(
344 &self,
345 info: &mut EqualityState<'db>,
346 extern_info: &MatchExternInfo<'db>,
347 arm: &MatchArm<'db>,
348 ) {
349 let Some((id, _)) = extern_info.function.get_extern(self.db) else { return };
350 let MatchArmSelector::VariantId(variant) = arm.arm_selector else { return };
352 if id == self.array_pop_front
353 || id == self.array_pop_front_consume
354 || id == self.array_snapshot_pop_front
355 || id == self.array_snapshot_pop_back
356 {
357 let [GenericArgumentId::Type(option_ty)] =
358 variant.concrete_enum_id.long(self.db).generic_args[..]
359 else {
360 panic!("Expected Option<T> with a single type argument");
361 };
362 let some_variant = option_some_variant(self.db, option_ty);
363 assert_eq!(
364 variant.concrete_enum_id.enum_id(self.db),
365 some_variant.concrete_enum_id.enum_id(self.db),
366 "Expected match to be on an Option<T>"
367 );
368 self.transfer_array_pop_arm(info, extern_info, arm, id, variant == some_variant);
369 }
370 }
371
372 fn transfer_array_pop_arm(
374 &self,
375 info: &mut EqualityState<'db>,
376 extern_info: &MatchExternInfo<'db>,
377 arm: &MatchArm<'db>,
378 id: ExternFunctionId<'db>,
379 is_some: bool,
380 ) {
381 if (id == self.array_pop_front || id == self.array_pop_front_consume) && is_some {
382 let input_arr = extern_info.inputs[0].var_id;
384 let remaining_arr = arm.var_ids[0];
385 let boxed_elem = arm.var_ids[1];
386 if let Some((ty, elems)) = info.get_array_construct(input_arr)
387 && let Some((first, rest)) = elems.split_first()
388 {
389 if let FieldVar::Var(first_var) = first {
392 info.set_relation(Relation::Box(*first_var), boxed_elem);
393 }
394 let rest_fields: Vec<FieldVar> = rest.iter().map(|f| f.find_rep(info)).collect();
395 info.set_relation(Relation::Array(ty, rest_fields), remaining_arr);
396 }
397 } else if (id == self.array_snapshot_pop_front || id == self.array_snapshot_pop_back)
398 && is_some
399 {
400 let input_snap_arr = extern_info.inputs[0].var_id;
402 let remaining_snap_arr = arm.var_ids[0];
403 let boxed_snap_elem = arm.var_ids[1];
404
405 let snap_rep = info.find(input_snap_arr);
407 let original_rep = match info.reverse.get(&snap_rep) {
408 Some(Relation::Snapshot(v)) => Some(*v),
409 _ => None,
410 };
411 let elems_opt = original_rep
412 .and_then(|orig| {
413 let orig = info.find_immut(orig);
414 info.get_array_construct_immut(orig)
415 })
416 .or_else(|| info.get_array_construct_immut(snap_rep));
417
418 let snap_ty = self.lowered.variables[remaining_snap_arr].ty;
419 let Some((_orig_ty, elems)) = elems_opt else {
420 return;
421 };
422 let pop_front = id == self.array_snapshot_pop_front;
423 let (elem, rest) = if pop_front {
424 let Some((first, tail)) = elems.split_first() else {
425 info.set_relation(Relation::Array(snap_ty, vec![]), remaining_snap_arr);
426 return;
427 };
428 (*first, tail)
429 } else {
430 let Some((last, init)) = elems.split_last() else {
431 info.set_relation(Relation::Array(snap_ty, vec![]), remaining_snap_arr);
432 return;
433 };
434 (*last, init)
435 };
436
437 if let FieldVar::Var(elem_var) = elem {
442 let elem_rep = info.find(elem_var);
443 if let Some(&snap_of_elem) = info.forward.get(&Relation::Snapshot(elem_rep)) {
444 info.set_relation(Relation::Box(snap_of_elem), boxed_snap_elem);
445 }
446 }
447
448 let rest_fields: Vec<FieldVar> = rest.iter().map(|f| f.find_rep(info)).collect();
449 info.set_relation(Relation::Array(snap_ty, rest_fields), remaining_snap_arr);
450 }
451 }
452}
453
454fn merge_referenced_vars<'db, 'a>(
457 info1: &'a EqualityState<'db>,
458 info2: &'a EqualityState<'db>,
459) -> impl Iterator<Item = VariableId> + 'a {
460 let union_find_vars = info1.union_find.keys().chain(info2.union_find.keys()).copied();
461
462 let forward_vars =
463 info1.forward.iter().chain(info2.forward.iter()).flat_map(|(relation, &output)| {
464 relation.referenced_vars().chain(std::iter::once(output))
465 });
466
467 let reverse_vars = info1
468 .reverse
469 .iter()
470 .chain(info2.reverse.iter())
471 .flat_map(|(&rep, relation)| std::iter::once(rep).chain(relation.referenced_vars()));
472
473 union_find_vars.chain(forward_vars).chain(reverse_vars)
474}
475
476fn merge_relations<'db>(
481 info1: &EqualityState<'db>,
482 info2: &EqualityState<'db>,
483 intersections: &OrderedHashMap<VariableId, Vec<(VariableId, VariableId)>>,
484 result: &mut EqualityState<'db>,
485 next_placeholder: &mut usize,
486) {
487 let mut info2_structs_by_output: OrderedHashMap<VariableId, (TypeId<'db>, Vec<FieldVar>)> =
491 OrderedHashMap::default();
492 let mut info2_arrays_by_output: OrderedHashMap<VariableId, (TypeId<'db>, Vec<FieldVar>)> =
493 OrderedHashMap::default();
494 for (relation, &output2) in info2.forward.iter() {
495 match relation {
496 Relation::StructConstruct(ty, fields) => {
497 info2_structs_by_output.insert(info2.find_immut(output2), (*ty, fields.clone()));
498 }
499 Relation::Array(ty, fields) => {
500 info2_arrays_by_output.insert(info2.find_immut(output2), (*ty, fields.clone()));
501 }
502 _ => {}
503 }
504 }
505
506 for (relation, &output1) in info1.forward.iter() {
508 match relation {
509 Relation::Box(source1) | Relation::Snapshot(source1) => {
510 for &(source2, intersection_var) in intersections.get(source1).into_iter().flatten()
511 {
512 let relation2 = match relation {
513 Relation::Box(_) => Relation::Box(source2),
514 Relation::Snapshot(_) => Relation::Snapshot(source2),
515 _ => unreachable!(),
516 };
517 let Some(&output2) = info2.forward.get(&relation2) else { continue };
518 let output_rep = result.find(output1);
523 if output_rep != result.find_immut(output2) {
524 continue;
525 }
526 let result_relation = match relation {
527 Relation::Box(_) => Relation::Box(result.find(intersection_var)),
528 Relation::Snapshot(_) => Relation::Snapshot(result.find(intersection_var)),
529 _ => unreachable!(),
530 };
531 result.set_relation(result_relation, output_rep);
532 }
533 }
534 Relation::EnumConstruct(variant, input1) => {
535 for &(input2, input_intersection) in
536 intersections.get(&info1.find_immut(*input1)).into_iter().flatten()
537 {
538 let relation2 = Relation::EnumConstruct(*variant, input2);
539 let Some(&output2) = info2.forward.get(&relation2) else { continue };
540 let output_rep = result.find(output1);
541 if output_rep != result.find_immut(output2) {
542 continue;
543 }
544 result.set_relation(
545 Relation::EnumConstruct(*variant, input_intersection),
546 output_rep,
547 );
548 }
549 }
550 Relation::StructConstruct(ty, fields1) | Relation::Array(ty, fields1) => {
551 let is_array = matches!(relation, Relation::Array(..));
552 let index =
553 if is_array { &info2_arrays_by_output } else { &info2_structs_by_output };
554 let output1_rep = info1.find_immut(output1);
555 for &(output2_rep, output_intersection) in
556 intersections.get(&output1_rep).into_iter().flatten()
557 {
558 let Some((ty2, fields2)) = index.get(&output2_rep) else {
559 continue;
560 };
561 if ty2 != ty || fields2.len() != fields1.len() {
562 continue;
563 }
564 let mut any_data = false;
565 let result_fields: Vec<FieldVar> = fields1
566 .iter()
567 .zip(fields2)
568 .map(|(f1, f2)| match (f1, f2) {
569 (FieldVar::Var(v), FieldVar::Var(w))
570 if result.find(*v) == result.find(*w) =>
571 {
572 any_data = true;
573 FieldVar::Var(result.find(*v))
574 }
575 (_, _) => {
576 *next_placeholder += 1;
577 FieldVar::Placeholder(*next_placeholder - 1)
578 }
579 })
580 .collect();
581 if any_data || result_fields.is_empty() {
582 let result_relation = if is_array {
583 Relation::Array(*ty, result_fields)
584 } else {
585 Relation::StructConstruct(*ty, result_fields)
586 };
587 result.set_relation(result_relation, output_intersection);
588 }
589 }
590 }
591 }
592 }
593}
594
595impl<'db, 'a> DataflowAnalyzer<'db, 'a> for EqualityAnalysis<'a, 'db> {
596 type Info = EqualityState<'db>;
597
598 const DIRECTION: Direction = Direction::Forward;
599
600 fn initial_info(&mut self, _block_id: BlockId, _block_end: &'a BlockEnd<'db>) -> Self::Info {
601 EqualityState::default()
602 }
603
604 fn merge(
605 &mut self,
606 _lowered: &Lowered<'db>,
607 _statement_location: super::StatementLocation,
608 info1: Self::Info,
609 info2: Self::Info,
610 ) -> Self::Info {
611 let mut result = EqualityState::default();
615
616 let mut groups: OrderedHashMap<(VariableId, VariableId), Vec<VariableId>> =
618 OrderedHashMap::default();
619
620 for var in merge_referenced_vars(&info1, &info2) {
622 let key = (info1.find_immut(var), info2.find_immut(var));
623 groups.entry(key).or_default().push(var);
624 }
625
626 for members in groups.values() {
630 let Some(&keep) = members.iter().min_by_key(|v| v.index()) else { continue };
631 for &var in members {
632 if var != keep {
633 result.add_equality(keep, var);
634 }
635 }
636 }
637
638 let mut intersections: OrderedHashMap<VariableId, Vec<(VariableId, VariableId)>> =
640 OrderedHashMap::default();
641 for (&(rep1, rep2), vars) in groups.iter() {
642 intersections.entry(rep1).or_default().push((rep2, result.find(vars[0])));
643 }
644
645 merge_relations(&info1, &info2, &intersections, &mut result, &mut self.next_placeholder);
646
647 result
648 }
649
650 fn transfer_stmt(
651 &mut self,
652 info: &mut Self::Info,
653 _statement_location: super::StatementLocation,
654 stmt: &'a Statement<'db>,
655 ) {
656 match stmt {
657 Statement::Snapshot(snapshot_stmt) => {
658 info.add_equality(snapshot_stmt.input.var_id, snapshot_stmt.original());
659 info.set_relation(
660 Relation::Snapshot(snapshot_stmt.input.var_id),
661 snapshot_stmt.snapshot(),
662 );
663 }
664
665 Statement::Desnap(desnap_stmt) => {
666 let input_rep = info.find(desnap_stmt.input.var_id);
667 if let Some(Relation::Snapshot(old_inner)) = info.reverse.get(&input_rep) {
668 info.add_equality(*old_inner, desnap_stmt.output);
669 } else {
670 info.set_relation(
671 Relation::Snapshot(desnap_stmt.output),
672 desnap_stmt.input.var_id,
673 );
674 }
675 }
676
677 Statement::IntoBox(into_box_stmt) => {
678 info.set_relation(Relation::Box(into_box_stmt.input.var_id), into_box_stmt.output);
679 }
680
681 Statement::Unbox(unbox_stmt) => {
682 let input_rep = info.find(unbox_stmt.input.var_id);
683 if let Some(Relation::Box(old_inner)) = info.reverse.get(&input_rep) {
684 info.add_equality(*old_inner, unbox_stmt.output);
685 } else {
686 info.set_relation(Relation::Box(unbox_stmt.output), unbox_stmt.input.var_id);
687 }
688 }
689
690 Statement::EnumConstruct(enum_stmt) => {
691 info.set_relation(
695 Relation::EnumConstruct(enum_stmt.variant, enum_stmt.input.var_id),
696 enum_stmt.output,
697 );
698 }
699
700 Statement::StructConstruct(struct_stmt) => {
701 let ty = self.lowered.variables[struct_stmt.output].ty;
705 let fields =
706 struct_stmt.inputs.iter().map(|i| FieldVar::Var(info.find(i.var_id))).collect();
707 info.set_relation(Relation::StructConstruct(ty, fields), struct_stmt.output);
708 }
709
710 Statement::StructDestructure(struct_stmt) => {
711 let struct_var = info.find(struct_stmt.input.var_id);
713 if let Some((_, field_reps)) = info.get_struct_construct(struct_var) {
716 for (&output, field) in struct_stmt.outputs.iter().zip(field_reps.iter()) {
717 if let FieldVar::Var(field_rep) = field {
718 info.add_equality(*field_rep, output);
719 }
720 }
721 }
722 let ty = self.lowered.variables[struct_var].ty;
724 let fields =
725 struct_stmt.outputs.iter().map(|&v| FieldVar::Var(info.find(v))).collect();
726 info.set_relation(Relation::StructConstruct(ty, fields), struct_var);
727 }
728
729 Statement::Call(call_stmt) => {
730 let Some((id, _)) = call_stmt.function.get_extern(self.db) else { return };
731 if id == self.array_new {
732 let ty = self.lowered.variables[call_stmt.outputs[0]].ty;
733 info.set_relation(Relation::Array(ty, vec![]), call_stmt.outputs[0]);
734 } else if id == self.array_append
735 && let Some((ty, elems)) = info.get_array_construct(call_stmt.inputs[0].var_id)
736 {
737 let mut new_elems = elems;
740 new_elems.push(FieldVar::Var(info.find(call_stmt.inputs[1].var_id)));
741 info.set_relation(Relation::Array(ty, new_elems), call_stmt.outputs[0]);
742 }
743 }
744
745 Statement::Const(_) => {}
746 }
747 }
748
749 fn transfer_edge(&mut self, info: &Self::Info, edge: &Edge<'db, 'a>) -> Self::Info {
750 let mut new_info = info.clone();
751 match edge {
752 Edge::Goto { remapping, .. } => {
753 for (dst, src_usage) in remapping.iter() {
755 new_info.add_equality(src_usage.var_id, *dst);
756 }
757 }
758 Edge::MatchArm { arm, match_info } => {
759 if let MatchInfo::Enum(enum_info) = match_info
761 && let MatchArmSelector::VariantId(variant) = arm.arm_selector
762 && let [arm_var] = arm.var_ids[..]
763 {
764 let matched_var = enum_info.input.var_id;
765
766 let matched_rep = new_info.find(matched_var);
770 if let Some((old_variant, input)) =
771 new_info.get_enum_construct_immut(matched_rep)
772 && variant == old_variant
773 {
774 new_info.add_equality(input, arm_var);
775 }
776
777 new_info.set_relation(Relation::EnumConstruct(variant, arm_var), matched_var);
779 }
780
781 if let MatchInfo::Extern(extern_info) = match_info {
783 self.transfer_extern_match_arm(&mut new_info, extern_info, arm);
784 }
785 }
786 Edge::Return { .. } | Edge::Panic { .. } => {}
787 }
788 new_info
789 }
790}