From 02d1fdbfa73e63f07eadb97c1ecfeba2cdce39be Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Sun, 9 Aug 2026 18:28:23 +0900 Subject: [PATCH 1/2] Fix name filters for struct field mutants --- NEWS.md | 1 + src/visit.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 646847c4..705de957 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ - New: `#[mutants::exclude_re("pattern")]` attribute to exclude specific mutations by regex, without disabling all mutations on the function. The attribute can be placed on functions, `impl` blocks, `trait` blocks, modules, files, and on expressions that can carry an attribute (such as `match`, struct literals, call expressions, method calls, and unary expressions). Multiple patterns can be applied. Also supported within `cfg_attr`. Requires the [mutants](https://crates.io/crates/mutants) crate version `0.0.5` or later. - Fixed: `#[mutants::skip]` (and `#[cfg_attr(..., mutants::skip)]`) is now honoured when placed on `const` and `static` items, including associated constants in `impl` and `trait` blocks. Previously the attribute was silently ignored on these items and operator mutants inside the initializer expression were still generated ([#508](https://github.com/sourcefrog/cargo-mutants/issues/508)). +- Fixed: Apply `--re` and `--exclude-re` name filters to struct-field deletion mutants. ## 27.1.0 diff --git a/src/visit.rs b/src/visit.rs index d3d9fa8a..01b897ec 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -964,8 +964,15 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> { struct_name: struct_name.clone(), }), ); - if !v.excluded_by_attr_re(&mutant.name) { + if v.excluded_by_attr_re(&mutant.name) { + trace!( + name = mutant.name(false), + "skip mutant by exclude_re attribute" + ); + } else if v.options.allows_mutant(&mutant) { v.mutants.push(mutant); + } else { + trace!(name = mutant.name(false), "skip mutant by options"); } } } @@ -1997,6 +2004,60 @@ mod test { ); } + fn struct_field_mutant_names(options: &Options) -> Vec { + mutate_source_str( + indoc! {" + fn settings() -> Settings { + Settings { + enabled: true, + count: 1 + 2, + ..Default::default() + } + } + "}, + options, + ) + .unwrap() + .into_iter() + .filter(|mutant| mutant.genre == Genre::StructField) + .map(|mutant| mutant.name(false)) + .collect() + } + + #[test] + fn struct_field_mutants_respect_examine_name_filter() { + let options = Options::from_arg_strs(["mutants", "--re", "delete field enabled"]); + + assert_eq!( + struct_field_mutant_names(&options), + ["src/main.rs: delete field enabled from struct Settings expression in settings"] + ); + } + + #[test] + fn struct_field_mutants_respect_exclude_name_filter() { + let options = Options::from_arg_strs(["mutants", "--exclude-re", "delete field enabled"]); + + assert_eq!( + struct_field_mutant_names(&options), + ["src/main.rs: delete field count from struct Settings expression in settings"] + ); + } + + #[test] + fn struct_field_mutants_survive_nonmatching_exclude_name_filter() { + let options = + Options::from_arg_strs(["mutants", "--exclude-re", "this pattern matches no mutant"]); + + assert_eq!( + struct_field_mutant_names(&options), + [ + "src/main.rs: delete field enabled from struct Settings expression in settings", + "src/main.rs: delete field count from struct Settings expression in settings", + ] + ); + } + #[test] fn exclude_re_attr_filters_specific_mutants() { let options = Options::default(); From c7fb815f64b09a4f205475c73ab28cc33e7ad37e Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Mon, 17 Aug 2026 22:21:37 +0900 Subject: [PATCH 2/2] Address review feedback for struct field filters --- NEWS.md | 1 + src/visit.rs | 21 ++++++++------------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/NEWS.md b/NEWS.md index 39a5df01..2003a1d1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ - New: `#[mutants::exclude_re("pattern")]` attribute to exclude specific mutations by regex, without disabling all mutations on the function. The attribute can be placed on functions, `impl` blocks, `trait` blocks, modules, files, and on expressions that can carry an attribute (such as `match`, struct literals, call expressions, method calls, and unary expressions). Multiple patterns can be applied. Also supported within `cfg_attr`. Requires the [mutants](https://crates.io/crates/mutants) crate version `0.0.5` or later. - Fixed: `#[mutants::skip]` (and `#[cfg_attr(..., mutants::skip)]`) is now honoured when placed on `const` and `static` items, including associated constants in `impl` and `trait` blocks. Previously the attribute was silently ignored on these items and operator mutants inside the initializer expression were still generated ([#508](https://github.com/sourcefrog/cargo-mutants/issues/508)). + - Fixed: Apply `--re` and `--exclude-re` name filters to struct-field deletion mutants. - Fixed: Shorter temporary directory names on Windows, to reduce the risk of running into the 260-character path limit. diff --git a/src/visit.rs b/src/visit.rs index 01b897ec..ce039d72 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -442,6 +442,10 @@ impl DiscoveryVisitor<'_> { genre, None, ); + self.push_mutant_if_allowed(mutant); + } + + fn push_mutant_if_allowed(&mut self, mutant: Mutant) { if self.excluded_by_attr_re(&mutant.name) { trace!( name = mutant.name(false), @@ -964,16 +968,7 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> { struct_name: struct_name.clone(), }), ); - if v.excluded_by_attr_re(&mutant.name) { - trace!( - name = mutant.name(false), - "skip mutant by exclude_re attribute" - ); - } else if v.options.allows_mutant(&mutant) { - v.mutants.push(mutant); - } else { - trace!(name = mutant.name(false), "skip mutant by options"); - } + v.push_mutant_if_allowed(mutant); } } } @@ -2025,7 +2020,7 @@ mod test { } #[test] - fn struct_field_mutants_respect_examine_name_filter() { + fn re_filters_struct_field_mutants() { let options = Options::from_arg_strs(["mutants", "--re", "delete field enabled"]); assert_eq!( @@ -2035,7 +2030,7 @@ mod test { } #[test] - fn struct_field_mutants_respect_exclude_name_filter() { + fn exclude_re_filters_struct_field_mutants() { let options = Options::from_arg_strs(["mutants", "--exclude-re", "delete field enabled"]); assert_eq!( @@ -2045,7 +2040,7 @@ mod test { } #[test] - fn struct_field_mutants_survive_nonmatching_exclude_name_filter() { + fn nonmatching_exclude_re_doesnt_filter_struct_field_mutants() { let options = Options::from_arg_strs(["mutants", "--exclude-re", "this pattern matches no mutant"]);