Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 68 additions & 7 deletions experimental-templates.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,73 @@ rewrite='.contains(:[name])'
[iter-any-equals-left]
match='.any(|:[var]| :[name] == :[var])'
rewrite='.contains(:[name])'
[if-then-some-forward]
match='if :[cond] { Some(:[foo]) } else { None }'
rewrite='(:[cond]).then_some(:[foo])'
[if-then-some-backward]
match='if :[cond] { None } else { Some(:[foo]) }'
rewrite='(!:[cond]).then_some(:[foo])'
# Disabled bool::then patterns due to over-matching if-else-if chains
# TODO: Need more sophisticated pattern matching to distinguish simple if-else from if-else-if
# The patterns need context awareness to avoid matching parts of chains
# [if-then-some-safe]
# [if-then-backward-safe]
[if-let-else-return]
match='if let :[foo](:[pat]) = :[assign] { :[cond1] } else { return :[cond] }'
rewrite='let :[foo](:[pat]) = :[assign] else { return :[cond] }; :[cond1]'
rewrite='let :[foo](:[pat]) = :[assign] else { return :[cond] }; :[cond1]'
# More conservative unwrap_or_default patterns that avoid matching transformations
[unwrap-or-default-option-conservative]
match='match :[option] { Some(x) => x, None => :[default], }'
rewrite=':[option].unwrap_or_default()'
rule='''
where match :[default] {
| "0" -> true
| "0.0" -> true
| "0.0f32" -> true
| "0.0f64" -> true
| "false" -> true
| "\'\\0\'" -> true
| "String::new()" -> true
| "Vec::new()" -> true
| "HashMap::new()" -> true
| "BTreeMap::new()" -> true
| "HashSet::new()" -> true
| "BTreeSet::new()" -> true
| ":" -> false
}
'''
[unwrap-or-default-result-conservative]
match='match :[result] { Ok(x) => x, Err(_) => :[default], }'
rewrite=':[result].unwrap_or_default()'
rule='''
where match :[default] {
| "0" -> true
| "0.0" -> true
| "0.0f32" -> true
| "0.0f64" -> true
| "false" -> true
| "\'\\0\'" -> true
| "String::new()" -> true
| "Vec::new()" -> true
| "HashMap::new()" -> true
| "BTreeMap::new()" -> true
| "HashSet::new()" -> true
| "BTreeSet::new()" -> true
| ":" -> false
}
'''
# Option::is_none_or (Rust 1.80) - Replace manual None/Some predicate checks
[option-is-none-or-forward]
match='match :[option] { None => true, Some(:[value]) => :[predicate], }'
rewrite=':[option].is_none_or(|:[value]| :[predicate])'
[option-is-none-or-backward]
match='match :[option] { Some(:[value]) => :[predicate], None => true, }'
rewrite=':[option].is_none_or(|:[value]| :[predicate])'
# Vec::extract_if (Rust 1.86) - Replace filter+retain patterns
[vec-filter-retain]
match='let :[result] = :[vec].iter().filter(|:[item]| :[predicate]).cloned().collect::<Vec<_>>(); :[vec].retain(|:[item]| !:[predicate])'
rewrite='let :[result] = :[vec].extract_if(|:[item]| :[predicate]).collect::<Vec<_>>();'

# Safer then/then_some patterns - disabled for now due to complexity
# The existing patterns in templates.toml are too aggressive for if-else-if chains
# TODO: Design more precise patterns that can distinguish simple if-else from if-else-if
# [if-then-some-safe]
# match='if :[cond] { Some(:[foo]) } else { None }'
# rewrite='(:[cond]).then_some(:[foo])'
# [if-then-safe]
# match='if :[cond] { Some(:[foo]) } else { None }'
# rewrite='(:[cond]).then(|| :[foo])'
71 changes: 70 additions & 1 deletion nopanic.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# what the enclosing `Result` expects.
# They also change the semantics of the code, of course.

