use super::spec::{MutationSpec, MutationTargetSymbol};
use std::collections::HashMap;
pub fn target_conflicts(a: &MutationTargetSymbol, b: &MutationTargetSymbol) -> bool {
match (a, b) {
(MutationTargetSymbol::ById(id_a), MutationTargetSymbol::ById(id_b)) => id_a == id_b,
(MutationTargetSymbol::ByPath(path_a), MutationTargetSymbol::ByPath(path_b)) => {
path_a == path_b
}
(
MutationTargetSymbol::ByKindAndName(kind_a, name_a),
MutationTargetSymbol::ByKindAndName(kind_b, name_b),
) => kind_a == kind_b && name_a == name_b,
(
MutationTargetSymbol::ByAffectedId {
parent_id: parent_a,
name: name_a,
..
},
MutationTargetSymbol::ByAffectedId {
parent_id: parent_b,
name: name_b,
..
},
) => parent_a == parent_b && name_a == name_b,
_ => true,
}
}
pub fn specs_conflict(a: &MutationSpec, b: &MutationSpec) -> bool {
use MutationSpec::*;
match (a, b) {
(
AddField {
target: target_a,
field_name: field_a,
..
},
AddField {
target: target_b,
field_name: field_b,
..
},
) => target_conflicts(target_a, target_b) && field_a == field_b,
(
AddField {
target: target_a,
field_name: field_a,
..
},
RemoveField {
target: target_b,
field_name: field_b,
..
},
)
| (
RemoveField {
target: target_a,
field_name: field_a,
..
},
AddField {
target: target_b,
field_name: field_b,
..
},
)
| (
RemoveField {
target: target_a,
field_name: field_a,
..
},
RemoveField {
target: target_b,
field_name: field_b,
..
},
) => target_conflicts(target_a, target_b) && field_a == field_b,
(
AddVariant {
target: target_a,
variant_name: variant_a,
..
},
AddVariant {
target: target_b,
variant_name: variant_b,
..
},
) => target_conflicts(target_a, target_b) && variant_a == variant_b,
(
AddVariant {
target: target_a,
variant_name: variant_a,
..
},
RemoveVariant {
target: target_b,
variant_name: variant_b,
..
},
)
| (
RemoveVariant {
target: target_a,
variant_name: variant_a,
..
},
AddVariant {
target: target_b,
variant_name: variant_b,
..
},
)
| (
RemoveVariant {
target: target_a,
variant_name: variant_a,
..
},
RemoveVariant {
target: target_b,
variant_name: variant_b,
..
},
) => target_conflicts(target_a, target_b) && variant_a == variant_b,
(
AddMethod {
target: target_a,
method_name: method_a,
..
},
AddMethod {
target: target_b,
method_name: method_b,
..
},
) => target_conflicts(target_a, target_b) && method_a == method_b,
(
AddMethod {
target: target_a,
method_name: method_a,
..
},
RemoveMethod {
target: target_b,
method_name: method_b,
..
},
)
| (
RemoveMethod {
target: target_a,
method_name: method_a,
..
},
AddMethod {
target: target_b,
method_name: method_b,
..
},
)
| (
RemoveMethod {
target: target_a,
method_name: method_a,
..
},
RemoveMethod {
target: target_b,
method_name: method_b,
..
},
) => target_conflicts(target_a, target_b) && method_a == method_b,
(
RemoveMod {
target: target_a, ..
},
RemoveMod {
target: target_b, ..
},
)
| (
RemoveMod {
target: target_a, ..
},
CreateMod {
target: target_b, ..
},
)
| (
CreateMod {
target: target_a, ..
},
RemoveMod {
target: target_b, ..
},
)
| (
CreateMod {
target: target_a, ..
},
CreateMod {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
Rename {
target: target_a, ..
},
Rename {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
AddDerive {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
AddDerive {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
RemoveDerive {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
RemoveDerive {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
ChangeVisibility {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
RemoveItem {
target: target_a, ..
},
RemoveItem {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
ExtractTrait {
target: target_a, ..
},
ExtractTrait {
target: target_b, ..
},
)
| (
ExtractTrait {
target: target_a, ..
},
InlineTrait {
target: target_b, ..
},
)
| (
InlineTrait {
target: target_a, ..
},
ExtractTrait {
target: target_b, ..
},
)
| (
InlineTrait {
target: target_a, ..
},
InlineTrait {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
EnumToTrait {
target: target_a, ..
},
EnumToTrait {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
DuplicateFunction {
target: target_a, ..
},
DuplicateFunction {
target: target_b, ..
},
)
| (
DuplicateStruct {
target: target_a, ..
},
DuplicateStruct {
target: target_b, ..
},
)
| (
DuplicateEnum {
target: target_a, ..
},
DuplicateEnum {
target: target_b, ..
},
)
| (
DuplicateModTree {
target: target_a, ..
},
DuplicateModTree {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
MoveItem {
source: source_a,
target: target_a,
..
},
MoveItem {
source: source_b,
target: target_b,
..
},
) => {
target_conflicts(source_a, source_b) || target_conflicts(target_a, target_b)
}
(
AddStructLiteralField {
target: target_a, ..
},
AddStructLiteralField {
target: target_b, ..
},
)
| (
AddStructLiteralField {
target: target_a, ..
},
RemoveStructLiteralField {
target: target_b, ..
},
)
| (
RemoveStructLiteralField {
target: target_a, ..
},
AddStructLiteralField {
target: target_b, ..
},
)
| (
RemoveStructLiteralField {
target: target_a, ..
},
RemoveStructLiteralField {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(
AddField {
target: target_a, ..
},
RemoveItem {
target: target_b, ..
},
)
| (
RemoveItem {
target: target_a, ..
},
AddField {
target: target_b, ..
},
)
| (
AddMethod {
target: target_a, ..
},
RemoveItem {
target: target_b, ..
},
)
| (
RemoveItem {
target: target_a, ..
},
AddMethod {
target: target_b, ..
},
)
| (
AddVariant {
target: target_a, ..
},
RemoveItem {
target: target_b, ..
},
)
| (
RemoveItem {
target: target_a, ..
},
AddVariant {
target: target_b, ..
},
)
| (
AddDerive {
target: target_a, ..
},
RemoveItem {
target: target_b, ..
},
)
| (
RemoveItem {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
ChangeVisibility {
target: target_a, ..
},
RemoveItem {
target: target_b, ..
},
)
| (
RemoveItem {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
RemoveItem {
target: target_b, ..
},
)
| (
RemoveItem {
target: target_a, ..
},
Rename {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
(AddItem { .. }, AddItem { .. }) => true, (OrganizeImports { .. }, OrganizeImports { .. }) => true,
(LoopToIterator { module_id: m_a, .. }, LoopToIterator { module_id: m_b, .. })
| (UnwrapToQuestion { module_id: m_a, .. }, UnwrapToQuestion { module_id: m_b, .. })
| (AssignOp { module_id: m_a, .. }, AssignOp { module_id: m_b, .. })
| (BoolSimplify { module_id: m_a, .. }, BoolSimplify { module_id: m_b, .. })
| (CloneOnCopy { module_id: m_a, .. }, CloneOnCopy { module_id: m_b, .. })
| (CollapsibleIf { module_id: m_a, .. }, CollapsibleIf { module_id: m_b, .. })
| (NoOpArmToTodo { module_id: m_a, .. }, NoOpArmToTodo { module_id: m_b, .. })
| (ComparisonToMethod { module_id: m_a, .. }, ComparisonToMethod { module_id: m_b, .. })
| (RedundantClosure { module_id: m_a, .. }, RedundantClosure { module_id: m_b, .. })
| (IntroduceVariable { module_id: m_a, .. }, IntroduceVariable { module_id: m_b, .. })
| (ManualMap { module_id: m_a, .. }, ManualMap { module_id: m_b, .. })
| (MatchToIfLet { module_id: m_a, .. }, MatchToIfLet { module_id: m_b, .. })
| (FilterNext { module_id: m_a, .. }, FilterNext { module_id: m_b, .. })
| (MapUnwrapOr { module_id: m_a, .. }, MapUnwrapOr { module_id: m_b, .. }) => m_a == m_b,
(
ReplaceExpr {
fn_id: f_a,
module_id: m_a,
..
},
ReplaceExpr {
fn_id: f_b,
module_id: m_b,
..
},
)
| (
RemoveStatement {
fn_id: f_a,
module_id: m_a,
..
},
RemoveStatement {
fn_id: f_b,
module_id: m_b,
..
},
)
| (
ReplaceStatement {
fn_id: f_a,
module_id: m_a,
..
},
ReplaceStatement {
fn_id: f_b,
module_id: m_b,
..
},
) => {
(f_a.is_some() && f_a == f_b) || (f_a.is_none() && f_b.is_none() && m_a == m_b)
}
(InsertStatement { fn_id: f_a, .. }, InsertStatement { fn_id: f_b, .. }) => f_a == f_b,
(AddMatchArm { target: t_a, .. }, AddMatchArm { target: t_b, .. })
| (RemoveMatchArm { target: t_a, .. }, RemoveMatchArm { target: t_b, .. })
| (ReplaceMatchArm { target: t_a, .. }, ReplaceMatchArm { target: t_b, .. }) => t_a == t_b,
(ReplaceType { .. }, ReplaceType { .. }) => true,
(AddSpec { .. }, AddSpec { .. }) => true,
(PluginTransform { .. }, PluginTransform { .. }) => true,
(
AddField {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
AddField {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
AddField {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
AddField {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
RemoveField {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
RemoveField {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
RemoveField {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
RemoveField {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
AddVariant {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
AddVariant {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
AddVariant {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
AddVariant {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
RemoveVariant {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
RemoveVariant {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
RemoveVariant {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
RemoveVariant {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
AddMethod {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
AddMethod {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
AddMethod {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
AddMethod {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
RemoveMethod {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
RemoveMethod {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
RemoveMethod {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
RemoveMethod {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
AddDerive {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
AddDerive {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
RemoveDerive {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
)
| (
RemoveDerive {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
ChangeVisibility {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
ChangeVisibility {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
ChangeVisibility {
target: target_a, ..
},
Rename {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
AddField {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
RemoveField {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
AddVariant {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
RemoveVariant {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
AddMethod {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
RemoveMethod {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
AddDerive {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
RemoveDerive {
target: target_b, ..
},
)
| (
Rename {
target: target_a, ..
},
ChangeVisibility {
target: target_b, ..
},
) => target_conflicts(target_a, target_b),
_ => false,
}
}
pub fn find_conflicting_pairs(specs: &[MutationSpec]) -> Vec<(usize, usize)> {
let mut conflicts = Vec::new();
for i in 0..specs.len() {
for j in (i + 1)..specs.len() {
if specs_conflict(&specs[i], &specs[j]) {
conflicts.push((i, j));
}
}
}
conflicts
}
pub fn group_by_conflicts(specs: &[MutationSpec]) -> Vec<Vec<usize>> {
if specs.is_empty() {
return vec![];
}
let mut parent: Vec<usize> = (0..specs.len()).collect();
let mut rank: Vec<usize> = vec![0; specs.len()];
fn find(parent: &mut [usize], i: usize) -> usize {
if parent[i] != i {
parent[i] = find(parent, parent[i]); }
parent[i]
}
fn union(parent: &mut [usize], rank: &mut [usize], i: usize, j: usize) {
let root_i = find(parent, i);
let root_j = find(parent, j);
if root_i != root_j {
if rank[root_i] < rank[root_j] {
parent[root_i] = root_j;
} else if rank[root_i] > rank[root_j] {
parent[root_j] = root_i;
} else {
parent[root_j] = root_i;
rank[root_i] += 1;
}
}
}
for i in 0..specs.len() {
for j in (i + 1)..specs.len() {
if specs_conflict(&specs[i], &specs[j]) {
union(&mut parent, &mut rank, i, j);
}
}
}
let mut groups: HashMap<usize, Vec<usize>> = HashMap::new();
for i in 0..specs.len() {
let root = find(&mut parent, i);
groups.entry(root).or_default().push(i);
}
groups.into_values().collect()
}
#[cfg(test)]
mod tests {
use super::*;
use ryo_symbol::SymbolId;
fn dummy_id(index: u32) -> SymbolId {
SymbolId::parse(&format!("{}v1", index)).expect("valid dummy id")
}
#[test]
fn test_target_conflicts_same_id() {
let id1 = dummy_id(1);
let target_a = MutationTargetSymbol::ById(id1);
let target_b = MutationTargetSymbol::ById(id1);
assert!(target_conflicts(&target_a, &target_b));
}
#[test]
fn test_target_conflicts_different_ids() {
let id1 = dummy_id(1);
let id2 = dummy_id(2);
let target_a = MutationTargetSymbol::ById(id1);
let target_b = MutationTargetSymbol::ById(id2);
assert!(!target_conflicts(&target_a, &target_b));
}
#[test]
fn test_specs_conflict_same_target_same_field() {
let id1 = dummy_id(1);
let spec_a = MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(),
field_type: "String".into(),
visibility: Default::default(),
};
let spec_b = MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(), field_type: "String".into(),
visibility: Default::default(),
};
assert!(specs_conflict(&spec_a, &spec_b));
}
#[test]
fn test_specs_no_conflict_same_target_different_fields() {
let id1 = dummy_id(1);
let spec_a = MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(),
field_type: "String".into(),
visibility: Default::default(),
};
let spec_b = MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "email".into(), field_type: "String".into(),
visibility: Default::default(),
};
assert!(!specs_conflict(&spec_a, &spec_b));
}
#[test]
fn test_specs_conflict_different_targets() {
let id1 = dummy_id(1);
let id2 = dummy_id(2);
let spec_a = MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(),
field_type: "String".into(),
visibility: Default::default(),
};
let spec_b = MutationSpec::AddField {
target: MutationTargetSymbol::ById(id2),
field_name: "email".into(),
field_type: "String".into(),
visibility: Default::default(),
};
assert!(!specs_conflict(&spec_a, &spec_b));
}
#[test]
fn test_group_by_conflicts_independent() {
let id1 = dummy_id(1);
let id2 = dummy_id(2);
let specs = vec![
MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(),
field_type: "String".into(),
visibility: Default::default(),
},
MutationSpec::AddField {
target: MutationTargetSymbol::ById(id2),
field_name: "email".into(),
field_type: "String".into(),
visibility: Default::default(),
},
];
let groups = group_by_conflicts(&specs);
assert_eq!(groups.len(), 2); }
#[test]
fn test_group_by_conflicts_conflicting() {
let id1 = dummy_id(1);
let specs = vec![
MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(),
field_type: "String".into(),
visibility: Default::default(),
},
MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(), field_type: "i32".into(),
visibility: Default::default(),
},
];
let groups = group_by_conflicts(&specs);
assert_eq!(groups.len(), 1); assert_eq!(groups[0].len(), 2);
}
#[test]
fn test_cross_variant_conflict() {
let id1 = dummy_id(1);
let spec_add = MutationSpec::AddField {
target: MutationTargetSymbol::ById(id1),
field_name: "name".into(),
field_type: "String".into(),
visibility: Default::default(),
};
let spec_remove = MutationSpec::RemoveItem {
target: MutationTargetSymbol::ById(id1),
item_kind: ryo_source::ItemKind::Struct,
};
assert!(specs_conflict(&spec_add, &spec_remove));
}
}