use crate::parser::{BinaryOp, Expression, Literal};
pub fn collect_concat_parts<'ast>(expr: &Expression<'ast>) -> Vec<Expression<'ast>> {
let mut parts = Vec::new();
collect_concat_parts_recursive(expr, &mut parts);
parts
}
fn collect_concat_parts_recursive<'ast>(
expr: &Expression<'ast>,
parts: &mut Vec<Expression<'ast>>,
) {
match expr {
Expression::Binary {
left,
op: BinaryOp::Add,
right,
..
} => {
collect_concat_parts_recursive(left, parts);
collect_concat_parts_recursive(right, parts);
}
_ => {
parts.push(expr.clone());
}
}
}
pub fn collect_concat_parts_static<'ast>(
expr: &Expression<'ast>,
parts: &mut Vec<Expression<'ast>>,
) {
collect_concat_parts_recursive(expr, parts);
}
pub fn contains_string_literal(expr: &Expression) -> bool {
match expr {
Expression::Literal {
value: Literal::String(_),
..
} => true,
Expression::Binary { left, right, .. } => {
contains_string_literal(left) || contains_string_literal(right)
}
_ => false,
}
}
pub fn expression_produces_string(expr: &Expression) -> bool {
use crate::parser::Statement;
match expr {
Expression::MacroInvocation { name, .. } => {
matches!(name.as_str(), "format" | "concat" | "format_args" | "write")
}
Expression::Call { function, .. } => {
if let Expression::Identifier { name, .. } = &**function {
name == "format" || name == "String" || name == "to_string"
} else if let Expression::FieldAccess { field, .. } = &**function {
field == "from" || field == "to_string"
} else {
false
}
}
Expression::MethodCall { method, .. } => method == "to_string" || method == "to_owned",
Expression::Block { statements, .. } => {
if let Some(last) = statements.last() {
match last {
Statement::Expression { expr, .. } => expression_produces_string(expr),
Statement::If {
then_block,
else_block,
..
} => {
let then_produces_string = then_block.last().is_some_and(|s| {
if let Statement::Expression { expr, .. } = s {
expression_produces_string(expr)
} else {
false
}
});
let else_produces_string = else_block.as_ref().is_some_and(|block| {
block.last().is_some_and(|s| {
if let Statement::Expression { expr, .. } = s {
expression_produces_string(expr)
} else {
false
}
})
});
then_produces_string || else_produces_string
}
_ => false,
}
} else {
false
}
}
_ => false,
}
}
pub fn expression_has_as_str(expr: &Expression) -> bool {
match expr {
Expression::MethodCall { method, object, .. } => {
super::rust_stdlib_annotations::is_strip_redundant(method)
|| expression_has_as_str(object)
}
Expression::Block { statements, .. } => block_has_as_str(statements),
Expression::FieldAccess { object, .. } => expression_has_as_str(object),
_ => false,
}
}
pub fn statement_has_as_str(stmt: &crate::parser::Statement) -> bool {
use crate::parser::Statement;
match stmt {
Statement::Expression { expr, .. } => expression_has_as_str(expr),
Statement::Return {
value: Some(expr), ..
} => expression_has_as_str(expr),
Statement::If {
then_block,
else_block,
..
} => {
block_has_as_str(then_block) || else_block.as_ref().is_some_and(|b| block_has_as_str(b))
}
_ => false,
}
}
pub fn block_has_as_str<'ast>(stmts: &[&'ast crate::parser::Statement<'ast>]) -> bool {
stmts.iter().any(|s| statement_has_as_str(s))
}
pub fn block_has_explicit_ref<'ast>(stmts: &[&'ast crate::parser::Statement<'ast>]) -> bool {
use crate::parser::Statement;
if stmts.is_empty() {
return false;
}
if let Some(last_stmt) = stmts.last() {
match last_stmt {
Statement::Expression { expr, .. } => expression_is_explicit_ref(expr),
Statement::Return {
value: Some(expr), ..
} => expression_is_explicit_ref(expr),
_ => false,
}
} else {
false
}
}
pub fn expression_is_explicit_ref(expr: &Expression) -> bool {
match expr {
Expression::Unary {
op: crate::parser::UnaryOp::Ref,
..
} => true,
Expression::Block { statements, .. } => block_has_explicit_ref(statements),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::source_map::Location;
use crate::test_utils::test_alloc_expr;
use std::path::PathBuf;
fn test_loc() -> Location {
Location {
file: PathBuf::from(""),
line: 0,
column: 0,
}
}
#[test]
fn test_collect_single_expression() {
let expr = Expression::Identifier {
name: "x".to_string(),
location: Some(test_loc()),
};
let parts = collect_concat_parts(&expr);
assert_eq!(parts.len(), 1);
}
#[test]
fn test_collect_nested_concatenation() {
let a = Expression::Literal {
value: Literal::String("a".to_string()),
location: Some(test_loc()),
};
let b = Expression::Literal {
value: Literal::String("b".to_string()),
location: Some(test_loc()),
};
let c = Expression::Literal {
value: Literal::String("c".to_string()),
location: Some(test_loc()),
};
let d = Expression::Literal {
value: Literal::String("d".to_string()),
location: Some(test_loc()),
};
let a_ref = test_alloc_expr(a);
let b_ref = test_alloc_expr(b);
let c_ref = test_alloc_expr(c);
let d_ref = test_alloc_expr(d);
let ab = test_alloc_expr(Expression::Binary {
left: a_ref,
op: BinaryOp::Add,
right: b_ref,
location: Some(test_loc()),
});
let cd = test_alloc_expr(Expression::Binary {
left: c_ref,
op: BinaryOp::Add,
right: d_ref,
location: Some(test_loc()),
});
let expr = Expression::Binary {
left: ab,
op: BinaryOp::Add,
right: cd,
location: Some(test_loc()),
};
let parts = collect_concat_parts(&expr);
assert_eq!(parts.len(), 4); }
#[test]
fn test_contains_string_in_nested_expression() {
let a = test_alloc_expr(Expression::Identifier {
name: "a".to_string(),
location: Some(test_loc()),
});
let b = test_alloc_expr(Expression::Identifier {
name: "b".to_string(),
location: Some(test_loc()),
});
let c = test_alloc_expr(Expression::Identifier {
name: "c".to_string(),
location: Some(test_loc()),
});
let hello = test_alloc_expr(Expression::Literal {
value: Literal::String("hello".to_string()),
location: Some(test_loc()),
});
let ab = test_alloc_expr(Expression::Binary {
left: a,
op: BinaryOp::Add,
right: b,
location: Some(test_loc()),
});
let ab_mul_c = test_alloc_expr(Expression::Binary {
left: ab,
op: BinaryOp::Mul,
right: c,
location: Some(test_loc()),
});
let expr = Expression::Binary {
left: ab_mul_c,
op: BinaryOp::Add,
right: hello,
location: Some(test_loc()),
};
assert!(contains_string_literal(&expr));
}
#[test]
fn test_no_string_in_complex_expression() {
let a = test_alloc_expr(Expression::Identifier {
name: "a".to_string(),
location: Some(test_loc()),
});
let b = test_alloc_expr(Expression::Identifier {
name: "b".to_string(),
location: Some(test_loc()),
});
let c = test_alloc_expr(Expression::Identifier {
name: "c".to_string(),
location: Some(test_loc()),
});
let d = test_alloc_expr(Expression::Identifier {
name: "d".to_string(),
location: Some(test_loc()),
});
let ab = test_alloc_expr(Expression::Binary {
left: a,
op: BinaryOp::Add,
right: b,
location: Some(test_loc()),
});
let cd = test_alloc_expr(Expression::Binary {
left: c,
op: BinaryOp::Sub,
right: d,
location: Some(test_loc()),
});
let expr = Expression::Binary {
left: ab,
op: BinaryOp::Mul,
right: cd,
location: Some(test_loc()),
};
assert!(!contains_string_literal(&expr));
}
}