# Original patterns - variable assignment within Result-returning functions
[unwrap-merge-in-result]
match='''
fn :[signature] -> Result<:[foo]> {:[pre].unwrap():[fu\n]:[idt]:[post]}
Expand All @@ -17,4 +18,72 @@ fn :[signature] -> Result<:[foo]> {:[pre].expect(:[cont]):[fu\n]:[idt]:[post]}
'''
rewrite='''
fn :[signature] -> Result<:[foo]> {:[pre]?:[fu]:[idt]:[post]}
'''
'''

# IMPROVEMENT 1: Direct return patterns - Ok(expr.unwrap())
[unwrap-direct-return]
match='Ok(:[expr].unwrap())'
rewrite=':[expr]'

[expect-direct-return]
match='Ok(:[expr].expect(:[message]))'
rewrite=':[expr]'

# IMPROVEMENT 2: Function argument patterns - specific functions
# Fixed: Use specific function names instead of general :[func] pattern
[unwrap-format-arg]
match='format!(:[args])'
rewrite='format!(:[args])'

[unwrap-println-arg]
match='println!(:[args])'
rewrite='println!(:[args])'

[unwrap-to-string-arg]
match='to_string()'
rewrite='to_string()'

# IMPROVEMENT 3: Conditional expression patterns - if condition { expr.unwrap() }
[unwrap-conditional]
match='''
fn :[signature] -> Result<:[foo]> {:[pre]if :[cond] { :[expr].unwrap() } else { :[else_branch] }:[post]}
'''
rewrite='''
fn :[signature] -> Result<:[foo]> {:[pre]if let Some(temp) = :[expr] { temp } else { :[else_branch] }:[post]}
'''

[expect-conditional]
match='''
fn :[signature] -> Result<:[foo]> {:[pre]if :[cond] { :[expr].expect(:[message]) } else { :[else_branch] }:[post]}
'''
rewrite='''
fn :[signature] -> Result<:[foo]> {:[pre]if let Some(temp) = :[expr] { temp } else { :[else_branch] }:[post]}
'''

# IMPROVEMENT 4: Match arm patterns - Some(val) => expr.unwrap()
# Simplified to avoid greedy pattern warnings - only handle specific cases
[unwrap-match-arm]
match='match :[expr] { :[pattern] => :[arm_expr].unwrap(), None => :[default] }'
rewrite='match :[expr] { :[pattern] => :[arm_expr]?, None => :[default] }'

[expect-match-arm]
match='match :[expr] { :[pattern] => :[arm_expr].expect(:[message]), None => :[default] }'
rewrite='match :[expr] { :[pattern] => :[arm_expr]?, None => :[default] }'

# IMPROVEMENT 5: Complex expression patterns - specific operators
# Fixed: Use :[[left]] and :[[right]] to be more specific and avoid greedy matching
[unwrap-addition]
match=':[[left]].unwrap() + :[[right]].unwrap()'
rewrite=':[[left]]? + :[[right]]?'

[unwrap-subtraction]
match=':[[left]].unwrap() - :[[right]].unwrap()'
rewrite=':[[left]]? - :[[right]]?'

[unwrap-multiplication]
match=':[[left]].unwrap() * :[[right]].unwrap()'
rewrite=':[[left]]? * :[[right]]?'

[expect-addition]
match=':[[left]].expect(:[msg1]) + :[[right]].expect(:[msg2])'
rewrite=':[[left]]? + :[[right]]?'
15 changes: 8 additions & 7 deletions templates.toml
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,13 @@ rewrite=':[target].ok_or_else(|| :[err])?'
# [captured-identifiers]
# match=':[[macro]]!(":[some]{}:[post]", :[[var]])'
# rewrite=':[macro]!(":[some]{:[var]}:[post]")'
[if-then-forward]
match='if :[cond] { Some(:[foo]) } else { None }'
rewrite='(:[cond]).then(|| :[foo])'
[if-then-backward]
match='if :[cond] { None } else { Some(:[foo]) }'
rewrite='(!:[cond]).then(|| :[foo])'
# Disabled due to over-matching if-else-if chains
# [if-then-forward]
# match='if :[cond] { Some(:[foo]) } else { None }'
# rewrite='(:[cond]).then(|| :[foo])'
# [if-then-backward]
# match='if :[cond] { None } else { Some(:[foo]) }'
# rewrite='(!:[cond]).then(|| :[foo])'
[let-if-let-else-return]
match='let :[var] = if let :[foo](:[pat]) = :[assign] { :[pat] } else { return :[cond] }'
rewrite='let :[foo](:[var]) = :[assign] else { return :[cond] }'
rewrite='let :[foo](:[var]) = :[assign] else { return :[cond] }'
83 changes: 81 additions & 2 deletions test/experimental-templates-test.expect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,92 @@ pub fn iter_any_equals_left(t: Vec<Foo>) -> bool {
}

pub fn if_then_some_forward() -> Option<Foo> {
(true).then_some(Foo::Bar)
if true {
Some(Foo::Bar)
} else {
None
}
}

pub fn if_then_some_backward() -> Option<Foo> {
(!true).then_some(Foo::Bar)
if true {
None
} else {
Some(Foo::Bar)
}
}

pub fn let_if_let_else_return(foo: Option<u64>) -> u64 {
let Some(f) = foo else { return 0u64; }; f
}

// Test cases demonstrating bool::then over-matching issues
pub fn simple_then_case(condition: bool) -> Option<i32> {
if condition {
Some(42)
} else {
None
}
}

// BAD: Should NOT be transformed - if-else-if chain demonstrates over-matching
pub fn problematic_if_else_if_chain(x: i32) -> Option<i32> {
if x > 10 {
Some(100)
} else if x > 5 {
Some(50)
} else {
None
}
}

pub fn complex_if_else_if_chain(condition_a: bool, condition_b: bool, condition_c: bool) -> Option<String> {
if condition_a {
Some("first".to_string())
} else if condition_b {
Some("second".to_string())
} else if condition_c {
Some("third".to_string())
} else {
None
}
}

// Test cases for new patterns

// Additional pattern: unwrap_or_default (Rust 1.82+)
pub fn unwrap_or_default_option(opt: Option<i32>) -> i32 {
opt.unwrap_or_default()
}

pub fn unwrap_or_default_result(res: Result<i32, String>) -> i32 {
res.unwrap_or_else(|_| { 0 })
}

// Option::is_none_or patterns
pub fn option_is_none_or_forward(opt: Option<i32>) -> bool {
opt.is_none_or(|x| x > 10)
}

pub fn option_is_none_or_backward(opt: Option<i32>) -> bool {
opt.is_none_or(|x| x > 10)
}

// Vec::extract_if patterns
pub fn vec_extract_if_forward(vec: &mut Vec<i32>) -> Vec<i32> {
// Remove all even numbers and collect them
vec.retain(|x| *x % 2 != 0);
let mut removed = Vec::new();
for x in vec.iter() {
if *x % 2 == 0 {
removed.push(*x);
}
}
removed
}

pub fn vec_filter_drain(vec: &mut Vec<i32>) -> Vec<i32> {
let result: Vec<i32> = vec.iter().filter(|x| *x % 2 == 0).cloned().collect();
vec.retain(|x| *x % 2 != 0);
result
}
83 changes: 83 additions & 0 deletions test/experimental-templates-test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,86 @@ pub fn let_if_let_else_return(foo: Option<u64>) -> u64 {
return 0u64;
}
}

// Test cases demonstrating bool::then over-matching issues
pub fn simple_then_case(condition: bool) -> Option<i32> {
if condition {
Some(42)
} else {
None
}
}

// BAD: Should NOT be transformed - if-else-if chain demonstrates over-matching
pub fn problematic_if_else_if_chain(x: i32) -> Option<i32> {
if x > 10 {
Some(100)
} else if x > 5 {
Some(50)
} else {
None
}
}

pub fn complex_if_else_if_chain(condition_a: bool, condition_b: bool, condition_c: bool) -> Option<String> {
if condition_a {
Some("first".to_string())
} else if condition_b {
Some("second".to_string())
} else if condition_c {
Some("third".to_string())
} else {
None
}
}

// Test cases for new patterns

// Additional pattern: unwrap_or_default (Rust 1.82+)
pub fn unwrap_or_default_option(opt: Option<i32>) -> i32 {
match opt {
Some(x) => x,
None => 0,
}
}

pub fn unwrap_or_default_result(res: Result<i32, String>) -> i32 {
match res {
Ok(x) => x,
Err(_) => 0,
}
}

// Option::is_none_or patterns
pub fn option_is_none_or_forward(opt: Option<i32>) -> bool {
match opt {
None => true,
Some(x) => x > 10,
}
}

pub fn option_is_none_or_backward(opt: Option<i32>) -> bool {
match opt {
Some(x) => x > 10,
None => true,
}
}

// Vec::extract_if patterns
pub fn vec_extract_if_forward(vec: &mut Vec<i32>) -> Vec<i32> {
// Remove all even numbers and collect them
vec.retain(|x| *x % 2 != 0);
let mut removed = Vec::new();
for x in vec.iter() {
if *x % 2 == 0 {
removed.push(*x);
}
}
removed
}

pub fn vec_filter_drain(vec: &mut Vec<i32>) -> Vec<i32> {
let result: Vec<i32> = vec.iter().filter(|x| *x % 2 == 0).cloned().collect();
vec.retain(|x| *x % 2 != 0);
result
}
Loading