use depyler_hir::hir::{AssignTarget, HirExpr, HirFunction, HirStmt, Literal, Type};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Default)]
pub struct StringOptimizer {
read_only_strings: HashSet<String>,
immutable_params: HashSet<String>,
returned_strings: HashSet<String>,
mixed_usage_strings: HashSet<String>,
string_literal_count: HashMap<String, usize>,
interned_strings: HashSet<String>,
interned_names: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum OptimalStringType {
StaticStr,
BorrowedStr { lifetime: Option<String> },
OwnedString,
CowStr,
}
impl StringOptimizer {
pub fn new() -> Self {
Self::default()
}
pub fn analyze_function(&mut self, func: &HirFunction) {
for param in &func.params {
if matches!(param.ty, Type::String) {
self.immutable_params.insert(param.name.clone());
}
}
for stmt in &func.body {
self.analyze_stmt(stmt);
}
for param in self.immutable_params.clone() {
if !self.is_immutable(¶m) {
self.immutable_params.remove(¶m);
}
}
}
pub fn get_optimal_type(&self, context: &StringContext) -> OptimalStringType {
match context {
StringContext::Literal(s) => {
if self.mixed_usage_strings.contains(s) {
OptimalStringType::CowStr
} else if self.returned_strings.contains(s) {
OptimalStringType::OwnedString
} else {
OptimalStringType::StaticStr
}
}
StringContext::Parameter(name) => {
if self.immutable_params.contains(name) {
OptimalStringType::BorrowedStr {
lifetime: Some("'a".to_string()),
}
} else if self.mixed_usage_strings.contains(name) {
OptimalStringType::CowStr
} else {
OptimalStringType::OwnedString
}
}
StringContext::Return => OptimalStringType::OwnedString,
StringContext::Concatenation => OptimalStringType::OwnedString,
}
}
fn analyze_stmt(&mut self, stmt: &HirStmt) {
match stmt {
HirStmt::Assign { target, value, .. } => {
self.analyze_assign_stmt(target, value);
}
HirStmt::Return(Some(expr)) => {
self.analyze_expr(expr, true);
}
HirStmt::If {
condition,
then_body,
else_body,
} => {
self.analyze_if_stmt(condition, then_body, else_body);
}
HirStmt::While { condition, body } => {
self.analyze_while_stmt(condition, body);
}
HirStmt::For { iter, body, .. } => {
self.analyze_for_stmt(iter, body);
}
HirStmt::Expr(expr) => {
self.analyze_expr(expr, false);
}
_ => {}
}
}
fn analyze_assign_stmt(&mut self, target: &AssignTarget, value: &HirExpr) {
if let AssignTarget::Symbol(symbol) = target {
if self.immutable_params.contains(symbol) {
self.immutable_params.remove(symbol);
}
}
self.analyze_expr(value, false);
}
fn analyze_if_stmt(
&mut self,
condition: &HirExpr,
then_body: &[HirStmt],
else_body: &Option<Vec<HirStmt>>,
) {
self.analyze_expr(condition, false);
for stmt in then_body {
self.analyze_stmt(stmt);
}
if let Some(else_stmts) = else_body {
for stmt in else_stmts {
self.analyze_stmt(stmt);
}
}
}
fn analyze_while_stmt(&mut self, condition: &HirExpr, body: &[HirStmt]) {
self.analyze_expr(condition, false);
for stmt in body {
self.analyze_stmt(stmt);
}
}
fn analyze_for_stmt(&mut self, iter: &HirExpr, body: &[HirStmt]) {
self.analyze_expr(iter, false);
for stmt in body {
self.analyze_stmt(stmt);
}
}
fn analyze_expr(&mut self, expr: &HirExpr, is_returned: bool) {
match expr {
HirExpr::Literal(Literal::String(s)) => {
self.analyze_string_literal(s, is_returned);
}
HirExpr::Var(name) => {
self.analyze_var_usage(name, is_returned);
}
HirExpr::Binary { op, left, right } => {
self.analyze_binary_expr(op, left, right);
}
HirExpr::Call { func, args, .. } => {
self.analyze_call_expr(func, args);
}
HirExpr::List(elts) | HirExpr::Tuple(elts) => {
self.analyze_collection_expr(elts, is_returned);
}
HirExpr::Dict(items) => {
self.analyze_dict_expr(items, is_returned);
}
_ => {}
}
}
fn analyze_string_literal(&mut self, s: &str, is_returned: bool) {
*self.string_literal_count.entry(s.to_string()).or_insert(0) += 1;
if self.string_literal_count.get(s).copied().unwrap_or(0) > 3 {
self.interned_strings.insert(s.to_string());
}
if is_returned {
self.returned_strings.insert(s.to_string());
} else {
self.read_only_strings.insert(s.to_string());
}
}
pub fn finalize_interned_names(&mut self) {
if !self.interned_names.is_empty() {
return;
}
let mut name_map: HashMap<String, Vec<String>> = HashMap::new();
for s in &self.interned_strings {
let base_name = self.generate_base_const_name(s);
name_map.entry(base_name).or_default().push(s.clone());
}
for (base_name, strings) in name_map {
if strings.len() == 1 {
self.interned_names.insert(strings[0].clone(), base_name);
} else {
for (idx, s) in strings.iter().enumerate() {
let unique_name = format!("{}_{}", base_name, idx + 1);
self.interned_names.insert(s.clone(), unique_name);
}
}
}
}
fn generate_base_const_name(&self, s: &str) -> String {
let name = s
.chars()
.map(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' => c.to_ascii_uppercase(),
_ => '_',
})
.collect::<String>();
let base_name = if name.is_empty() {
"EMPTY".to_string()
} else {
name
};
format!("STR_{}", base_name)
}
fn analyze_var_usage(&mut self, name: &str, is_returned: bool) {
if is_returned && self.immutable_params.contains(name) {
self.mixed_usage_strings.insert(name.to_string());
}
}
fn analyze_binary_expr(&mut self, op: &depyler_hir::hir::BinOp, left: &HirExpr, right: &HirExpr) {
if matches!(op, depyler_hir::hir::BinOp::Add)
&& (self.is_string_expr(left) || self.is_string_expr(right))
{
self.mark_as_owned(left);
self.mark_as_owned(right);
}
self.analyze_expr(left, false);
self.analyze_expr(right, false);
}
fn analyze_call_expr(&mut self, func: &str, args: &[HirExpr]) {
if self.is_mutating_method(func) && !args.is_empty() {
if let HirExpr::Var(name) = &args[0] {
self.immutable_params.remove(name);
}
}
for arg in args {
self.analyze_expr(arg, false);
}
}
fn analyze_collection_expr(&mut self, elts: &[HirExpr], is_returned: bool) {
for elt in elts {
self.analyze_expr(elt, is_returned);
}
}
fn analyze_dict_expr(&mut self, items: &[(HirExpr, HirExpr)], is_returned: bool) {
for (k, v) in items {
self.analyze_expr(k, false);
self.analyze_expr(v, is_returned);
}
}
#[allow(dead_code)]
fn is_read_only(&self, s: &str) -> bool {
self.read_only_strings.contains(s) && !self.returned_strings.contains(s)
}
fn is_immutable(&self, param: &str) -> bool {
self.immutable_params.contains(param)
}
fn is_string_expr(&self, expr: &HirExpr) -> bool {
match expr {
HirExpr::Literal(Literal::String(_)) => true,
HirExpr::Var(name) => self.immutable_params.contains(name),
HirExpr::Call { func, .. } => {
matches!(func.as_str(), "str" | "format" | "to_string" | "join")
}
_ => false,
}
}
fn mark_as_owned(&mut self, expr: &HirExpr) {
match expr {
HirExpr::Literal(Literal::String(s)) => {
self.read_only_strings.remove(s);
}
HirExpr::Var(name) => {
self.immutable_params.remove(name);
}
_ => {}
}
}
fn is_mutating_method(&self, method: &str) -> bool {
matches!(
method,
"push_str" | "push" | "insert" | "insert_str" | "replace_range" | "clear" | "truncate"
)
}
pub fn should_intern(&self, s: &str) -> bool {
self.interned_strings.contains(s)
}
pub fn get_interned_name(&self, s: &str) -> Option<String> {
self.interned_names.get(s).cloned()
}
pub fn generate_interned_constants(&self) -> Vec<String> {
let mut constants = Vec::new();
for (string_value, const_name) in &self.interned_names {
constants.push(format!(
"const {}: &'static str = \"{}\";",
const_name,
escape_string(string_value)
));
}
constants
}
}
#[derive(Debug, Clone)]
pub enum StringContext {
Literal(String),
Parameter(String),
Return,
Concatenation,
}
impl std::fmt::Display for StringContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StringContext::Literal(s) => write!(f, "\"{}\"", s),
StringContext::Parameter(name) => write!(f, "{}", name),
StringContext::Return => write!(f, "<return>"),
StringContext::Concatenation => write!(f, "<concat>"),
}
}
}
pub fn generate_optimized_string(optimizer: &StringOptimizer, context: &StringContext) -> String {
match optimizer.get_optimal_type(context) {
OptimalStringType::StaticStr => generate_static_str(context),
OptimalStringType::BorrowedStr { .. } => generate_borrowed_str(context),
OptimalStringType::OwnedString => generate_owned_string(context),
OptimalStringType::CowStr => generate_cow_str(context),
}
}
fn generate_static_str(context: &StringContext) -> String {
match context {
StringContext::Literal(s) => format!("\"{}\"", escape_string(s)),
_ => format!("{}.to_string()", context),
}
}
fn generate_borrowed_str(context: &StringContext) -> String {
match context {
StringContext::Parameter(name) => name.clone(),
StringContext::Literal(s) => format!("\"{}\"", escape_string(s)),
_ => format!("{}.as_str()", context),
}
}
fn generate_owned_string(context: &StringContext) -> String {
match context {
StringContext::Literal(s) => format!("\"{}\".to_string()", escape_string(s)),
StringContext::Parameter(name) => format!("{}.to_string()", name),
StringContext::Concatenation | StringContext::Return => "String::new()".to_string(),
}
}
fn generate_cow_str(context: &StringContext) -> String {
match context {
StringContext::Literal(s) => format!("Cow::Borrowed(\"{}\")", escape_string(s)),
StringContext::Parameter(name) => format!("Cow::Borrowed({})", name),
StringContext::Concatenation | StringContext::Return => {
"Cow::Owned(String::new())".to_string()
}
}
}
fn escape_string(s: &str) -> String {
s.chars().flat_map(escape_char).collect()
}
fn escape_char(c: char) -> Vec<char> {
match c {
'"' => vec!['\\', '"'],
'\\' => vec!['\\', '\\'],
'\n' => vec!['\\', 'n'],
'\r' => vec!['\\', 'r'],
'\t' => vec!['\\', 't'],
c => vec![c],
}
}
#[cfg(test)]
mod tests {
use super::*;
use depyler_hir::hir::*;
#[test]
fn test_read_only_string_optimization() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::Expr(HirExpr::Call {
func: "print".to_string(),
args: vec![HirExpr::Literal(Literal::String("hello".to_string()))],
kwargs: vec![],
})],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
let context = StringContext::Literal("hello".to_string());
assert_eq!(
optimizer.get_optimal_type(&context),
OptimalStringType::StaticStr
);
}
#[test]
fn test_returned_string_needs_ownership() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::String,
body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::String(
"result".to_string(),
))))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
let context = StringContext::Literal("result".to_string());
assert_eq!(
optimizer.get_optimal_type(&context),
OptimalStringType::OwnedString
);
}
#[test]
fn test_immutable_parameter_borrowing() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![HirParam::new("s".to_string(), Type::String)].into(),
ret_type: Type::Int,
body: vec![HirStmt::Return(Some(HirExpr::Call {
func: "len".to_string(),
args: vec![HirExpr::Var("s".to_string())],
kwargs: vec![],
}))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
let context = StringContext::Parameter("s".to_string());
assert_eq!(
optimizer.get_optimal_type(&context),
OptimalStringType::BorrowedStr {
lifetime: Some("'a".to_string())
}
);
}
#[test]
fn test_generate_optimized_string_code() {
let optimizer = StringOptimizer::new();
let code =
generate_optimized_string(&optimizer, &StringContext::Literal("hello".to_string()));
assert!(code == "\"hello\".to_string()" || code == "\"hello\"");
}
#[test]
fn test_new_creates_default() {
let optimizer = StringOptimizer::new();
assert!(!optimizer.should_intern("any"));
assert!(optimizer.get_interned_name("any").is_none());
}
#[test]
fn test_mixed_usage_strings_get_cow() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![HirParam::new("s".to_string(), Type::String)].into(),
ret_type: Type::String,
body: vec![
HirStmt::Expr(HirExpr::Var("s".to_string())),
HirStmt::Return(Some(HirExpr::Var("s".to_string()))),
],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
let context = StringContext::Parameter("s".to_string());
assert_eq!(
optimizer.get_optimal_type(&context),
OptimalStringType::BorrowedStr {
lifetime: Some("'a".to_string())
}
);
assert!(optimizer.mixed_usage_strings.contains("s"));
}
#[test]
fn test_mutated_parameter_loses_immutability() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![HirParam::new("s".to_string(), Type::String)].into(),
ret_type: Type::None,
body: vec![HirStmt::Assign {
target: AssignTarget::Symbol("s".to_string()),
value: HirExpr::Literal(Literal::String("new".to_string())),
type_annotation: None,
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
let context = StringContext::Parameter("s".to_string());
assert_eq!(
optimizer.get_optimal_type(&context),
OptimalStringType::OwnedString
);
}
#[test]
fn test_string_interning_threshold() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![
HirStmt::Expr(HirExpr::Literal(Literal::String("common".to_string()))),
HirStmt::Expr(HirExpr::Literal(Literal::String("common".to_string()))),
HirStmt::Expr(HirExpr::Literal(Literal::String("common".to_string()))),
HirStmt::Expr(HirExpr::Literal(Literal::String("common".to_string()))),
],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(optimizer.should_intern("common"));
}
#[test]
fn test_finalize_interned_names_no_collision() {
let mut optimizer = StringOptimizer::new();
optimizer.interned_strings.insert("hello".to_string());
optimizer.finalize_interned_names();
let name = optimizer.get_interned_name("hello").unwrap();
assert_eq!(name, "STR_HELLO");
}
#[test]
fn test_finalize_interned_names_with_collision() {
let mut optimizer = StringOptimizer::new();
optimizer.interned_strings.insert("hello!".to_string());
optimizer.interned_strings.insert("hello?".to_string());
optimizer.finalize_interned_names();
let name1 = optimizer.get_interned_name("hello!").unwrap();
let name2 = optimizer.get_interned_name("hello?").unwrap();
assert!(name1.starts_with("STR_HELLO_"));
assert!(name2.starts_with("STR_HELLO_"));
assert_ne!(name1, name2);
}
#[test]
fn test_finalize_interned_names_already_finalized() {
let mut optimizer = StringOptimizer::new();
optimizer.interned_strings.insert("test".to_string());
optimizer.finalize_interned_names();
optimizer.finalize_interned_names();
assert!(optimizer.get_interned_name("test").is_some());
}
#[test]
fn test_generate_base_const_name_empty() {
let optimizer = StringOptimizer::new();
let name = optimizer.generate_base_const_name("");
assert_eq!(name, "STR_EMPTY");
}
#[test]
fn test_generate_base_const_name_special_chars() {
let optimizer = StringOptimizer::new();
let name = optimizer.generate_base_const_name("hello world!");
assert_eq!(name, "STR_HELLO_WORLD_");
}
#[test]
fn test_generate_interned_constants() {
let mut optimizer = StringOptimizer::new();
optimizer.interned_strings.insert("test".to_string());
optimizer.finalize_interned_names();
let constants = optimizer.generate_interned_constants();
assert_eq!(constants.len(), 1);
assert!(constants[0].contains("STR_TEST"));
assert!(constants[0].contains("\"test\""));
}
#[test]
fn test_analyze_if_stmt_with_else() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::If {
condition: HirExpr::Literal(Literal::Bool(true)),
then_body: vec![HirStmt::Expr(HirExpr::Literal(Literal::String(
"then".to_string(),
)))],
else_body: Some(vec![HirStmt::Expr(HirExpr::Literal(Literal::String(
"else".to_string(),
)))]),
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(optimizer.is_read_only("then"));
assert!(optimizer.is_read_only("else"));
}
#[test]
fn test_analyze_while_stmt() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::While {
condition: HirExpr::Literal(Literal::Bool(true)),
body: vec![HirStmt::Expr(HirExpr::Literal(Literal::String(
"loop".to_string(),
)))],
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(optimizer.is_read_only("loop"));
}
#[test]
fn test_analyze_for_stmt() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::For {
target: AssignTarget::Symbol("i".to_string()),
iter: HirExpr::List(vec![]),
body: vec![HirStmt::Expr(HirExpr::Literal(Literal::String(
"body".to_string(),
)))],
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(optimizer.is_read_only("body"));
}
#[test]
fn test_analyze_string_concatenation() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::Expr(HirExpr::Binary {
op: BinOp::Add,
left: Box::new(HirExpr::Literal(Literal::String("a".to_string()))),
right: Box::new(HirExpr::Literal(Literal::String("b".to_string()))),
})],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(optimizer.read_only_strings.contains("a"));
assert!(optimizer.read_only_strings.contains("b"));
}
#[test]
fn test_analyze_mutating_call() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![HirParam::new("s".to_string(), Type::String)].into(),
ret_type: Type::None,
body: vec![HirStmt::Expr(HirExpr::Call {
func: "push_str".to_string(),
args: vec![HirExpr::Var("s".to_string())],
kwargs: vec![],
})],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(!optimizer.immutable_params.contains("s"));
}
#[test]
fn test_analyze_list_and_tuple() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![
HirStmt::Expr(HirExpr::List(vec![HirExpr::Literal(Literal::String(
"list".to_string(),
))])),
HirStmt::Expr(HirExpr::Tuple(vec![HirExpr::Literal(Literal::String(
"tuple".to_string(),
))])),
],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(optimizer.is_read_only("list"));
assert!(optimizer.is_read_only("tuple"));
}
#[test]
fn test_analyze_dict() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::Expr(HirExpr::Dict(vec![(
HirExpr::Literal(Literal::String("key".to_string())),
HirExpr::Literal(Literal::String("value".to_string())),
)]))],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
assert!(optimizer.is_read_only("key"));
assert!(optimizer.is_read_only("value"));
}
#[test]
fn test_is_string_expr_call() {
let optimizer = StringOptimizer::new();
assert!(optimizer.is_string_expr(&HirExpr::Call {
func: "str".to_string(),
args: vec![],
kwargs: vec![]
}));
assert!(optimizer.is_string_expr(&HirExpr::Call {
func: "format".to_string(),
args: vec![],
kwargs: vec![]
}));
assert!(optimizer.is_string_expr(&HirExpr::Call {
func: "to_string".to_string(),
args: vec![],
kwargs: vec![]
}));
assert!(optimizer.is_string_expr(&HirExpr::Call {
func: "join".to_string(),
args: vec![],
kwargs: vec![]
}));
assert!(!optimizer.is_string_expr(&HirExpr::Call {
func: "len".to_string(),
args: vec![],
kwargs: vec![]
}));
}
#[test]
fn test_is_mutating_method() {
let optimizer = StringOptimizer::new();
assert!(optimizer.is_mutating_method("push_str"));
assert!(optimizer.is_mutating_method("push"));
assert!(optimizer.is_mutating_method("insert"));
assert!(optimizer.is_mutating_method("insert_str"));
assert!(optimizer.is_mutating_method("replace_range"));
assert!(optimizer.is_mutating_method("clear"));
assert!(optimizer.is_mutating_method("truncate"));
assert!(!optimizer.is_mutating_method("len"));
}
#[test]
fn test_get_optimal_type_return_context() {
let optimizer = StringOptimizer::new();
assert_eq!(
optimizer.get_optimal_type(&StringContext::Return),
OptimalStringType::OwnedString
);
}
#[test]
fn test_get_optimal_type_concatenation_context() {
let optimizer = StringOptimizer::new();
assert_eq!(
optimizer.get_optimal_type(&StringContext::Concatenation),
OptimalStringType::OwnedString
);
}
#[test]
fn test_string_context_display() {
assert_eq!(
format!("{}", StringContext::Literal("hello".to_string())),
"\"hello\""
);
assert_eq!(
format!("{}", StringContext::Parameter("s".to_string())),
"s"
);
assert_eq!(format!("{}", StringContext::Return), "<return>");
assert_eq!(format!("{}", StringContext::Concatenation), "<concat>");
}
#[test]
fn test_generate_static_str() {
let s = generate_static_str(&StringContext::Literal("hello".to_string()));
assert_eq!(s, "\"hello\"");
let s = generate_static_str(&StringContext::Parameter("s".to_string()));
assert_eq!(s, "s.to_string()");
}
#[test]
fn test_generate_borrowed_str() {
let s = generate_borrowed_str(&StringContext::Parameter("s".to_string()));
assert_eq!(s, "s");
let s = generate_borrowed_str(&StringContext::Literal("test".to_string()));
assert_eq!(s, "\"test\"");
let s = generate_borrowed_str(&StringContext::Return);
assert_eq!(s, "<return>.as_str()");
}
#[test]
fn test_generate_owned_string() {
let s = generate_owned_string(&StringContext::Literal("hello".to_string()));
assert_eq!(s, "\"hello\".to_string()");
let s = generate_owned_string(&StringContext::Parameter("s".to_string()));
assert_eq!(s, "s.to_string()");
let s = generate_owned_string(&StringContext::Return);
assert_eq!(s, "String::new()");
let s = generate_owned_string(&StringContext::Concatenation);
assert_eq!(s, "String::new()");
}
#[test]
fn test_generate_cow_str() {
let s = generate_cow_str(&StringContext::Literal("hello".to_string()));
assert_eq!(s, "Cow::Borrowed(\"hello\")");
let s = generate_cow_str(&StringContext::Parameter("s".to_string()));
assert_eq!(s, "Cow::Borrowed(s)");
let s = generate_cow_str(&StringContext::Return);
assert_eq!(s, "Cow::Owned(String::new())");
let s = generate_cow_str(&StringContext::Concatenation);
assert_eq!(s, "Cow::Owned(String::new())");
}
#[test]
fn test_escape_string_special_chars() {
assert_eq!(escape_string("hello\"world"), "hello\\\"world");
assert_eq!(escape_string("hello\\world"), "hello\\\\world");
assert_eq!(escape_string("hello\nworld"), "hello\\nworld");
assert_eq!(escape_string("hello\rworld"), "hello\\rworld");
assert_eq!(escape_string("hello\tworld"), "hello\\tworld");
assert_eq!(escape_string("normal"), "normal");
}
#[test]
fn test_return_none_handled() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::Return(None)],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
}
#[test]
fn test_mark_as_owned_var() {
let mut optimizer = StringOptimizer::new();
optimizer.immutable_params.insert("s".to_string());
optimizer.mark_as_owned(&HirExpr::Var("s".to_string()));
assert!(!optimizer.immutable_params.contains("s"));
}
#[test]
fn test_mark_as_owned_other() {
let mut optimizer = StringOptimizer::new();
optimizer.mark_as_owned(&HirExpr::Literal(Literal::Int(42)));
}
#[test]
fn test_analyze_assign_non_symbol_target() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::Assign {
target: AssignTarget::Index {
base: Box::new(HirExpr::Var("arr".to_string())),
index: Box::new(HirExpr::Literal(Literal::Int(0))),
},
value: HirExpr::Literal(Literal::String("value".to_string())),
type_annotation: None,
}],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
}
#[test]
fn test_call_with_no_args() {
let mut optimizer = StringOptimizer::new();
let func = HirFunction {
name: "test".to_string(),
params: vec![].into(),
ret_type: Type::None,
body: vec![HirStmt::Expr(HirExpr::Call {
func: "push_str".to_string(),
args: vec![],
kwargs: vec![],
})],
properties: FunctionProperties::default(),
annotations: Default::default(),
docstring: None,
};
optimizer.analyze_function(&func);
}
#[test]
fn test_mixed_usage_literal() {
let mut optimizer = StringOptimizer::new();
optimizer.mixed_usage_strings.insert("mixed".to_string());
let context = StringContext::Literal("mixed".to_string());
assert_eq!(
optimizer.get_optimal_type(&context),
OptimalStringType::CowStr
);
}
#[test]
fn test_parameter_mixed_usage() {
let mut optimizer = StringOptimizer::new();
optimizer.mixed_usage_strings.insert("param".to_string());
let context = StringContext::Parameter("param".to_string());
assert_eq!(
optimizer.get_optimal_type(&context),
OptimalStringType::CowStr
);
}
#[test]
fn test_optimal_string_type_debug() {
let static_str = OptimalStringType::StaticStr;
assert!(format!("{:?}", static_str).contains("StaticStr"));
let borrowed = OptimalStringType::BorrowedStr {
lifetime: Some("'a".to_string()),
};
assert!(format!("{:?}", borrowed).contains("BorrowedStr"));
assert!(format!("{:?}", borrowed).contains("'a"));
let owned = OptimalStringType::OwnedString;
assert!(format!("{:?}", owned).contains("OwnedString"));
let cow = OptimalStringType::CowStr;
assert!(format!("{:?}", cow).contains("CowStr"));
}
#[test]
fn test_optimal_string_type_clone() {
let original = OptimalStringType::BorrowedStr {
lifetime: Some("'b".to_string()),
};
let cloned = original.clone();
assert_eq!(original, cloned);
}
#[test]
fn test_optimal_string_type_partial_eq() {
assert_eq!(OptimalStringType::StaticStr, OptimalStringType::StaticStr);
assert_eq!(
OptimalStringType::OwnedString,
OptimalStringType::OwnedString
);
assert_eq!(OptimalStringType::CowStr, OptimalStringType::CowStr);
assert_ne!(OptimalStringType::StaticStr, OptimalStringType::OwnedString);
assert_ne!(OptimalStringType::CowStr, OptimalStringType::StaticStr);
let borrowed1 = OptimalStringType::BorrowedStr {
lifetime: Some("'a".to_string()),
};
let borrowed2 = OptimalStringType::BorrowedStr {
lifetime: Some("'a".to_string()),
};
let borrowed3 = OptimalStringType::BorrowedStr {
lifetime: Some("'b".to_string()),
};
let borrowed_none = OptimalStringType::BorrowedStr { lifetime: None };
assert_eq!(borrowed1, borrowed2);
assert_ne!(borrowed1, borrowed3);
assert_ne!(borrowed1, borrowed_none);
}
#[test]
fn test_string_optimizer_debug() {
let optimizer = StringOptimizer::new();
let debug_str = format!("{:?}", optimizer);
assert!(debug_str.contains("StringOptimizer"));
}
#[test]
fn test_string_optimizer_default() {
let optimizer = StringOptimizer::default();
assert!(optimizer.read_only_strings.is_empty());
assert!(optimizer.immutable_params.is_empty());
assert!(optimizer.returned_strings.is_empty());
assert!(optimizer.mixed_usage_strings.is_empty());
assert!(optimizer.string_literal_count.is_empty());
assert!(optimizer.interned_strings.is_empty());
assert!(optimizer.interned_names.is_empty());
}
#[test]
fn test_escape_char_backslash() {
let result = escape_char('\\');
assert_eq!(result, vec!['\\', '\\']);
}
#[test]
fn test_escape_char_quote() {
let result = escape_char('"');
assert_eq!(result, vec!['\\', '"']);
}
#[test]
fn test_escape_char_newline() {
let result = escape_char('\n');
assert_eq!(result, vec!['\\', 'n']);
}
#[test]
fn test_escape_char_carriage_return() {
let result = escape_char('\r');
assert_eq!(result, vec!['\\', 'r']);
}
#[test]
fn test_escape_char_tab() {
let result = escape_char('\t');
assert_eq!(result, vec!['\\', 't']);
}
#[test]
fn test_escape_char_normal() {
let result = escape_char('a');
assert_eq!(result, vec!['a']);
let result = escape_char('Z');
assert_eq!(result, vec!['Z']);
let result = escape_char('5');
assert_eq!(result, vec!['5']);
}
#[test]
fn test_string_context_literal() {
let ctx = StringContext::Literal("hello".to_string());
if let StringContext::Literal(s) = ctx {
assert_eq!(s, "hello");
} else {
panic!("Expected Literal");
}
}
#[test]
fn test_string_context_parameter() {
let ctx = StringContext::Parameter("name".to_string());
if let StringContext::Parameter(s) = ctx {
assert_eq!(s, "name");
} else {
panic!("Expected Parameter");
}
}
#[test]
fn test_string_context_return() {
let ctx = StringContext::Return;
assert!(matches!(ctx, StringContext::Return));
}
#[test]
fn test_string_context_concatenation() {
let ctx = StringContext::Concatenation;
assert!(matches!(ctx, StringContext::Concatenation));
}
#[test]
fn test_is_read_only_true() {
let mut optimizer = StringOptimizer::new();
optimizer.read_only_strings.insert("readonly".to_string());
assert!(optimizer.is_read_only("readonly"));
}
#[test]
fn test_is_read_only_false() {
let optimizer = StringOptimizer::new();
assert!(!optimizer.is_read_only("nonexistent"));
}
#[test]
fn test_is_immutable_param_true() {
let mut optimizer = StringOptimizer::new();
optimizer.immutable_params.insert("param".to_string());
assert!(optimizer.is_immutable("param"));
}
#[test]
fn test_is_immutable_param_false() {
let optimizer = StringOptimizer::new();
assert!(!optimizer.is_immutable("nonexistent")); }
}