use cairo_lang_debug::DebugWithDb;
use cairo_lang_defs::ids::{ExternFunctionId, NamedLanguageElementId};
use cairo_lang_semantic::corelib::option_some_variant;
use cairo_lang_semantic::helper::ModuleHelper;
use cairo_lang_semantic::{ConcreteVariant, GenericArgumentId, MatchArmSelector, TypeId};
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use itertools::{Itertools, zip_eq};
use salsa::Database;
use crate::analysis::core::Edge;
use crate::analysis::{DataflowAnalyzer, Direction, ForwardDataflowAnalysis};
use crate::{
BlockEnd, BlockId, Lowered, MatchArm, MatchExternInfo, MatchInfo, Statement, VariableId,
};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
struct Placeholder(usize);
fn fresh_placeholder(next_placeholder: &mut usize) -> Placeholder {
*next_placeholder += 1;
Placeholder(*next_placeholder - 1)
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
enum FieldVar {
Var(VariableId),
Placeholder(Placeholder),
}
impl FieldVar {
fn as_var(self) -> Option<VariableId> {
match self {
FieldVar::Var(v) => Some(v),
FieldVar::Placeholder(_) => None,
}
}
fn find_rep(self, info: &mut EqualityState<'_>) -> Self {
match self {
FieldVar::Var(v) => FieldVar::Var(info.find(v)),
p @ FieldVar::Placeholder(_) => p,
}
}
}
const MAX_ARRAY_TRACKED_ITEMS: usize = 5;
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
struct ArrayItems {
prefix_placeholder: Option<Placeholder>,
suffix: Vec<FieldVar>,
}
impl ArrayItems {
fn is_empty(&self) -> bool {
self.prefix_placeholder.is_none() && self.suffix.is_empty()
}
fn referenced_vars(&self) -> impl Iterator<Item = VariableId> + '_ {
self.suffix.iter().filter_map(|f| f.as_var())
}
fn find_rep(self, info: &mut EqualityState<'_>) -> Self {
Self { suffix: self.suffix.into_iter().map(|f| f.find_rep(info)).collect(), ..self }
}
fn append(mut self, elem: FieldVar, next_placeholder: &mut usize) -> Self {
if self.suffix.len() == MAX_ARRAY_TRACKED_ITEMS {
self.prefix_placeholder = Some(fresh_placeholder(next_placeholder));
self.suffix.remove(0);
}
self.suffix.push(elem);
self
}
fn pop(&self, from_front: bool, next_placeholder: &mut usize) -> PopResult {
if self.is_empty() {
return PopResult::KnownEmpty;
}
if self.prefix_placeholder.is_some() {
if from_front {
let [_, rest @ ..] = self.suffix.as_slice() else {
return PopResult::Unknown;
};
return PopResult::ForgottenElement {
remaining: ArrayItems {
prefix_placeholder: Some(fresh_placeholder(next_placeholder)),
suffix: rest.to_vec(),
},
};
}
if self.suffix.is_empty() {
return PopResult::Unknown;
}
}
let (&element, rest) =
if from_front { self.suffix.split_first() } else { self.suffix.split_last() }
.expect("suffix is non-empty");
PopResult::Element {
element,
remaining: ArrayItems {
prefix_placeholder: self.prefix_placeholder,
suffix: rest.to_vec(),
},
}
}
}
enum PopResult {
Element { element: FieldVar, remaining: ArrayItems },
ForgottenElement { remaining: ArrayItems },
KnownEmpty,
Unknown,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
enum Relation<'db> {
Box(VariableId),
Snapshot(VariableId),
EnumConstruct(ConcreteVariant<'db>, VariableId),
StructConstruct(TypeId<'db>, Vec<FieldVar>),
Array(TypeId<'db>, ArrayItems),
}
impl<'db> Relation<'db> {
fn referenced_vars(&self) -> Box<dyn Iterator<Item = VariableId> + '_> {
match self {
Relation::Box(v) | Relation::Snapshot(v) | Relation::EnumConstruct(_, v) => {
Box::new(std::iter::once(*v))
}
Relation::StructConstruct(_, fields) => {
Box::new(fields.iter().filter_map(|f| f.as_var()))
}
Relation::Array(_, items) => Box::new(items.referenced_vars()),
}
}
fn with_fresh_reps(self, state: &mut EqualityState<'_>) -> Self {
match self {
Relation::Box(v) => Relation::Box(state.find(v)),
Relation::Snapshot(v) => Relation::Snapshot(state.find(v)),
Relation::EnumConstruct(variant, v) => Relation::EnumConstruct(variant, state.find(v)),
Relation::StructConstruct(ty, fields) => Relation::StructConstruct(
ty,
fields.into_iter().map(|f| f.find_rep(state)).collect(),
),
Relation::Array(ty, items) => Relation::Array(ty, items.find_rep(state)),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct EqualityState<'db> {
union_find: OrderedHashMap<VariableId, VariableId>,
forward: OrderedHashMap<Relation<'db>, VariableId>,
reverse: OrderedHashMap<VariableId, Relation<'db>>,
}
impl<'db> EqualityState<'db> {
fn get_parent(&self, var: VariableId) -> VariableId {
self.union_find.get(&var).copied().unwrap_or(var)
}
fn find(&mut self, mut var: VariableId) -> VariableId {
let mut parent = self.get_parent(var);
while parent != var {
let grandparent = self.get_parent(parent);
self.union_find.insert(var, grandparent);
var = parent;
parent = grandparent;
}
var
}
pub(crate) fn find_immut(&self, mut var: VariableId) -> VariableId {
let mut parent = self.get_parent(var);
while parent != var {
var = parent;
parent = self.get_parent(var);
}
var
}
fn add_equality(&mut self, old_var: VariableId, union_var: VariableId) -> VariableId {
let old_var = self.find(old_var);
let new_var = self.find(union_var);
if new_var == old_var {
return old_var;
}
assert!(union_var == new_var, "Expected variables to always be 'new' or equal to old");
self.union_find.insert(new_var, old_var);
old_var
}
fn set_relation(&mut self, relation: Relation<'db>, output: VariableId) {
let relation = relation.with_fresh_reps(self);
let existing_output = if let Some(&existing_output) = self.forward.get(&relation) {
self.add_equality(existing_output, output)
} else {
output
};
self.forward.insert(relation.clone(), existing_output);
self.reverse.insert(existing_output, relation);
}
fn get_struct_construct_immut(&self, rep: VariableId) -> Option<(TypeId<'db>, Vec<FieldVar>)> {
match self.reverse.get(&rep)? {
Relation::StructConstruct(ty, fields) => Some((*ty, fields.clone())),
_ => None,
}
}
fn get_struct_construct(&mut self, var: VariableId) -> Option<(TypeId<'db>, Vec<FieldVar>)> {
let rep = self.find(var);
self.get_struct_construct_immut(rep)
}
fn get_array_construct_immut(&self, rep: VariableId) -> Option<(TypeId<'db>, ArrayItems)> {
match self.reverse.get(&rep)? {
Relation::Array(ty, items) => Some((*ty, items.clone())),
_ => None,
}
}
fn get_array_construct(&mut self, var: VariableId) -> Option<(TypeId<'db>, ArrayItems)> {
let rep = self.find(var);
self.get_array_construct_immut(rep)
}
pub(crate) fn get_enum_construct(
&self,
var: VariableId,
) -> Option<(ConcreteVariant<'db>, VariableId)> {
let rep = self.find_immut(var);
self.get_enum_construct_immut(rep)
}
fn get_enum_construct_immut(
&self,
rep: VariableId,
) -> Option<(ConcreteVariant<'db>, VariableId)> {
match self.reverse.get(&rep)? {
Relation::EnumConstruct(variant, input) => Some((*variant, *input)),
_ => None,
}
}
}
impl<'db> DebugWithDb<'db> for EqualityState<'db> {
type Db = dyn Database;
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, db: &'db Self::Db) -> std::fmt::Result {
let v = |id: VariableId| format!("v{}", self.find_immut(id).index());
let mut lines = Vec::<String>::new();
for (relation, &output) in self.forward.iter() {
match relation {
Relation::Snapshot(source) => {
lines.push(format!("@{} = {}", v(*source), v(output)));
}
Relation::Box(source) => {
lines.push(format!("Box({}) = {}", v(*source), v(output)));
}
Relation::EnumConstruct(variant, input) => {
let name = variant.id.name(db).to_string(db);
lines.push(format!("{name}({}) = {}", v(*input), v(output)));
}
Relation::StructConstruct(ty, inputs) => {
let type_name = ty.format(db);
let fields = inputs
.iter()
.map(|f| match f {
FieldVar::Var(id) => v(*id),
FieldVar::Placeholder(_) => "?".to_string(),
})
.collect::<Vec<_>>()
.join(", ");
lines.push(format!("{type_name}({fields}) = {}", v(output)));
}
Relation::Array(ty, items) => {
let type_name = ty.format(db);
let elem = |f: &FieldVar| match f {
FieldVar::Var(id) => v(*id),
FieldVar::Placeholder(_) => "?".to_string(),
};
let prefix = items.prefix_placeholder.iter().map(|_| "?(*)".to_string());
let rendered =
prefix.chain(items.suffix.iter().map(elem)).collect::<Vec<_>>().join(", ");
lines.push(format!("{type_name}[{rendered}] = {}", v(output)));
}
}
}
for &var in self.union_find.keys() {
let rep = self.find_immut(var);
if var != rep {
lines.push(format!("v{} = v{}", rep.index(), var.index()));
}
}
lines.sort();
if lines.is_empty() {
write!(f, "(empty)")
} else {
write!(f, "{}", lines.iter().format(", "))
}
}
}
pub struct EqualityAnalysis<'a, 'db> {
db: &'db dyn Database,
lowered: &'a Lowered<'db>,
next_placeholder: usize,
array_new: ExternFunctionId<'db>,
array_append: ExternFunctionId<'db>,
array_pop_front: ExternFunctionId<'db>,
array_pop_front_consume: ExternFunctionId<'db>,
array_snapshot_pop_front: ExternFunctionId<'db>,
array_snapshot_pop_back: ExternFunctionId<'db>,
}
impl<'a, 'db> EqualityAnalysis<'a, 'db> {
pub fn new(db: &'db dyn Database, lowered: &'a Lowered<'db>) -> Self {
let array_module = ModuleHelper::core(db).submodule("array");
Self {
db,
lowered,
next_placeholder: 0,
array_new: array_module.extern_function_id("array_new"),
array_append: array_module.extern_function_id("array_append"),
array_pop_front: array_module.extern_function_id("array_pop_front"),
array_pop_front_consume: array_module.extern_function_id("array_pop_front_consume"),
array_snapshot_pop_front: array_module.extern_function_id("array_snapshot_pop_front"),
array_snapshot_pop_back: array_module.extern_function_id("array_snapshot_pop_back"),
}
}
pub fn analyze(
db: &'db dyn Database,
lowered: &'a Lowered<'db>,
) -> Vec<Option<EqualityState<'db>>> {
ForwardDataflowAnalysis::new(lowered, EqualityAnalysis::new(db, lowered)).run()
}
fn transfer_extern_match_arm(
&mut self,
info: &mut EqualityState<'db>,
extern_info: &MatchExternInfo<'db>,
arm: &MatchArm<'db>,
) {
let Some((id, _)) = extern_info.function.get_extern(self.db) else { return };
let MatchArmSelector::VariantId(variant) = arm.arm_selector else { return };
if id == self.array_pop_front
|| id == self.array_pop_front_consume
|| id == self.array_snapshot_pop_front
|| id == self.array_snapshot_pop_back
{
let [GenericArgumentId::Type(option_ty)] =
variant.concrete_enum_id.long(self.db).generic_args[..]
else {
panic!("Expected Option<T> with a single type argument");
};
let some_variant = option_some_variant(self.db, option_ty);
assert_eq!(
variant.concrete_enum_id.enum_id(self.db),
some_variant.concrete_enum_id.enum_id(self.db),
"Expected match to be on an Option<T>"
);
self.transfer_array_pop_arm(info, extern_info, arm, id, variant == some_variant);
}
}
fn transfer_array_pop_arm(
&mut self,
info: &mut EqualityState<'db>,
extern_info: &MatchExternInfo<'db>,
arm: &MatchArm<'db>,
id: ExternFunctionId<'db>,
is_some: bool,
) {
let is_snapshot = id == self.array_snapshot_pop_front || id == self.array_snapshot_pop_back;
let is_owned = id == self.array_pop_front || id == self.array_pop_front_consume;
if !is_some || !(is_snapshot || is_owned) {
return;
}
let input_arr = extern_info.inputs[0].var_id;
let remaining_arr = arm.var_ids[0];
let boxed_elem = arm.var_ids[1];
let input_rep = info.find(input_arr);
let original_rep = match info.reverse.get(&input_rep) {
Some(Relation::Snapshot(v)) if is_snapshot => Some(info.find_immut(*v)),
_ => None,
};
let Some((_, items)) = original_rep
.and_then(|orig| info.get_array_construct_immut(orig))
.or_else(|| info.get_array_construct_immut(input_rep))
else {
return;
};
let from_front = id != self.array_snapshot_pop_back;
let remaining = match items.pop(from_front, &mut self.next_placeholder) {
PopResult::Element { element, remaining } => {
if let FieldVar::Var(elem_var) = element {
let box_target = if is_snapshot {
let elem_rep = info.find(elem_var);
info.forward.get(&Relation::Snapshot(elem_rep)).copied()
} else {
Some(elem_var)
};
if let Some(target) = box_target {
info.set_relation(Relation::Box(target), boxed_elem);
}
}
remaining
}
PopResult::ForgottenElement { remaining } => remaining,
PopResult::KnownEmpty | PopResult::Unknown => return,
};
let ty = self.lowered.variables[remaining_arr].ty;
info.set_relation(Relation::Array(ty, remaining), remaining_arr);
}
}
fn merge_referenced_vars<'db, 'a>(
info1: &'a EqualityState<'db>,
info2: &'a EqualityState<'db>,
) -> impl Iterator<Item = VariableId> + 'a {
let union_find_vars = info1.union_find.keys().chain(info2.union_find.keys()).copied();
let forward_vars =
info1.forward.iter().chain(info2.forward.iter()).flat_map(|(relation, &output)| {
relation.referenced_vars().chain(std::iter::once(output))
});
let reverse_vars = info1
.reverse
.iter()
.chain(info2.reverse.iter())
.flat_map(|(&rep, relation)| std::iter::once(rep).chain(relation.referenced_vars()));
union_find_vars.chain(forward_vars).chain(reverse_vars)
}
fn merge_field(
f1: FieldVar,
f2: FieldVar,
result: &mut EqualityState<'_>,
next_placeholder: &mut usize,
any_data: &mut bool,
) -> FieldVar {
match (f1, f2) {
(FieldVar::Var(v), FieldVar::Var(w)) if result.find(v) == result.find(w) => {
*any_data = true;
FieldVar::Var(result.find(v))
}
(_, _) => FieldVar::Placeholder(fresh_placeholder(next_placeholder)),
}
}
fn merge_array_items(
items1: &ArrayItems,
items2: &ArrayItems,
result: &mut EqualityState<'_>,
next_placeholder: &mut usize,
) -> (ArrayItems, bool) {
let mut any_data = false;
let kept = items1.suffix.len().min(items2.suffix.len());
let same_shape = items1.suffix.len() == items2.suffix.len();
let prefix_placeholder = match (items1.prefix_placeholder, items2.prefix_placeholder) {
(None, None) if same_shape => None,
(Some(p1), Some(p2)) if p1 == p2 && same_shape => {
any_data = true;
Some(p1)
}
_ => Some(fresh_placeholder(next_placeholder)),
};
let suffix = zip_eq(
&items1.suffix[items1.suffix.len() - kept..],
&items2.suffix[items2.suffix.len() - kept..],
)
.map(|(f1, f2)| merge_field(*f1, *f2, result, next_placeholder, &mut any_data))
.collect();
(ArrayItems { prefix_placeholder, suffix }, any_data)
}
fn merge_relations<'db>(
info1: &EqualityState<'db>,
info2: &EqualityState<'db>,
intersections: &OrderedHashMap<VariableId, Vec<(VariableId, VariableId)>>,
result: &mut EqualityState<'db>,
next_placeholder: &mut usize,
) {
let mut info2_structs_by_output: OrderedHashMap<VariableId, (TypeId<'db>, Vec<FieldVar>)> =
OrderedHashMap::default();
let mut info2_arrays_by_output: OrderedHashMap<VariableId, (TypeId<'db>, ArrayItems)> =
OrderedHashMap::default();
for (relation, &output2) in info2.forward.iter() {
match relation {
Relation::StructConstruct(ty, fields) => {
info2_structs_by_output.insert(info2.find_immut(output2), (*ty, fields.clone()));
}
Relation::Array(ty, items) => {
info2_arrays_by_output.insert(info2.find_immut(output2), (*ty, items.clone()));
}
_ => {}
}
}
for (relation, &output1) in info1.forward.iter() {
match relation {
Relation::Box(source1) | Relation::Snapshot(source1) => {
for &(source2, intersection_var) in intersections.get(source1).into_iter().flatten()
{
let relation2 = match relation {
Relation::Box(_) => Relation::Box(source2),
Relation::Snapshot(_) => Relation::Snapshot(source2),
_ => unreachable!(),
};
let Some(&output2) = info2.forward.get(&relation2) else { continue };
let output_rep = result.find(output1);
if output_rep != result.find_immut(output2) {
continue;
}
let result_relation = match relation {
Relation::Box(_) => Relation::Box(result.find(intersection_var)),
Relation::Snapshot(_) => Relation::Snapshot(result.find(intersection_var)),
_ => unreachable!(),
};
result.set_relation(result_relation, output_rep);
}
}
Relation::EnumConstruct(variant, input1) => {
for &(input2, input_intersection) in
intersections.get(&info1.find_immut(*input1)).into_iter().flatten()
{
let relation2 = Relation::EnumConstruct(*variant, input2);
let Some(&output2) = info2.forward.get(&relation2) else { continue };
let output_rep = result.find(output1);
if output_rep != result.find_immut(output2) {
continue;
}
result.set_relation(
Relation::EnumConstruct(*variant, input_intersection),
output_rep,
);
}
}
Relation::StructConstruct(ty, fields1) => {
let output1_rep = info1.find_immut(output1);
for &(output2_rep, output_intersection) in
intersections.get(&output1_rep).into_iter().flatten()
{
let Some((ty2, fields2)) = info2_structs_by_output.get(&output2_rep) else {
continue;
};
if ty2 != ty || fields2.len() != fields1.len() {
continue;
}
let mut any_data = false;
let result_fields: Vec<FieldVar> = fields1
.iter()
.zip(fields2)
.map(|(f1, f2)| {
merge_field(*f1, *f2, result, next_placeholder, &mut any_data)
})
.collect();
if any_data || result_fields.is_empty() {
result.set_relation(
Relation::StructConstruct(*ty, result_fields),
output_intersection,
);
}
}
}
Relation::Array(ty, items1) => {
let output1_rep = info1.find_immut(output1);
for &(output2_rep, output_intersection) in
intersections.get(&output1_rep).into_iter().flatten()
{
let Some((ty2, items2)) = info2_arrays_by_output.get(&output2_rep) else {
continue;
};
if ty2 != ty {
continue;
}
let (result_items, any_data) =
merge_array_items(items1, items2, result, next_placeholder);
if any_data || result_items.is_empty() {
result
.set_relation(Relation::Array(*ty, result_items), output_intersection);
}
}
}
}
}
}
impl<'db, 'a> DataflowAnalyzer<'db, 'a> for EqualityAnalysis<'a, 'db> {
type Info = EqualityState<'db>;
const DIRECTION: Direction = Direction::Forward;
fn initial_info(&mut self, _block_id: BlockId, _block_end: &'a BlockEnd<'db>) -> Self::Info {
EqualityState::default()
}
fn merge(
&mut self,
_lowered: &Lowered<'db>,
_statement_location: super::StatementLocation,
info1: Self::Info,
info2: Self::Info,
) -> Self::Info {
let mut result = EqualityState::default();
let mut groups: OrderedHashMap<(VariableId, VariableId), Vec<VariableId>> =
OrderedHashMap::default();
for var in merge_referenced_vars(&info1, &info2) {
let key = (info1.find_immut(var), info2.find_immut(var));
groups.entry(key).or_default().push(var);
}
for members in groups.values() {
let Some(&keep) = members.iter().min_by_key(|v| v.index()) else { continue };
for &var in members {
if var != keep {
result.add_equality(keep, var);
}
}
}
let mut intersections: OrderedHashMap<VariableId, Vec<(VariableId, VariableId)>> =
OrderedHashMap::default();
for (&(rep1, rep2), vars) in groups.iter() {
intersections.entry(rep1).or_default().push((rep2, result.find(vars[0])));
}
merge_relations(&info1, &info2, &intersections, &mut result, &mut self.next_placeholder);
result
}
fn transfer_stmt(
&mut self,
info: &mut Self::Info,
_statement_location: super::StatementLocation,
stmt: &'a Statement<'db>,
) {
match stmt {
Statement::Snapshot(snapshot_stmt) => {
info.add_equality(snapshot_stmt.input.var_id, snapshot_stmt.original());
info.set_relation(
Relation::Snapshot(snapshot_stmt.input.var_id),
snapshot_stmt.snapshot(),
);
}
Statement::Desnap(desnap_stmt) => {
let input_rep = info.find(desnap_stmt.input.var_id);
if let Some(Relation::Snapshot(old_inner)) = info.reverse.get(&input_rep) {
info.add_equality(*old_inner, desnap_stmt.output);
} else {
info.set_relation(
Relation::Snapshot(desnap_stmt.output),
desnap_stmt.input.var_id,
);
}
}
Statement::IntoBox(into_box_stmt) => {
info.set_relation(Relation::Box(into_box_stmt.input.var_id), into_box_stmt.output);
}
Statement::Unbox(unbox_stmt) => {
let input_rep = info.find(unbox_stmt.input.var_id);
if let Some(Relation::Box(old_inner)) = info.reverse.get(&input_rep) {
info.add_equality(*old_inner, unbox_stmt.output);
} else {
info.set_relation(Relation::Box(unbox_stmt.output), unbox_stmt.input.var_id);
}
}
Statement::EnumConstruct(enum_stmt) => {
info.set_relation(
Relation::EnumConstruct(enum_stmt.variant, enum_stmt.input.var_id),
enum_stmt.output,
);
}
Statement::StructConstruct(struct_stmt) => {
let ty = self.lowered.variables[struct_stmt.output].ty;
let fields =
struct_stmt.inputs.iter().map(|i| FieldVar::Var(info.find(i.var_id))).collect();
info.set_relation(Relation::StructConstruct(ty, fields), struct_stmt.output);
}
Statement::StructDestructure(struct_stmt) => {
let struct_var = info.find(struct_stmt.input.var_id);
if let Some((_, field_reps)) = info.get_struct_construct(struct_var) {
for (&output, field) in struct_stmt.outputs.iter().zip(field_reps.iter()) {
if let FieldVar::Var(field_rep) = field {
info.add_equality(*field_rep, output);
}
}
}
let ty = self.lowered.variables[struct_var].ty;
let fields =
struct_stmt.outputs.iter().map(|&v| FieldVar::Var(info.find(v))).collect();
info.set_relation(Relation::StructConstruct(ty, fields), struct_var);
}
Statement::Call(call_stmt) => {
let Some((id, _)) = call_stmt.function.get_extern(self.db) else { return };
if id == self.array_new {
let ty = self.lowered.variables[call_stmt.outputs[0]].ty;
info.set_relation(
Relation::Array(ty, ArrayItems::default()),
call_stmt.outputs[0],
);
} else if id == self.array_append
&& let Some((ty, items)) = info.get_array_construct(call_stmt.inputs[0].var_id)
{
let elem = FieldVar::Var(info.find(call_stmt.inputs[1].var_id));
let new_items = items.append(elem, &mut self.next_placeholder);
info.set_relation(Relation::Array(ty, new_items), call_stmt.outputs[0]);
}
}
Statement::Const(_) => {}
}
}
fn transfer_edge(&mut self, info: &Self::Info, edge: &Edge<'db, 'a>) -> Self::Info {
let mut new_info = info.clone();
match edge {
Edge::Goto { remapping, .. } => {
for (dst, src_usage) in remapping.iter() {
new_info.add_equality(src_usage.var_id, *dst);
}
}
Edge::MatchArm { arm, match_info } => {
if let MatchInfo::Enum(enum_info) = match_info
&& let MatchArmSelector::VariantId(variant) = arm.arm_selector
&& let [arm_var] = arm.var_ids[..]
{
let matched_var = enum_info.input.var_id;
let matched_rep = new_info.find(matched_var);
if let Some((old_variant, input)) =
new_info.get_enum_construct_immut(matched_rep)
&& variant == old_variant
{
new_info.add_equality(input, arm_var);
}
new_info.set_relation(Relation::EnumConstruct(variant, arm_var), matched_var);
}
if let MatchInfo::Extern(extern_info) = match_info {
self.transfer_extern_match_arm(&mut new_info, extern_info, arm);
}
}
Edge::Return { .. } | Edge::Panic { .. } => {}
}
new_info
}
}