Skip to content

Commit 06eb438

Browse files
committed
Mutate match arms by deletion when _ pattern is present
1 parent a154cc7 commit 06eb438

File tree

2 files changed

+117
-24
lines changed

2 files changed

+117
-24
lines changed

src/mutant.rs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub enum Genre {
2626
/// Replace `==` with `!=` and so on.
2727
BinaryOperator,
2828
UnaryOperator,
29+
/// Delete match arm.
30+
MatchArm,
2931
}
3032

3133
/// A mutation applied to source code.
@@ -131,33 +133,39 @@ impl Mutant {
131133
style(s.to_string())
132134
}
133135
let mut v: Vec<StyledObject<String>> = Vec::new();
134-
if self.genre == Genre::FnValue {
135-
v.push(s("replace "));
136-
let function = self
137-
.function
138-
.as_ref()
139-
.expect("FnValue mutant should have a function");
140-
v.push(s(&function.function_name).bright().magenta());
141-
if !function.return_type.is_empty() {
142-
v.push(s(" "));
143-
v.push(s(&function.return_type).magenta());
144-
}
145-
v.push(s(" with "));
146-
v.push(s(self.replacement_text()).yellow());
147-
} else {
148-
if self.replacement.is_empty() {
149-
v.push(s("delete "));
150-
} else {
136+
match self.genre {
137+
Genre::FnValue => {
151138
v.push(s("replace "));
152-
}
153-
v.push(s(self.original_text()).yellow());
154-
if !self.replacement.is_empty() {
139+
let function = self
140+
.function
141+
.as_ref()
142+
.expect("FnValue mutant should have a function");
143+
v.push(s(&function.function_name).bright().magenta());
144+
if !function.return_type.is_empty() {
145+
v.push(s(" "));
146+
v.push(s(&function.return_type).magenta());
147+
}
155148
v.push(s(" with "));
156-
v.push(s(&self.replacement).bright().yellow());
149+
v.push(s(self.replacement_text()).yellow());
157150
}
158-
if let Some(function) = &self.function {
159-
v.push(s(" in "));
160-
v.push(s(&function.function_name).bright().magenta());
151+
Genre::MatchArm => {
152+
v.push(s("delete match arm"));
153+
}
154+
_ => {
155+
if self.replacement.is_empty() {
156+
v.push(s("delete "));
157+
} else {
158+
v.push(s("replace "));
159+
}
160+
v.push(s(self.original_text()).yellow());
161+
if !self.replacement.is_empty() {
162+
v.push(s(" with "));
163+
v.push(s(&self.replacement).bright().yellow());
164+
}
165+
if let Some(function) = &self.function {
166+
v.push(s(" in "));
167+
v.push(s(&function.function_name).bright().magenta());
168+
}
161169
}
162170
}
163171
v

src/visit.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,35 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> {
595595
};
596596
syn::visit::visit_expr_unary(self, i);
597597
}
598+
599+
fn visit_expr_match(&mut self, i: &'ast syn::ExprMatch) {
600+
let _span = trace_span!("match", line = i.span().start().line).entered();
601+
602+
// While it's not currently possible to annotate expressions with custom attributes, this
603+
// limitation could be lifted in the future.
604+
if attrs_excluded(&i.attrs) {
605+
trace!("match excluded by attrs");
606+
return;
607+
}
608+
609+
let has_catchall = i
610+
.arms
611+
.iter()
612+
.any(|arm| matches!(arm.pat, syn::Pat::Wild(_)));
613+
if has_catchall {
614+
i.arms
615+
.iter()
616+
// Don't mutate the wild arm, because that will likely be unviable.
617+
.filter(|arm| !matches!(arm.pat, syn::Pat::Wild(_)))
618+
.for_each(|arm| {
619+
self.collect_mutant(arm.span().into(), &quote! {}, Genre::MatchArm);
620+
});
621+
} else {
622+
trace!("match has no `_` pattern");
623+
}
624+
625+
syn::visit::visit_expr_match(self, i);
626+
}
598627
}
599628

600629
// Get the span of the block excluding the braces, or None if it is empty.
@@ -1084,4 +1113,60 @@ mod test {
10841113
"#}
10851114
);
10861115
}
1116+
1117+
#[test]
1118+
fn mutate_match_arms_with_fallback() {
1119+
let options = Options::default();
1120+
let mutants = mutate_source_str(
1121+
indoc! {"
1122+
fn main() {
1123+
match x {
1124+
X::A => {},
1125+
X::B => {},
1126+
_ => {},
1127+
}
1128+
}
1129+
"},
1130+
&options,
1131+
)
1132+
.unwrap();
1133+
assert_eq!(
1134+
mutants
1135+
.iter()
1136+
.filter(|m| m.genre == Genre::MatchArm)
1137+
.map(|m| m.name(true))
1138+
.collect_vec(),
1139+
[
1140+
"src/main.rs:3:9: delete match arm",
1141+
"src/main.rs:4:9: delete match arm",
1142+
]
1143+
);
1144+
}
1145+
1146+
#[test]
1147+
fn skip_match_arms_without_fallback() {
1148+
let options = Options::default();
1149+
let mutants = mutate_source_str(
1150+
indoc! {"
1151+
fn main() {
1152+
match x {
1153+
X::A => {},
1154+
X::B => {},
1155+
}
1156+
}
1157+
"},
1158+
&options,
1159+
)
1160+
.unwrap();
1161+
1162+
let empty: &[&str] = &[];
1163+
assert_eq!(
1164+
mutants
1165+
.iter()
1166+
.filter(|m| m.genre == Genre::MatchArm)
1167+
.map(|m| m.name(true))
1168+
.collect_vec(),
1169+
empty
1170+
);
1171+
}
10871172
}

0 commit comments

Comments
 (0)