use super::pattern_adjustments::{PatternMatchInfo, PatternRecognizer, PatternType};
use syn::{Expr, ExprMatch, Pat, Stmt};
pub struct MatchExpressionRecognizer;
impl Default for MatchExpressionRecognizer {
fn default() -> Self {
Self
}
}
impl MatchExpressionRecognizer {
pub fn new() -> Self {
Self
}
#[allow(clippy::only_used_in_recursion)]
pub fn is_simple_arm(&self, body: &Expr) -> bool {
match body {
Expr::Return(_) | Expr::Break(_) | Expr::Continue(_) => true,
Expr::Lit(_) | Expr::Path(_) => true,
Expr::MethodCall(_) | Expr::Field(_) => true,
Expr::Call(call) => {
matches!(&*call.func, Expr::Path(_))
}
Expr::Try(try_expr) => self.is_simple_arm(&try_expr.expr),
Expr::Macro(_) => true,
Expr::Block(block) => {
let block = &block.block;
match block.stmts.len() {
0 => true, 1 => match &block.stmts[0] {
Stmt::Expr(expr, _) => self.is_simple_arm(expr),
_ => false,
},
2 => {
matches!(&block.stmts[1], Stmt::Expr(Expr::Return(_), _))
}
_ => false,
}
}
_ => false,
}
}
fn has_wildcard_arm(&self, match_expr: &ExprMatch) -> bool {
match_expr
.arms
.iter()
.any(|arm| matches!(&arm.pat, Pat::Wild(_) | Pat::Ident(_)))
}
fn detect_enum_matching(&self, match_expr: &ExprMatch) -> bool {
let variant_count = match_expr
.arms
.iter()
.filter(|arm| {
matches!(
&arm.pat,
Pat::Path(_) | Pat::TupleStruct(_) | Pat::Struct(_)
)
})
.count();
variant_count as f32 / match_expr.arms.len() as f32 > 0.5
}
fn detect_string_matching(&self, match_expr: &ExprMatch) -> bool {
match_expr.arms.iter().any(|arm| {
if let Pat::Lit(pat_lit) = &arm.pat {
matches!(pat_lit.lit, syn::Lit::Str(_))
} else {
false
}
})
}
}
impl PatternRecognizer for MatchExpressionRecognizer {
fn detect(&self, block: &syn::Block) -> Option<PatternMatchInfo> {
block
.stmts
.iter()
.find_map(|stmt| self.analyze_statement(stmt))
}
fn adjust_complexity(&self, info: &PatternMatchInfo, _base: u32) -> u32 {
let adjusted = (info.condition_count as f32).log2().ceil() as u32;
let default_penalty = if !info.has_default { 1 } else { 0 };
adjusted + default_penalty
}
}
impl MatchExpressionRecognizer {
fn analyze_statement(&self, stmt: &Stmt) -> Option<PatternMatchInfo> {
match stmt {
Stmt::Expr(Expr::Match(match_expr), _) => self.analyze_match_expression(match_expr),
_ => None,
}
}
fn analyze_match_expression(&self, match_expr: &ExprMatch) -> Option<PatternMatchInfo> {
if !self.is_pattern_extractable(match_expr) {
return None;
}
let pattern_type = self.determine_pattern_type(match_expr);
Some(PatternMatchInfo {
variable_name: "match_expr".to_string(),
condition_count: match_expr.arms.len(),
has_default: self.has_wildcard_arm(match_expr),
pattern_type,
})
}
fn is_pattern_extractable(&self, match_expr: &ExprMatch) -> bool {
match_expr.arms.len() >= 3
&& match_expr
.arms
.iter()
.all(|arm| self.is_simple_arm(&arm.body))
}
fn determine_pattern_type(&self, match_expr: &ExprMatch) -> PatternType {
if self.detect_enum_matching(match_expr) {
PatternType::EnumMatching
} else if self.detect_string_matching(match_expr) {
PatternType::StringMatching
} else {
PatternType::SimpleComparison
}
}
fn all_arms_simple(&self, match_expr: &ExprMatch) -> bool {
match_expr
.arms
.iter()
.all(|arm| self.is_simple_arm(&arm.body))
}
pub fn analyze_expr(&self, expr: &Expr, min_arms: usize) -> Option<PatternMatchInfo> {
let match_expr = match expr {
Expr::Match(m) => m,
_ => return None,
};
if match_expr.arms.len() < min_arms || !self.all_arms_simple(match_expr) {
return None;
}
Some(PatternMatchInfo {
variable_name: "match_expr".to_string(),
condition_count: match_expr.arms.len(),
has_default: self.has_wildcard_arm(match_expr),
pattern_type: self.determine_pattern_type(match_expr),
})
}
}
pub fn detect_match_expression(expr: &Expr) -> Option<PatternMatchInfo> {
MatchExpressionRecognizer::new().analyze_expr(expr, 2)
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_match_expression_detection() {
let block: syn::Block = parse_quote! {{
match file_type {
FileType::Rust => "rust",
FileType::Python => "python",
FileType::JavaScript => "javascript",
FileType::TypeScript => "typescript",
_ => "unknown",
}
}};
let recognizer = MatchExpressionRecognizer::new();
let info = recognizer.detect(&block);
assert!(info.is_some());
let info = info.unwrap();
assert_eq!(info.condition_count, 5);
assert_eq!(info.pattern_type, PatternType::EnumMatching);
assert!(info.has_default);
}
#[test]
fn test_logarithmic_scaling_for_match() {
let info = PatternMatchInfo {
variable_name: "test".to_string(),
condition_count: 16,
has_default: true,
pattern_type: PatternType::EnumMatching,
};
let recognizer = MatchExpressionRecognizer::new();
let adjusted = recognizer.adjust_complexity(&info, 16);
assert_eq!(adjusted, 4);
}
#[test]
fn test_simple_arm_detection() {
let recognizer = MatchExpressionRecognizer::new();
let expr: Expr = parse_quote!(return 42);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(42);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(FileType::Rust);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(if x > 0 { foo() } else { bar() });
assert!(!recognizer.is_simple_arm(&expr));
}
#[test]
fn test_detect_enum_matching_comprehensive() {
let recognizer = MatchExpressionRecognizer::new();
let match_expr: ExprMatch = parse_quote! {
match value {
Enum::Variant1 => 1,
Enum::Variant2(x) => x,
Enum::Variant3 { field } => field,
SomeType::Other => 0,
_ => -1,
}
};
assert!(recognizer.detect_enum_matching(&match_expr));
}
#[test]
fn test_detect_enum_matching_low_ratio() {
let recognizer = MatchExpressionRecognizer::new();
let match_expr: ExprMatch = parse_quote! {
match value {
1 => "one",
2 => "two",
3 => "three",
Enum::Variant => "enum",
_ => "other",
}
};
assert!(!recognizer.detect_enum_matching(&match_expr));
}
#[test]
fn test_detect_string_matching() {
let recognizer = MatchExpressionRecognizer::new();
let match_expr: ExprMatch = parse_quote! {
match input {
"hello" => 1,
"world" => 2,
Enum::Variant => 3,
_ => 0,
}
};
assert!(recognizer.detect_string_matching(&match_expr));
}
#[test]
fn test_detect_no_string_matching() {
let recognizer = MatchExpressionRecognizer::new();
let match_expr: ExprMatch = parse_quote! {
match value {
1 => "one",
2 => "two",
_ => "other",
}
};
assert!(!recognizer.detect_string_matching(&match_expr));
}
#[test]
fn test_wildcard_arm_detection() {
let recognizer = MatchExpressionRecognizer::new();
let match_expr: ExprMatch = parse_quote! {
match value {
1 => "one",
2 => "two",
_ => "other",
}
};
assert!(recognizer.has_wildcard_arm(&match_expr));
let match_expr: ExprMatch = parse_quote! {
match value {
1 => "one",
2 => "two",
}
};
assert!(!recognizer.has_wildcard_arm(&match_expr));
}
#[test]
fn test_is_simple_arm_edge_cases() {
let recognizer = MatchExpressionRecognizer::new();
let expr: Expr = parse_quote!({ 42 });
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!({
return 42;
});
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!({
let x = 42;
return x;
});
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!({
let x = 42;
let y = 43;
return x + y;
});
assert!(!recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(obj.method());
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(obj.field);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(SomeType::new());
assert!(recognizer.is_simple_arm(&expr));
}
#[test]
fn test_detect_insufficient_arms() {
let recognizer = MatchExpressionRecognizer::new();
let block: syn::Block = parse_quote! {{
match value {
1 => "one",
_ => "other",
}
}};
let info = recognizer.detect(&block);
assert!(info.is_none());
}
#[test]
fn test_adjust_complexity_with_default() {
let recognizer = MatchExpressionRecognizer::new();
let info = PatternMatchInfo {
variable_name: "test".to_string(),
condition_count: 8,
has_default: true,
pattern_type: PatternType::EnumMatching,
};
let adjusted = recognizer.adjust_complexity(&info, 8);
assert_eq!(adjusted, 3);
}
#[test]
fn test_adjust_complexity_without_default() {
let recognizer = MatchExpressionRecognizer::new();
let info = PatternMatchInfo {
variable_name: "test".to_string(),
condition_count: 8,
has_default: false,
pattern_type: PatternType::EnumMatching,
};
let adjusted = recognizer.adjust_complexity(&info, 8);
assert_eq!(adjusted, 4);
}
#[test]
fn test_direct_match_expression_detection() {
let expr: Expr = parse_quote! {
match status {
Status::Active => "active",
Status::Inactive => "inactive",
Status::Pending => "pending",
}
};
let info = detect_match_expression(&expr);
assert!(info.is_some());
let info = info.unwrap();
assert_eq!(info.condition_count, 3);
assert_eq!(info.pattern_type, PatternType::EnumMatching);
assert!(!info.has_default);
}
#[test]
fn test_direct_match_expression_with_complex_arms() {
let expr: Expr = parse_quote! {
match value {
1 => {
let x = complex_computation();
if x > 0 {
x * 2
} else {
0
}
},
2 => simple_value(),
_ => 0,
}
};
let info = detect_match_expression(&expr);
assert!(info.is_none());
}
#[test]
fn test_simple_arm_try_expression() {
let recognizer = MatchExpressionRecognizer::new();
let expr: Expr = parse_quote!(helper_fn()?);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(self.write_header()?);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(foo()?.bar()?);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(some_result?);
assert!(recognizer.is_simple_arm(&expr));
}
#[test]
fn test_simple_arm_macro_invocation() {
let recognizer = MatchExpressionRecognizer::new();
let expr: Expr = parse_quote!(writeln!(w, "text"));
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(writeln!(w, "text")?);
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(println!("debug"));
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(format!("{}", x));
assert!(recognizer.is_simple_arm(&expr));
}
#[test]
fn test_simple_arm_block_with_try() {
let recognizer = MatchExpressionRecognizer::new();
let expr: Expr = parse_quote!({ helper_fn()? });
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!({ writeln!(w, "text")? });
assert!(recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!({});
assert!(recognizer.is_simple_arm(&expr));
}
#[test]
fn test_complex_arm_not_simple() {
let recognizer = MatchExpressionRecognizer::new();
let expr: Expr = parse_quote!({
let x = compute();
process(x)?;
cleanup()
});
assert!(!recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(if cond { foo() } else { bar() });
assert!(!recognizer.is_simple_arm(&expr));
let expr: Expr = parse_quote!(for i in 0..10 {
process(i)
});
assert!(!recognizer.is_simple_arm(&expr));
}
#[test]
fn test_write_section_style_match() {
let block: syn::Block = parse_quote! {{
match section {
Section::Header { rank, score } => write_header(w, rank, score)?,
Section::Location { file, line } => writeln!(w, "{}:{}", file, line)?,
Section::Action { action } => writeln!(w, "{}", action)?,
Section::Impact { reduction } => write_impact(w, reduction)?,
Section::Evidence { text } => writeln!(w, "{}", text)?,
}
}};
let recognizer = MatchExpressionRecognizer::new();
let info = recognizer.detect(&block);
assert!(info.is_some(), "Should detect as pattern match");
let info = info.unwrap();
assert_eq!(info.condition_count, 5);
let adjusted = recognizer.adjust_complexity(&info, 5);
assert!(
adjusted <= 4,
"Adjusted complexity should be ~3-4, got {}",
adjusted
);
}
}