boilerplate-recursion
A recursive walk over a type is written out by hand, arm by arm.
- Group
- Opportunities
- Runs
- Runs under ur report:
ur report --rule boilerplate-recursion - Level
- note
- Reads
- Every file
- Fix
- None: the shape of the shared walk, and its name, are a design choice
Why
These recursive functions match on the variants of a type, and most arms only recurse into the fields they bind, combining the results the same way each time, or do nothing. A generic walk over the type, written or derived once, would do what those arms do, leaving each function only the arms that do its own work. It reads every language whose grammar states its shapes in a Shapes.rsc; an advisory rule that ur report runs.
The fix, as Ur suggests it: write or derive one generic walk over the type, and keep only the arms that do their own work.
For agents
Before writing a recursive function that matches on every variant of a type, look for a generic walk over the type and override only the variants the function is about.
Example
src/filter.rspub enum Filter {
And(Box<Filter>, Box<Filter>),
Or(Box<Filter>, Box<Filter>),
Not(Box<Filter>),
Any(Vec<Filter>),
Field(String, String),
}
pub fn mentions(filter: &Filter, name: &str) -> bool {
match filter {
Filter::And(left, right) | Filter::Or(left, right) => {
mentions(left, name) || mentions(right, name)
}
Filter::Not(inner) => mentions(inner, name),
Filter::Any(filters) => filters.iter().any(|filter| mentions(filter, name)),
Filter::Field(field, _) => field == name,
}
}
$ ur report --rule boilerplate-recursion
opportunity[boilerplate-recursion]: `mentions` walks `Filter` by hand: 4 of 5 variants only recurse into the fields they bind (saves about 4 lines)
--> ./src/filter.rs:10:5-17 match in `mentions`: 5 of 5 variants only recurse or do nothing
= hint: write or derive one generic walk over the type, and keep only the arms that do their own work
1 opportunity saving about 4 lines in all