Add #[mutants::exclude_re("pattern")] attribute#607
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new #[mutants::exclude_re("...")] attribute to allow excluding only specific mutants (by regex) while still generating other mutations, including inheritance from outer scopes and support inside cfg_attr.
Changes:
- Introduces the
mutants::exclude_reproc-macro attribute (no-op at compile time) and documents it. - Implements exclude-by-regex behavior in the discovery visitor via an inherited scope stack, plus regex parsing from attributes (including
cfg_attr). - Adds integration + fixture coverage (new testdata tree, new snapshot, and a new integration test).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
mutants_attrs/src/lib.rs |
Adds the exclude_re proc-macro attribute entry point and docs. |
src/visit.rs |
Implements exclude-re parsing, scope inheritance, and filtering during mutant collection; adds unit tests. |
testdata/exclude_re_attr/src/lib.rs |
New fixture crate source exercising supported scopes. |
testdata/exclude_re_attr/Cargo_test.toml |
New fixture crate manifest for the exclude-re attribute tests. |
tests/main.rs |
Adds an integration test to snapshot --list output for the new fixture. |
tests/util/snapshots/main__util__list_mutants_in_exclude_re_attr.snap |
Snapshot for the new integration test output. |
book/src/attrs.md |
Documents the new attribute, usage, and scope inheritance rules. |
NEWS.md |
Adds an “Unreleased” changelog entry for the new attribute. |
Comments suppressed due to low confidence (1)
src/visit.rs:636
visit_item_modpushes an exclude_re scope, but the early-return path whenfind_path_attributereports an invalid (absolute)#[path]returns without popping. That leaves the exclude_re stack unbalanced and can incorrectly apply the module’s exclude patterns to the rest of the file. Pop the scope before returning (or use an RAII guard to ensure pop on all exits).
if !self.push_exclude_re(&node.attrs) {
return;
}
let source_location = Span::from(node.span());
// Extract path attribute value, if any (e.g. `#[path="..."]`)
let path_attribute = match find_path_attribute(&node.attrs) {
Ok(path) => path,
Err(path_attribute) => {
let definition_site = self
.source_file
.format_source_location(source_location.start);
error!(?path_attribute, ?definition_site, %mod_name, "invalid filesystem traversal in mod path attribute");
return;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add a new attribute that excludes specific mutations by regex, without disabling all mutations on the function like #[mutants::skip] does. The attribute can be placed on functions, impl blocks, trait blocks, modules, and files (as an inner attribute). Patterns from outer scopes are inherited. Also supported within cfg_attr. Closes sourcefrog#551
add_numbers used 'replace .* with ()' where () was a regex capture group matching empty string, causing it to exclude ALL mutations instead of just 'with ()'. Use r"with \(\)" instead. subtract used 'replace .* with' which excluded everything. Use 'with 0' to demonstrate cfg_attr while keeping other mutations.
- mutants_attrs: bump version 0.0.4 -> 0.0.5 for the new exclude_re attribute. - visit.rs: replace TokenStream::to_string() + substring scan in attr_mutants_exclude_re_pattern with AST-based parsing using Attribute::parse_args_with and Attribute::parse_nested_meta, mirroring attr_is_mutants_skip. Handles multiple exclude_re attrs in one cfg_attr, arbitrary whitespace, and other token shapes correctly. - visit.rs: malformed #[mutants::exclude_re] (missing arg, multiple args, non-string arg) is now a hard error stored on visitor.error, matching the existing invalid-regex behaviour. Previously these were silently no-ops, giving users a false sense of safety. - visit.rs: invalid-regex error now includes the source file/line of the offending attribute and the offending pattern, e.g. "src/main.rs:3:1: invalid regex in #[mutants::exclude_re(\"(unclosed\")]: ...". - visit.rs: introduce in_exclude_re_scope closure helper that guarantees pop_exclude_re runs even if the visited body returns early. visit_item_mod previously left the exclude_re stack unbalanced on its invalid-#[path] early return; this also covers future early-return paths in visit_item_impl and friends. - Tests: rewrite exclude_re_attr_filters_specific_mutants to actually exercise filtering on an i32 function (exclude "with 0", assert "with 1"/"with -1"/binop survive) - previously the regex with \(\) was a no-op on i32, so the test passed even if filtering was broken. - Tests: strengthen exclude_re_attr_keeps_all_when_no_match to compare position-independent mutant names, not just lengths. - Tests: add regression tests for malformed attribute forms and for the source-location in the error message. - testdata/exclude_re_attr & book/src/attrs.md: switch the misleading with \(\) example to with 0 so it actually demonstrates the attribute working on an i32 function. Regenerate the integration snapshot, which now visibly shows with 0 filtered out of add_numbers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
63b9c32 to
d0314a9
Compare
|
Addressed all feedback. Robot summary: Owner feedback (@sourcefrog):
Copilot reviewer feedback:
Also addressed (from the suppressed low-confidence review comment about Also added |
The feature was already supported (via DiscoveryVisitor::visit_file
picking up the file's inner attributes) and had a unit test in
visit.rs, but the integration testdata tree didn't exercise it.
Add a sub-module file_scoped.rs that uses an inner attribute
#![mutants::exclude_re("replace .* -> bool")] and refresh the
list_mutants_in_exclude_re_attr snapshot to confirm the bool-returning
function's mutations are filtered out while the i32-returning function's
mutations remain.
Also fix a cargo fmt deviation introduced earlier on this branch in the
exclude_re_attr_keeps_all_when_no_match test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wrap the body of visit_expr_call, visit_expr_method_call, visit_expr_unary,
visit_expr_match, and visit_expr_struct in in_exclude_re_scope so that an
exclude_re attribute on such an expression suppresses mutants generated by
that expression and any expression nested within it. Patterns from enclosing
scopes are still inherited via the existing exclude_re stack.
Add a comment to visit_expr_binary explaining why no equivalent wrapping is
applied there: a #[mutants::exclude_re("...")] placed before a bare binary
expression like a + b is absorbed by syn into the leftmost operand's attrs
(e.g., the path expression) rather than into ExprBinary.attrs, so the field
is unreachable from current Rust source.
Tests are organised one file per expression type under src/visit/test/ and
declared as submodules of the existing inline mod test in src/visit.rs.
The exclude_re_expr_common.rs file also locks in the precedence rule that
mutants::skip wins over mutants::exclude_re on the same expression (even
when the regex is malformed) in both attribute orders, to guard against
regressions in future unification refactors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I noticed that |
The previous wording (looks for ... within other attributes such as cfg_attr, without evaluating the outer attribute) was imprecise on two points: - It implied that any wrapper attribute can contain mutants::exclude_re, when only cfg_attr is actually recognised. - It described cfg_attr as he outer attribute and said it was not �valuated, leaving ambiguous whether the cfg *condition* was evaluated. Reword to make both points unambiguous. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a new attribute that excludes specific mutations by regex, without disabling all mutations on the function like
#[mutants::skip]does.The attribute can be placed on functions,
implblocks,traitblocks, modules, and files (as an inner attribute). Patterns from outer scopes are inherited. Also supported withincfg_attr.Changes
exclude_reproc-macro attribute (no-op, likeskip)exclude_re_stackon the discovery visitor, push/pop at scope boundaries,attrs_exclude_re_patternsparsing, filtering incollect_mutantDesign decisions
--exclude-reCLI behavior#[mutants::exclude_re]attributes on the same item are OR'dCloses #551