use super::ast::*;
#[derive(Debug, Clone)]
pub struct PureRenameResult {
pub count: usize,
pub old_name: String,
pub new_name: String,
}
pub struct PureRename;
impl PureRename {
pub fn apply(file: &mut PureFile, old_name: &str, new_name: &str) -> PureRenameResult {
let mut renamer = SymbolRenamer::new(old_name, new_name);
renamer.visit_file(file);
PureRenameResult {
count: renamer.count,
old_name: old_name.to_string(),
new_name: new_name.to_string(),
}
}
pub fn rename_local_in_fn(
file: &mut PureFile,
fn_name: &str,
old_name: &str,
new_name: &str,
) -> PureRenameResult {
let mut renamer = ScopedRenamer::new(fn_name, old_name, new_name);
renamer.visit_file(file);
PureRenameResult {
count: renamer.count,
old_name: old_name.to_string(),
new_name: new_name.to_string(),
}
}
pub fn apply_cow(
file: &PureFile,
old_name: &str,
new_name: &str,
) -> (PureFile, PureRenameResult) {
let mut cloned = file.clone();
let result = Self::apply(&mut cloned, old_name, new_name);
(cloned, result)
}
}
struct SymbolRenamer {
old_name: String,
new_name: String,
count: usize,
}
impl SymbolRenamer {
fn new(old_name: &str, new_name: &str) -> Self {
Self {
old_name: old_name.to_string(),
new_name: new_name.to_string(),
count: 0,
}
}
fn maybe_rename(&mut self, name: &mut String) -> bool {
if *name == self.old_name {
*name = self.new_name.clone();
self.count += 1;
true
} else {
false
}
}
fn visit_file(&mut self, file: &mut PureFile) {
for item in &mut file.items {
self.visit_item(item);
}
}
fn visit_item(&mut self, item: &mut PureItem) {
match item {
PureItem::Fn(f) => self.visit_fn(f),
PureItem::Struct(s) => self.visit_struct(s),
PureItem::Enum(e) => self.visit_enum(e),
PureItem::Impl(i) => self.visit_impl(i),
PureItem::Trait(t) => self.visit_trait(t),
PureItem::Const(c) => self.visit_const(c),
PureItem::Static(s) => self.visit_static(s),
PureItem::Type(t) => self.visit_type_alias(t),
PureItem::Mod(m) => self.visit_mod(m),
PureItem::Use(u) => self.visit_use(u),
PureItem::Macro(_) | PureItem::Other(_) => {}
}
}
fn visit_fn(&mut self, f: &mut PureFn) {
self.maybe_rename(&mut f.name);
for param in &mut f.params {
if let PureParam::Typed { name, ty } = param {
self.maybe_rename(name);
self.visit_type(ty);
}
}
if let Some(ty) = &mut f.ret {
self.visit_type(ty);
}
self.visit_block(&mut f.body);
}
fn visit_struct(&mut self, s: &mut PureStruct) {
self.maybe_rename(&mut s.name);
self.visit_fields(&mut s.fields);
}
fn visit_fields(&mut self, fields: &mut PureFields) {
match fields {
PureFields::Named(named) => {
for field in named {
self.visit_type(&mut field.ty);
}
}
PureFields::Tuple(types) => {
for ty in types {
self.visit_type(ty);
}
}
PureFields::Unit => {}
}
}
fn visit_enum(&mut self, e: &mut PureEnum) {
self.maybe_rename(&mut e.name);
for variant in &mut e.variants {
self.visit_fields(&mut variant.fields);
}
}
fn visit_impl(&mut self, i: &mut PureImpl) {
self.maybe_rename(&mut i.self_ty);
if let Some(trait_name) = &mut i.trait_ {
self.maybe_rename(trait_name);
}
for item in &mut i.items {
if let PureImplItem::Fn(f) = item {
self.visit_fn(f);
}
}
}
fn visit_trait(&mut self, t: &mut PureTrait) {
self.maybe_rename(&mut t.name);
}
fn visit_const(&mut self, c: &mut PureConst) {
self.maybe_rename(&mut c.name);
self.visit_type(&mut c.ty);
if let Some(v) = &mut c.value {
self.visit_expr(v);
}
}
fn visit_static(&mut self, s: &mut PureStatic) {
self.maybe_rename(&mut s.name);
self.visit_type(&mut s.ty);
self.visit_expr(&mut s.value);
}
fn visit_type_alias(&mut self, t: &mut PureTypeAlias) {
self.maybe_rename(&mut t.name);
self.visit_type(&mut t.ty);
}
fn visit_mod(&mut self, m: &mut PureMod) {
self.maybe_rename(&mut m.name);
for item in &mut m.items {
self.visit_item(item);
}
}
fn visit_use(&mut self, u: &mut PureUse) {
self.visit_use_tree(&mut u.tree);
}
fn visit_use_tree(&mut self, tree: &mut PureUseTree) {
match tree {
PureUseTree::Path { tree, .. } => {
self.visit_use_tree(tree);
}
PureUseTree::Name(name) => {
self.maybe_rename(name);
}
PureUseTree::Rename { name, .. } => {
self.maybe_rename(name);
}
PureUseTree::Glob => {}
PureUseTree::Group(trees) => {
for t in trees {
self.visit_use_tree(t);
}
}
}
}
fn visit_type(&mut self, ty: &mut PureType) {
match ty {
PureType::Path(path) => {
self.maybe_rename_qualified_path(path);
}
PureType::Ref { ty, .. } => {
self.visit_type(ty);
}
PureType::Slice(ty) => {
self.visit_type(ty);
}
PureType::Array { ty, .. } => {
self.visit_type(ty);
}
PureType::Tuple(types) => {
for ty in types {
self.visit_type(ty);
}
}
PureType::Fn { params, ret } => {
for ty in params {
self.visit_type(ty);
}
if let Some(ret_ty) = ret {
self.visit_type(ret_ty);
}
}
PureType::ImplTrait(bounds) => {
for bound in bounds {
self.maybe_rename_qualified_path(bound);
}
}
PureType::TraitObject(bounds) => {
for bound in bounds {
self.maybe_rename_qualified_path(bound);
}
}
PureType::Infer | PureType::Never | PureType::Other(_) => {}
}
}
fn visit_block(&mut self, block: &mut PureBlock) {
for stmt in &mut block.stmts {
self.visit_stmt(stmt);
}
}
fn visit_stmt(&mut self, stmt: &mut PureStmt) {
match stmt {
PureStmt::Local { pattern, init, ty } => {
self.visit_pattern(pattern);
if let Some(ty) = ty {
self.visit_type(ty);
}
if let Some(expr) = init {
self.visit_expr(expr);
}
}
PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
self.visit_expr(expr);
}
PureStmt::Item(item) => {
self.visit_item(item);
}
}
}
fn visit_pattern(&mut self, pattern: &mut PurePattern) {
match pattern {
PurePattern::Ident { name, .. } => {
self.maybe_rename(name);
}
PurePattern::Tuple(pats) => {
for pat in pats {
self.visit_pattern(pat);
}
}
PurePattern::Struct { path, fields, .. } => {
self.maybe_rename_qualified_path(path);
for (_, pat) in fields {
self.visit_pattern(pat);
}
}
PurePattern::Ref { pattern, .. } => {
self.visit_pattern(pattern);
}
PurePattern::Or(pats) => {
for pat in pats {
self.visit_pattern(pat);
}
}
PurePattern::Slice(pats) => {
for pat in pats {
self.visit_pattern(pat);
}
}
PurePattern::Path(path) => {
self.maybe_rename_qualified_path(path);
}
PurePattern::Wild
| PurePattern::Lit(_)
| PurePattern::Range { .. }
| PurePattern::Rest
| PurePattern::Other(_) => {}
}
}
fn maybe_rename_qualified_path(&mut self, path: &mut String) {
if path.contains("::") {
if let Some(first) = path.split("::").next() {
if first == self.old_name {
let rest = &path[first.len()..];
*path = format!("{}{}", self.new_name, rest);
self.count += 1;
return;
}
}
if let Some(last) = path.rsplit("::").next() {
if last == self.old_name {
if let Some(sep_idx) = path.rfind("::") {
let prefix = &path[..sep_idx + 2];
*path = format!("{}{}", prefix, self.new_name);
self.count += 1;
} else {
*path = self.new_name.to_string();
self.count += 1;
}
}
}
} else {
self.maybe_rename(path);
}
}
fn visit_expr(&mut self, expr: &mut PureExpr) {
match expr {
PureExpr::Path(path) => {
self.maybe_rename_qualified_path(path);
}
PureExpr::Binary { left, right, .. } => {
self.visit_expr(left);
self.visit_expr(right);
}
PureExpr::Unary { expr, .. } => {
self.visit_expr(expr);
}
PureExpr::Call { func, args } => {
self.visit_expr(func);
for arg in args {
self.visit_expr(arg);
}
}
PureExpr::MethodCall { receiver, args, .. } => {
self.visit_expr(receiver);
for arg in args {
self.visit_expr(arg);
}
}
PureExpr::Field { expr, .. } => {
self.visit_expr(expr);
}
PureExpr::Index { expr, index } => {
self.visit_expr(expr);
self.visit_expr(index);
}
PureExpr::Block { block, .. } => {
self.visit_block(block);
}
PureExpr::If {
cond,
then_branch,
else_branch,
} => {
self.visit_expr(cond);
self.visit_block(then_branch);
if let Some(else_expr) = else_branch {
self.visit_expr(else_expr);
}
}
PureExpr::Match { expr, arms } => {
self.visit_expr(expr);
for arm in arms {
self.visit_pattern(&mut arm.pattern);
if let Some(guard) = &mut arm.guard {
self.visit_expr(guard);
}
self.visit_expr(&mut arm.body);
}
}
PureExpr::Loop { body: block, .. } => {
self.visit_block(block);
}
PureExpr::While { cond, body, .. } => {
self.visit_expr(cond);
self.visit_block(body);
}
PureExpr::For {
pat, expr, body, ..
} => {
self.visit_expr(expr);
self.visit_pattern(pat);
self.visit_block(body);
}
PureExpr::Return(Some(expr))
| PureExpr::Break {
expr: Some(expr), ..
} => {
self.visit_expr(expr);
}
PureExpr::Closure { params, body, .. } => {
for param in params {
self.visit_pattern(&mut param.pattern);
}
self.visit_expr(body);
}
PureExpr::Struct { path, fields } => {
if !path.contains("::") {
self.maybe_rename(path);
}
for (_, expr) in fields {
self.visit_expr(expr);
}
}
PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
for expr in exprs {
self.visit_expr(expr);
}
}
PureExpr::Ref { expr, .. } => {
self.visit_expr(expr);
}
PureExpr::Await(expr) | PureExpr::Try(expr) => {
self.visit_expr(expr);
}
PureExpr::Macro { tokens, .. } => {
self.rename_in_tokens(tokens);
}
_ => {}
}
}
fn rename_in_tokens(&mut self, tokens: &mut String) {
let qualified_pattern = format!("{}::", self.old_name);
let qualified_replacement = format!("{}::", self.new_name);
if tokens.contains(&qualified_pattern) {
let count = tokens.matches(&qualified_pattern).count();
*tokens = tokens.replace(&qualified_pattern, &qualified_replacement);
self.count += count;
}
let mut new_tokens = String::with_capacity(tokens.len());
let mut last_end = 0;
let old_bytes = self.old_name.as_bytes();
let token_bytes = tokens.as_bytes();
let mut i = 0;
while i <= token_bytes.len().saturating_sub(old_bytes.len()) {
if &token_bytes[i..i + old_bytes.len()] == old_bytes {
let is_word_start = i == 0 || !is_ident_char(token_bytes[i - 1] as char);
let is_word_end = i + old_bytes.len() >= token_bytes.len()
|| !is_ident_char(token_bytes[i + old_bytes.len()] as char);
let is_qualified = i + old_bytes.len() < token_bytes.len()
&& token_bytes[i + old_bytes.len()] == b':';
if is_word_start && is_word_end && !is_qualified {
new_tokens.push_str(&tokens[last_end..i]);
new_tokens.push_str(&self.new_name);
last_end = i + old_bytes.len();
self.count += 1;
i += old_bytes.len();
continue;
}
}
i += 1;
}
new_tokens.push_str(&tokens[last_end..]);
*tokens = new_tokens;
}
}
fn is_ident_char(c: char) -> bool {
c.is_alphanumeric() || c == '_'
}
struct ScopedRenamer {
target_fn: String,
old_name: String,
new_name: String,
count: usize,
in_target_fn: bool,
}
impl ScopedRenamer {
fn new(target_fn: &str, old_name: &str, new_name: &str) -> Self {
Self {
target_fn: target_fn.to_string(),
old_name: old_name.to_string(),
new_name: new_name.to_string(),
count: 0,
in_target_fn: false,
}
}
fn maybe_rename(&mut self, name: &mut String) {
if self.in_target_fn && *name == self.old_name {
*name = self.new_name.clone();
self.count += 1;
}
}
fn visit_file(&mut self, file: &mut PureFile) {
for item in &mut file.items {
self.visit_item(item);
}
}
fn visit_item(&mut self, item: &mut PureItem) {
if let PureItem::Fn(f) = item {
let was_in_target = self.in_target_fn;
if f.name == self.target_fn {
self.in_target_fn = true;
}
self.visit_fn(f);
self.in_target_fn = was_in_target;
}
}
fn visit_fn(&mut self, f: &mut PureFn) {
for param in &mut f.params {
if let PureParam::Typed { name, .. } = param {
self.maybe_rename(name);
}
}
self.visit_block(&mut f.body);
}
fn visit_block(&mut self, block: &mut PureBlock) {
for stmt in &mut block.stmts {
self.visit_stmt(stmt);
}
}
fn visit_stmt(&mut self, stmt: &mut PureStmt) {
match stmt {
PureStmt::Local { pattern, init, .. } => {
self.visit_pattern(pattern);
if let Some(expr) = init {
self.visit_expr(expr);
}
}
PureStmt::Expr(expr) | PureStmt::Semi(expr) => {
self.visit_expr(expr);
}
PureStmt::Item(_) => {}
}
}
fn visit_pattern(&mut self, pattern: &mut PurePattern) {
match pattern {
PurePattern::Ident { name, .. } => {
self.maybe_rename(name);
}
PurePattern::Tuple(pats) => {
for pat in pats {
self.visit_pattern(pat);
}
}
PurePattern::Struct { fields, .. } => {
for (_, pat) in fields {
self.visit_pattern(pat);
}
}
PurePattern::Ref { pattern, .. } => {
self.visit_pattern(pattern);
}
PurePattern::Or(pats) => {
for pat in pats {
self.visit_pattern(pat);
}
}
PurePattern::Slice(pats) => {
for pat in pats {
self.visit_pattern(pat);
}
}
PurePattern::Path(path) => {
if let Some(last) = path.rsplit("::").next() {
if last == self.old_name {
if let Some(sep_idx) = path.rfind("::") {
let prefix = &path[..sep_idx + 2];
*path = format!("{}{}", prefix, self.new_name);
} else {
*path = self.new_name.to_string();
}
}
}
}
PurePattern::Wild
| PurePattern::Lit(_)
| PurePattern::Range { .. }
| PurePattern::Rest
| PurePattern::Other(_) => {}
}
}
fn visit_expr(&mut self, expr: &mut PureExpr) {
match expr {
PureExpr::Path(path) if !path.contains("::") => {
self.maybe_rename(path);
}
PureExpr::Binary { left, right, .. } => {
self.visit_expr(left);
self.visit_expr(right);
}
PureExpr::Unary { expr, .. } => {
self.visit_expr(expr);
}
PureExpr::Call { func, args } => {
self.visit_expr(func);
for arg in args {
self.visit_expr(arg);
}
}
PureExpr::MethodCall { receiver, args, .. } => {
self.visit_expr(receiver);
for arg in args {
self.visit_expr(arg);
}
}
PureExpr::Field { expr, .. } => {
self.visit_expr(expr);
}
PureExpr::Index { expr, index } => {
self.visit_expr(expr);
self.visit_expr(index);
}
PureExpr::Block { block, .. } => {
self.visit_block(block);
}
PureExpr::If {
cond,
then_branch,
else_branch,
} => {
self.visit_expr(cond);
self.visit_block(then_branch);
if let Some(else_expr) = else_branch {
self.visit_expr(else_expr);
}
}
PureExpr::Match { expr, arms } => {
self.visit_expr(expr);
for arm in arms {
self.visit_pattern(&mut arm.pattern);
if let Some(guard) = &mut arm.guard {
self.visit_expr(guard);
}
self.visit_expr(&mut arm.body);
}
}
PureExpr::Loop { body: block, .. } => {
self.visit_block(block);
}
PureExpr::While { cond, body, .. } => {
self.visit_expr(cond);
self.visit_block(body);
}
PureExpr::For {
pat, expr, body, ..
} => {
self.visit_expr(expr);
self.visit_pattern(pat);
self.visit_block(body);
}
PureExpr::Return(Some(expr))
| PureExpr::Break {
expr: Some(expr), ..
} => {
self.visit_expr(expr);
}
PureExpr::Closure { params, body, .. } => {
for param in params {
self.visit_pattern(&mut param.pattern);
}
self.visit_expr(body);
}
PureExpr::Struct { fields, .. } => {
for (_, expr) in fields {
self.visit_expr(expr);
}
}
PureExpr::Tuple(exprs) | PureExpr::Array(exprs) => {
for expr in exprs {
self.visit_expr(expr);
}
}
PureExpr::Ref { expr, .. } => {
self.visit_expr(expr);
}
PureExpr::Await(expr) | PureExpr::Try(expr) => {
self.visit_expr(expr);
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rename_local_var() {
let mut file = PureFile::from_source(
r#"
fn main() {
let x = 1;
let y = x + 1;
}
"#,
)
.unwrap();
let result = PureRename::apply(&mut file, "x", "value");
assert_eq!(result.count, 2);
let f = file.functions().into_iter().next().unwrap();
assert!(f.body.stmts.iter().any(|stmt| {
if let PureStmt::Local {
pattern: PurePattern::Ident { name, .. },
..
} = stmt
{
name == "value"
} else {
false
}
}));
}
#[test]
fn test_rename_function() {
let mut file = PureFile::from_source(
r#"
fn foo() {}
fn main() {
foo();
}
"#,
)
.unwrap();
let result = PureRename::apply(&mut file, "foo", "bar");
assert_eq!(result.count, 2);
let fns = file.functions();
assert!(fns.iter().any(|f| f.name == "bar"));
assert!(!fns.iter().any(|f| f.name == "foo"));
}
#[test]
fn test_rename_struct() {
let mut file = PureFile::from_source(
r#"
struct Point { x: i32, y: i32 }
fn main() {
let p: Point = Point { x: 0, y: 0 };
}
"#,
)
.unwrap();
let result = PureRename::apply(&mut file, "Point", "Vec2");
assert!(result.count >= 2);
let structs = file.structs();
assert!(structs.iter().any(|s| s.name == "Vec2"));
assert!(!structs.iter().any(|s| s.name == "Point"));
}
#[test]
fn test_scoped_rename() {
let mut file = PureFile::from_source(
r#"
fn foo() {
let x = 1;
}
fn bar() {
let x = 2;
}
"#,
)
.unwrap();
let result = PureRename::rename_local_in_fn(&mut file, "foo", "x", "renamed");
assert_eq!(result.count, 1);
let foo = file
.functions()
.into_iter()
.find(|f| f.name == "foo")
.unwrap();
assert!(foo.body.stmts.iter().any(|stmt| {
if let PureStmt::Local {
pattern: PurePattern::Ident { name, .. },
..
} = stmt
{
name == "renamed"
} else {
false
}
}));
let bar = file
.functions()
.into_iter()
.find(|f| f.name == "bar")
.unwrap();
assert!(bar.body.stmts.iter().any(|stmt| {
if let PureStmt::Local {
pattern: PurePattern::Ident { name, .. },
..
} = stmt
{
name == "x"
} else {
false
}
}));
}
#[test]
fn test_cow_rename() {
let original = PureFile::from_source("fn foo() {}").unwrap();
let (renamed, result) = PureRename::apply_cow(&original, "foo", "bar");
assert_eq!(result.count, 1);
assert!(original.functions().iter().any(|f| f.name == "foo"));
assert!(renamed.functions().iter().any(|f| f.name == "bar"));
}
#[test]
fn test_parallel_rename() {
use std::sync::Arc;
use std::thread;
let file = PureFile::from_source(
r#"
fn alpha() {}
fn beta() {}
fn gamma() {}
"#,
)
.unwrap();
let shared = Arc::new(file);
let handles: Vec<_> = vec!["alpha", "beta", "gamma"]
.into_iter()
.map(|old_name| {
let f = Arc::clone(&shared);
let new_name = format!("{}_renamed", old_name);
thread::spawn(move || {
let (renamed, result) = PureRename::apply_cow(&f, old_name, &new_name);
(renamed.functions().len(), result.count)
})
})
.collect();
for handle in handles {
let (fn_count, rename_count) = handle.join().unwrap();
assert_eq!(fn_count, 3);
assert_eq!(rename_count, 1);
}
}
}