src/tools/rust-analyzer/crates/ide/src/rename.rs RUST 4,175 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 4,175.
1//! Renaming functionality.2//!3//! This is mostly front-end for [`ide_db::rename`], but it also includes the4//! tests. This module also implements a couple of magic tricks, like renaming5//! `self` and to `self` (to switch between associated function and method).67use hir::{AsAssocItem, FindPathConfig, HasContainer, HirDisplay, InFile, Name, Semantics, sym};8use ide_db::{9    FileId, FileRange, RootDatabase,10    defs::{Definition, NameClass, NameRefClass},11    rename::{IdentifierKind, RenameDefinition, bail, format_err, source_edit_from_references},12    source_change::SourceChangeBuilder,13};14use itertools::Itertools;15use std::fmt::Write;16use stdx::{always, format_to, never};17use syntax::{18    AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,19    ast::{self, HasArgList, prec::ExprPrecedence},20};2122use ide_db::text_edit::TextEdit;2324use crate::{FilePosition, RangeInfo, SourceChange};2526pub use ide_db::rename::RenameError;2728type RenameResult<T> = Result<T, RenameError>;2930pub struct RenameConfig {31    pub prefer_no_std: bool,32    pub prefer_prelude: bool,33    pub prefer_absolute: bool,34    pub show_conflicts: bool,35}3637impl RenameConfig {38    fn find_path_config(&self) -> FindPathConfig {39        FindPathConfig {40            prefer_no_std: self.prefer_no_std,41            prefer_prelude: self.prefer_prelude,42            prefer_absolute: self.prefer_absolute,43            allow_unstable: true,44        }45    }4647    fn ide_db_config(&self) -> ide_db::rename::RenameConfig {48        ide_db::rename::RenameConfig { show_conflicts: self.show_conflicts }49    }50}5152/// This is similar to `collect::<Result<Vec<_>, _>>`, but unlike it, it succeeds if there is *any* `Ok` item.53fn ok_if_any<T, E>(iter: impl Iterator<Item = Result<T, E>>) -> Result<Vec<T>, E> {54    let mut err = None;55    let oks = iter56        .filter_map(|item| match item {57            Ok(it) => Some(it),58            Err(it) => {59                err = Some(it);60                None61            }62        })63        .collect::<Vec<_>>();64    if !oks.is_empty() {65        Ok(oks)66    } else if let Some(err) = err {67        Err(err)68    } else {69        Ok(Vec::new())70    }71}7273/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is74/// being targeted for a rename.75pub(crate) fn prepare_rename(76    db: &RootDatabase,77    position: FilePosition,78) -> RenameResult<RangeInfo<()>> {79    let sema = Semantics::new(db);80    let source_file = sema.parse_guess_edition(position.file_id);81    let syntax = source_file.syntax();82    if let Some(lifetime_token) = syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")83    {84        return Ok(RangeInfo::new(lifetime_token.text_range(), ()));85    }86    let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))?87        .filter(|(_, _, def, _, _)| def.range_for_rename(&sema).is_some())88        .map(|(frange, kind, _, _, _)| {89            always!(90                frange.range.contains_inclusive(position.offset)91                    && frange.file_id == position.file_id92            );9394            Ok(match kind {95                SyntaxKind::LIFETIME => {96                    TextRange::new(frange.range.start() + TextSize::from(1), frange.range.end())97                }98                _ => frange.range,99            })100        })101        .reduce(|acc, cur| match (acc, cur) {102            // ensure all ranges are the same103            (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),104            (e @ Err(_), _) | (_, e @ Err(_)) => e,105            _ => bail!("inconsistent text range"),106        });107108    match res {109        // ensure at least one definition was found110        Some(res) => res.map(|range| RangeInfo::new(range, ())),111        None => bail!("No references found at position"),112    }113}114115// Feature: Rename116//117// Renames the item below the cursor and all of its references118//119// | Editor  | Shortcut |120// |---------|----------|121// | VS Code | <kbd>F2</kbd> |122//123// ![Rename](https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif)124//125// #### Magic Renames126//127// rust-analyzer supports some special renames that do additional magic:128//129//  - **Anonymous lifetime renames**. You can rename `'_` to any lifetime name (the new name must start with `'`),130//    and rust-analyzer will automatically add the new lifetime to the list of generic parameters.131//  - **`self` renames**. You can rename parameters to/from `self`. Renaming `self` into another name will update132//    all callers using method syntax to call the function like an associated function. Renaming to `self` is only133//    supported for the first parameter inside an `impl` and when the `Self` type matches the type of the parameter,134//    and will update callers to use method call syntax.135pub(crate) fn rename(136    db: &RootDatabase,137    position: FilePosition,138    new_name: &str,139    config: &RenameConfig,140) -> RenameResult<SourceChange> {141    let sema = Semantics::new(db);142    let file_id = sema143        .attach_first_edition_opt(position.file_id)144        .ok_or_else(|| format_err!("No references found at position"))?;145    let source_file = sema.parse(file_id);146    let syntax = source_file.syntax();147148    let edition = file_id.edition(db);149    let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;150    if kind == IdentifierKind::Lifetime151        && let Some(lifetime_token) =152            syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")153    {154        let new_name_str = new_name.display(db, edition).to_string();155        return rename_elided_lifetime(position, lifetime_token, &new_name_str);156    }157158    let defs = find_definitions(&sema, syntax, position, &new_name)?;159    let alias_fallback =160        alias_fallback(syntax, position, &new_name.display(db, edition).to_string());161162    let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {163        Some(_) => ok_if_any(164            defs165                // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can166                // properly find "direct" usages/references.167                .map(|(.., def, new_name, _)| {168                    match kind {169                        IdentifierKind::Ident => (),170                        IdentifierKind::Lifetime => {171                            bail!("Cannot alias reference to a lifetime identifier")172                        }173                        IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),174                        IdentifierKind::LowercaseSelf => {175                            bail!("Cannot rename alias reference to `self`")176                        }177                    };178                    let mut usages = def.usages(&sema).all();179180                    // FIXME: hack - removes the usage that triggered this rename operation.181                    match usages.references.get_mut(&file_id).and_then(|refs| {182                        refs.iter()183                            .position(|ref_| ref_.range.contains_inclusive(position.offset))184                            .map(|idx| refs.remove(idx))185                    }) {186                        Some(_) => (),187                        None => never!(),188                    };189190                    let mut source_change = SourceChange::default();191                    source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {192                        (193                            position.file_id,194                            source_edit_from_references(db, refs, def, &new_name, edition),195                        )196                    }));197198                    Ok(source_change)199                }),200        ),201        None => ok_if_any(defs.map(|(.., def, new_name, rename_def)| {202            if let Definition::Local(local) = def {203                if let Some(self_param) = local.as_self_param(sema.db) {204                    cov_mark::hit!(rename_self_to_param);205                    return rename_self_to_param(206                        &sema,207                        local,208                        self_param,209                        &new_name,210                        kind,211                        config.find_path_config(),212                    );213                }214                if kind == IdentifierKind::LowercaseSelf {215                    cov_mark::hit!(rename_to_self);216                    return rename_to_self(&sema, local);217                }218            }219            def.rename(&sema, new_name.as_str(), rename_def, &config.ide_db_config())220        })),221    };222223    ops?.into_iter()224        .chain(alias_fallback)225        .reduce(|acc, elem| acc.merge(elem))226        .ok_or_else(|| format_err!("No references found at position"))227}228229/// Called by the client when it is about to rename a file.230pub(crate) fn will_rename_file(231    db: &RootDatabase,232    file_id: FileId,233    new_name_stem: &str,234    config: &RenameConfig,235) -> Option<SourceChange> {236    let sema = Semantics::new(db);237    let module = sema.file_to_module_def(file_id)?;238    let def = Definition::Module(module);239    let mut change =240        def.rename(&sema, new_name_stem, RenameDefinition::Yes, &config.ide_db_config()).ok()?;241    change.file_system_edits.clear();242    Some(change)243}244245// FIXME: Should support `extern crate`.246fn alias_fallback(247    syntax: &SyntaxNode,248    FilePosition { file_id, offset }: FilePosition,249    new_name: &str,250) -> Option<SourceChange> {251    let use_tree = syntax252        .token_at_offset(offset)253        .flat_map(|syntax| syntax.parent_ancestors())254        .find_map(ast::UseTree::cast)?;255256    let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;257    if !last_path_segment.syntax().text_range().contains_inclusive(offset) {258        return None;259    };260261    let mut builder = SourceChangeBuilder::new(file_id);262263    match use_tree.rename() {264        Some(rename) => {265            let offset = rename.syntax().text_range();266            builder.replace(offset, format!("as {new_name}"));267        }268        None => {269            let offset = use_tree.syntax().text_range().end();270            builder.insert(offset, format!(" as {new_name}"));271        }272    }273274    Some(builder.finish())275}276277fn find_definitions<'db>(278    sema: &Semantics<'db, RootDatabase>,279    syntax: &SyntaxNode,280    FilePosition { file_id, offset }: FilePosition,281    new_name: &Name,282) -> RenameResult<283    impl Iterator<Item = (FileRange, SyntaxKind, Definition<'db>, Name, RenameDefinition)>,284> {285    let maybe_format_args =286        syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));287288    if let Some((range, _, _, Some(resolution))) =289        maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset))290    {291        return Ok(vec![(292            FileRange { file_id, range },293            SyntaxKind::STRING,294            Definition::from(resolution),295            new_name.clone(),296            RenameDefinition::Yes,297        )]298        .into_iter());299    }300301    let original_ident = syntax302        .token_at_offset(offset)303        .max_by_key(|t| {304            t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT)305        })306        .map(|t| {307            if t.kind() == SyntaxKind::LIFETIME_IDENT {308                Name::new_lifetime(t.text())309            } else {310                Name::new_root(t.text())311            }312        })313        .ok_or_else(|| format_err!("No references found at position"))?;314    let symbols =315        sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| {316            let kind = name_like.syntax().kind();317            let range = sema318                .original_range_opt(name_like.syntax())319                .ok_or_else(|| format_err!("No references found at position"))?;320            let res = match &name_like {321                // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet322                ast::NameLike::Name(name)323                    if name324                        .syntax()325                        .parent().is_some_and(|it| ast::Rename::can_cast(it.kind()))326                        // FIXME: uncomment this once we resolve to usages to extern crate declarations327                        // && name328                        //     .syntax()329                        //     .ancestors()330                        //     .nth(2)331                        //     .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind()))332                        =>333                {334                    bail!("Renaming aliases is currently unsupported")335                }336                ast::NameLike::Name(name) => NameClass::classify(sema, name)337                    .map(|class| match class {338                        NameClass::Definition(it) | NameClass::ConstReference(it) => it,339                        NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {340                            Definition::Local(local_def)341                        }342                    })343                    .ok_or_else(|| format_err!("No references found at position")),344                ast::NameLike::NameRef(name_ref) => {345                    NameRefClass::classify(sema, name_ref)346                        .map(|class| match class {347                            NameRefClass::Definition(def, _) => def,348                            NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => {349                                Definition::Local(local_ref)350                            }351                            NameRefClass::ExternCrateShorthand { decl, .. } => {352                                Definition::ExternCrateDecl(decl)353                            }354                        })355                        // FIXME: uncomment this once we resolve to usages to extern crate declarations356                        .filter(|def| !matches!(def, Definition::ExternCrateDecl(..)))357                        .ok_or_else(|| format_err!("No references found at position"))358                        .and_then(|def| {359                            // if the name differs from the definitions name it has to be an alias360                            if def361                                .name(sema.db).is_some_and(|it| it.as_str() != name_ref.text().trim_start_matches("r#"))362                            {363                                Err(format_err!("Renaming aliases is currently unsupported"))364                            } else {365                                Ok(def)366                            }367                        })368                }369                ast::NameLike::Lifetime(lifetime) => {370                    NameRefClass::classify_lifetime(sema, lifetime)371                        .and_then(|class| match class {372                            NameRefClass::Definition(def, _) => Some(def),373                            _ => None,374                        })375                        .or_else(|| {376                            NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {377                                NameClass::Definition(it) => Some(it),378                                _ => None,379                            })380                        })381                        .ok_or_else(|| format_err!("No references found at position"))382                }383            };384            res.map(|def| {385                let n = def.name(sema.db)?;386                if n == original_ident {387                    Some((range, kind, def, new_name.clone(), RenameDefinition::Yes))388                } else if let Some(suffix) =  n.as_str().strip_prefix(original_ident.as_str()) {389                    Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No))390                } else {391                     n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\''))392                        .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No))393                }394            })395        });396397    let res: RenameResult<Vec<_>> = ok_if_any(symbols.filter_map(Result::transpose));398    match res {399        Ok(v) => {400            // remove duplicates, comparing `Definition`s401            Ok(v.into_iter()402                .unique_by(|&(.., def, _, _)| def)403                .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e))404                .collect::<Vec<_>>()405                .into_iter())406        }407        Err(e) => Err(e),408    }409}410411fn transform_assoc_fn_into_method_call(412    sema: &Semantics<'_, RootDatabase>,413    source_change: &mut SourceChange,414    f: hir::Function,415) {416    let calls = Definition::Function(f).usages(sema).all();417    for (_file_id, calls) in calls {418        for call in calls {419            let Some(fn_name) = call.name.as_name_ref() else { continue };420            let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {421                continue;422            };423            let path = path.parent_path();424            // The `PathExpr` is the direct parent, above it is the `CallExpr`.425            let Some(call) =426                path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))427            else {428                continue;429            };430431            let Some(arg_list) = call.arg_list() else { continue };432            let mut args = arg_list.args();433            let Some(mut self_arg) = args.next() else { continue };434            let second_arg = args.next();435436            // Strip (de)references, as they will be taken automatically by auto(de)ref.437            loop {438                let self_ = match &self_arg {439                    ast::Expr::RefExpr(self_) => self_.expr(),440                    ast::Expr::ParenExpr(self_) => self_.expr(),441                    ast::Expr::PrefixExpr(self_)442                        if self_.op_kind() == Some(ast::UnaryOp::Deref) =>443                    {444                        self_.expr()445                    }446                    _ => break,447                };448                self_arg = match self_ {449                    Some(it) => it,450                    None => break,451                };452            }453454            let self_needs_parens =455                self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);456457            let replace_start = path.syntax().text_range().start();458            let replace_end = match second_arg {459                Some(second_arg) => second_arg.syntax().text_range().start(),460                None => arg_list461                    .r_paren_token()462                    .map(|it| it.text_range().start())463                    .unwrap_or_else(|| arg_list.syntax().text_range().end()),464            };465            let replace_range = TextRange::new(replace_start, replace_end);466            let macro_file = sema.hir_file_for(fn_name.syntax());467            let Some((replace_range, _)) =468                InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)469            else {470                continue;471            };472473            let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {474                continue;475            };476            let mut replacement = String::new();477            if self_needs_parens {478                replacement.push('(');479            }480            replacement.push_str(macro_mapped_self.text(sema.db));481            if self_needs_parens {482                replacement.push(')');483            }484            replacement.push('.');485            format_to!(replacement, "{fn_name}");486            replacement.push('(');487488            source_change.insert_source_edit(489                replace_range.file_id.file_id(sema.db),490                TextEdit::replace(replace_range.range, replacement),491            );492        }493    }494}495496fn rename_to_self<'db>(497    sema: &Semantics<'db, RootDatabase>,498    local: hir::Local<'db>,499) -> RenameResult<SourceChange> {500    if never!(local.is_self(sema.db)) {501        bail!("rename_to_self invoked on self");502    }503504    let fn_def = match local.parent(sema.db) {505        hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,506        _ => bail!("Cannot rename local to self outside of function"),507    };508509    if fn_def.self_param(sema.db).is_some() {510        bail!("Method already has a self parameter");511    }512513    let params = fn_def.assoc_fn_params(sema.db);514    let first_param = params515        .first()516        .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;517    match first_param.as_local(sema.db) {518        Some(plocal) => {519            if plocal != local {520                bail!("Only the first parameter may be renamed to self");521            }522        }523        None => bail!("rename_to_self invoked on destructuring parameter"),524    }525526    let assoc_item = fn_def527        .as_assoc_item(sema.db)528        .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;529    let impl_ = match assoc_item.container(sema.db) {530        hir::AssocItemContainer::Trait(_) => {531            bail!("Cannot rename parameter to self for trait functions");532        }533        hir::AssocItemContainer::Impl(impl_) => impl_,534    };535    let first_param_ty = first_param.ty();536    let impl_ty = impl_.self_ty(sema.db);537    let (ty, self_param) = if impl_ty.is_reference() {538        // if the impl is a ref to the type we can just match the `&T` with self directly539        (first_param_ty.clone(), "self")540    } else {541        first_param_ty.as_reference_inner().map_or((first_param_ty.clone(), "self"), |ty| {542            (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })543        })544    };545546    if ty != impl_ty {547        bail!("Parameter type differs from impl block type");548    }549550    let InFile { file_id, value: param_source } = sema551        .source(first_param.clone())552        .ok_or_else(|| format_err!("No source for parameter found"))?;553554    let def = Definition::Local(local);555    let usages = def.usages(sema).all();556    let mut source_change = SourceChange::default();557    source_change.extend(usages.iter().map(|(file_id, references)| {558        (559            file_id.file_id(sema.db),560            source_edit_from_references(561                sema.db,562                references,563                def,564                &Name::new_symbol_root(sym::self_),565                file_id.edition(sema.db),566            ),567        )568    }));569    source_change.insert_source_edit(570        file_id.original_file(sema.db).file_id(sema.db),571        TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),572    );573    transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);574    Ok(source_change)575}576577#[derive(Debug, Clone, Copy, PartialEq, Eq)]578enum CallReceiverAdjust {579    Deref,580    Ref,581    RefMut,582    None,583}584585fn method_to_assoc_fn_call_self_adjust(586    sema: &Semantics<'_, RootDatabase>,587    self_arg: &ast::Expr,588) -> CallReceiverAdjust {589    let mut result = CallReceiverAdjust::None;590    let self_adjust = sema.expr_adjustments(self_arg);591    if let Some(self_adjust) = self_adjust {592        let mut i = 0;593        while i < self_adjust.len() {594            if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))595                && matches!(596                    self_adjust.get(i + 1),597                    Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. })598                )599            {600                // Deref then ref (reborrow), skip them.601                i += 2;602                continue;603            }604605            match self_adjust[i].kind {606                hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => {607                    // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref608                    // it automatically.609                    result = CallReceiverAdjust::Deref;610                }611                hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => {612                    match (result, mutability) {613                        (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {}614                        (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut,615                        (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref,616                    }617                }618                _ => {}619            }620621            i += 1;622        }623    }624    result625}626627fn transform_method_call_into_assoc_fn(628    sema: &Semantics<'_, RootDatabase>,629    source_change: &mut SourceChange,630    f: hir::Function,631    find_path_config: FindPathConfig,632) {633    let calls = Definition::Function(f).usages(sema).all();634    for (_file_id, calls) in calls {635        for call in calls {636            let Some(fn_name) = call.name.as_name_ref() else { continue };637            let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast)638            else {639                continue;640            };641            let Some(mut self_arg) = method_call.receiver() else {642                continue;643            };644645            let Some(scope) = sema.scope(fn_name.syntax()) else {646                continue;647            };648            let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg);649650            // Strip parentheses, function arguments have higher precedence than any operator.651            while let ast::Expr::ParenExpr(it) = &self_arg {652                self_arg = match it.expr() {653                    Some(it) => it,654                    None => break,655                };656            }657658            let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some());659660            let self_needs_parens = self_adjust != CallReceiverAdjust::None661                && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix);662663            let replace_start = method_call.syntax().text_range().start();664            let replace_end = method_call665                .arg_list()666                .and_then(|it| it.l_paren_token())667                .map(|it| it.text_range().end())668                .unwrap_or_else(|| method_call.syntax().text_range().end());669            let replace_range = TextRange::new(replace_start, replace_end);670            let macro_file = sema.hir_file_for(fn_name.syntax());671            let Some((replace_range, _)) =672                InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)673            else {674                continue;675            };676677            let fn_container_path = match f.container(sema.db) {678                hir::ItemContainer::Trait(trait_) => {679                    // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but680                    // that could conflict with an inherent method)? Or maybe `<Type as Trait>::function`?681                    // Or let the user decide?682                    let Some(path) = scope.module().find_path(683                        sema.db,684                        hir::ItemInNs::Types(trait_.into()),685                        find_path_config,686                    ) else {687                        continue;688                    };689                    path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string()690                }691                hir::ItemContainer::Impl(impl_) => {692                    let ty = impl_.self_ty(sema.db);693                    match ty.as_adt() {694                        Some(adt) => {695                            let Some(path) = scope.module().find_path(696                                sema.db,697                                hir::ItemInNs::Types(adt.into()),698                                find_path_config,699                            ) else {700                                continue;701                            };702                            path.display(sema.db, replace_range.file_id.edition(sema.db))703                                .to_string()704                        }705                        None => {706                            let Ok(mut ty) =707                                ty.display_source_code(sema.db, scope.module().into(), false)708                            else {709                                continue;710                            };711                            ty.insert(0, '<');712                            ty.push('>');713                            ty714                        }715                    }716                }717                _ => continue,718            };719720            let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {721                continue;722            };723            let mut replacement = String::new();724            replacement.push_str(&fn_container_path);725            replacement.push_str("::");726            format_to!(replacement, "{fn_name}");727            replacement.push('(');728            replacement.push_str(match self_adjust {729                CallReceiverAdjust::Deref => "*",730                CallReceiverAdjust::Ref => "&",731                CallReceiverAdjust::RefMut => "&mut ",732                CallReceiverAdjust::None => "",733            });734            if self_needs_parens {735                replacement.push('(');736            }737            replacement.push_str(macro_mapped_self.text(sema.db));738            if self_needs_parens {739                replacement.push(')');740            }741            if needs_comma {742                replacement.push_str(", ");743            }744745            source_change.insert_source_edit(746                replace_range.file_id.file_id(sema.db),747                TextEdit::replace(replace_range.range, replacement),748            );749        }750    }751}752753fn rename_self_to_param<'db>(754    sema: &Semantics<'db, RootDatabase>,755    local: hir::Local<'db>,756    self_param: hir::SelfParam,757    new_name: &Name,758    identifier_kind: IdentifierKind,759    find_path_config: FindPathConfig,760) -> RenameResult<SourceChange> {761    if identifier_kind == IdentifierKind::LowercaseSelf {762        // Let's do nothing rather than complain.763        cov_mark::hit!(rename_self_to_self);764        return Ok(SourceChange::default());765    }766767    let fn_def = match local.parent(sema.db) {768        hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,769        _ => bail!("Cannot rename local to self outside of function"),770    };771772    let InFile { file_id, value: self_param } =773        sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;774775    let def = Definition::Local(local);776    let usages = def.usages(sema).all();777    let edit = text_edit_from_self_param(778        &self_param,779        new_name.display(sema.db, file_id.edition(sema.db)).to_string(),780    )781    .ok_or_else(|| format_err!("No target type found"))?;782    if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {783        bail!("Cannot rename reference to `_` as it is being referenced multiple times");784    }785    let mut source_change = SourceChange::default();786    source_change.insert_source_edit(file_id.original_file(sema.db).file_id(sema.db), edit);787    source_change.extend(usages.iter().map(|(file_id, references)| {788        (789            file_id.file_id(sema.db),790            source_edit_from_references(791                sema.db,792                references,793                def,794                new_name,795                file_id.edition(sema.db),796            ),797        )798    }));799    transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);800    Ok(source_change)801}802803fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {804    let mut replacement_text = new_name;805    replacement_text.push_str(": ");806807    if self_param.amp_token().is_some() {808        replacement_text.push('&');809    }810    if let Some(lifetime) = self_param.lifetime() {811        write!(replacement_text, "{lifetime} ").unwrap();812    }813    if self_param.amp_token().and(self_param.mut_token()).is_some() {814        replacement_text.push_str("mut ");815    }816817    replacement_text.push_str("Self");818819    Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))820}821822fn rename_elided_lifetime(823    position: FilePosition,824    lifetime_token: syntax::SyntaxToken,825    new_name: &str,826) -> RenameResult<SourceChange> {827    let parent = lifetime_token.parent().unwrap();828    let root = parent.tree_top();829830    let mut builder = SourceChangeBuilder::new(position.file_id);831832    let editor = builder.make_editor(&root);833    let make = editor.make();834835    editor.replace(lifetime_token, make.lifetime(new_name).syntax().clone());836837    if let Some(has_generic_params) = parent.ancestors().find_map(ast::AnyHasGenericParams::cast) {838        let lifetime_param = make.lifetime_param(make.lifetime(new_name));839        editor.add_generic_param(&has_generic_params, lifetime_param.into());840    }841842    builder.add_file_edits(position.file_id, editor);843844    Ok(builder.finish())845}846847#[cfg(test)]848mod tests {849    use expect_test::{Expect, expect};850    use ide_db::source_change::SourceChange;851    use ide_db::text_edit::TextEdit;852    use itertools::Itertools;853    use stdx::trim_indent;854    use test_utils::assert_eq_text;855856    use crate::fixture;857858    use super::{RangeInfo, RenameConfig, RenameError};859860    const TEST_CONFIG: RenameConfig = RenameConfig {861        prefer_no_std: false,862        prefer_prelude: true,863        prefer_absolute: false,864        show_conflicts: true,865    };866867    #[track_caller]868    fn check(869        new_name: &str,870        #[rust_analyzer::rust_fixture] ra_fixture_before: &str,871        #[rust_analyzer::rust_fixture] ra_fixture_after: &str,872    ) {873        let ra_fixture_after = &trim_indent(ra_fixture_after);874        let (analysis, position) = fixture::position(ra_fixture_before);875        if !ra_fixture_after.starts_with("error: ")876            && let Err(err) = analysis.prepare_rename(position).unwrap()877        {878            panic!("Prepare rename to '{new_name}' was failed: {err}")879        }880        let rename_result = analysis881            .rename(position, new_name, &TEST_CONFIG)882            .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));883        match rename_result {884            Ok(source_change) => {885                let mut text_edit_builder = TextEdit::builder();886                let (&file_id, edit) = match source_change.source_file_edits.len() {887                    0 => return,888                    1 => source_change.source_file_edits.iter().next().unwrap(),889                    _ => panic!(),890                };891                for indel in edit.0.iter() {892                    text_edit_builder.replace(indel.delete, indel.insert.clone());893                }894                let mut result = analysis.file_text(file_id).unwrap().to_string();895                text_edit_builder.finish().apply(&mut result);896                assert_eq_text!(ra_fixture_after, &*result);897            }898            Err(err) => {899                if ra_fixture_after.starts_with("error:") {900                    let error_message =901                        ra_fixture_after.chars().skip("error:".len()).collect::<String>();902                    assert_eq!(error_message.trim(), err.to_string());903                } else {904                    panic!("Rename to '{new_name}' failed unexpectedly: {err}")905                }906            }907        };908    }909910    #[track_caller]911    fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) {912        let (analysis, position, conflicts) = fixture::annotations(ra_fixture);913        let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();914        let expected_conflicts = conflicts915            .into_iter()916            .map(|(file_range, _)| (file_range.file_id, file_range.range))917            .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))918            .collect_vec();919        let found_conflicts = source_change920            .source_file_edits921            .iter()922            .filter(|(_, (edit, _))| edit.change_annotation().is_some())923            .flat_map(|(file_id, (edit, _))| {924                edit.into_iter().map(move |edit| (*file_id, edit.delete))925            })926            .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))927            .collect_vec();928        assert_eq!(929            expected_conflicts, found_conflicts,930            "rename conflicts mismatch: {source_change:#?}"931        );932    }933934    fn check_expect(935        new_name: &str,936        #[rust_analyzer::rust_fixture] ra_fixture: &str,937        expect: Expect,938    ) {939        let (analysis, position) = fixture::position(ra_fixture);940        let source_change = analysis941            .rename(position, new_name, &TEST_CONFIG)942            .unwrap()943            .expect("Expect returned a RenameError");944        expect.assert_eq(&filter_expect(source_change))945    }946947    fn check_expect_will_rename_file(948        new_name: &str,949        #[rust_analyzer::rust_fixture] ra_fixture: &str,950        expect: Expect,951    ) {952        let (analysis, position) = fixture::position(ra_fixture);953        let source_change = analysis954            .will_rename_file(position.file_id, new_name, &TEST_CONFIG)955            .unwrap()956            .expect("Expect returned a RenameError");957        expect.assert_eq(&filter_expect(source_change))958    }959960    fn check_prepare(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {961        let (analysis, position) = fixture::position(ra_fixture);962        let result = analysis963            .prepare_rename(position)964            .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {err}"));965        match result {966            Ok(RangeInfo { range, info: () }) => {967                let source = analysis.file_text(position.file_id).unwrap();968                expect.assert_eq(&format!("{range:?}: {}", &source[range]))969            }970            Err(RenameError(err)) => expect.assert_eq(&err),971        };972    }973974    fn filter_expect(source_change: SourceChange) -> String {975        let source_file_edits = source_change976            .source_file_edits977            .into_iter()978            .map(|(id, (text_edit, _))| (id, text_edit.into_iter().collect::<Vec<_>>()))979            .collect::<Vec<_>>();980981        format!(982            "source_file_edits: {:#?}\nfile_system_edits: {:#?}\n",983            source_file_edits, source_change.file_system_edits984        )985    }986987    #[test]988    fn rename_will_shadow() {989        check_conflicts(990            "new_name",991            r#"992fn foo() {993    let mut new_name = 123;994    let old_name$0 = 456;995     // ^^^^^^^^996    new_name = 789 + new_name;997}998        "#,999        );1000    }10011002    #[test]1003    fn rename_will_be_shadowed() {1004        check_conflicts(1005            "new_name",1006            r#"1007fn foo() {1008    let mut old_name$0 = 456;1009         // ^^^^^^^^1010    let new_name = 123;1011    old_name = 789 + old_name;1012 // ^^^^^^^^         ^^^^^^^^1013}1014        "#,1015        );1016    }10171018    #[test]1019    fn test_prepare_rename_namelikes() {1020        check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);1021        check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]);1022        check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);1023    }10241025    #[test]1026    fn test_prepare_rename_in_macro() {1027        check_prepare(1028            r"macro_rules! foo {1029    ($ident:ident) => {1030        pub struct $ident;1031    }1032}1033foo!(Foo$0);",1034            expect![[r#"83..86: Foo"#]],1035        );1036    }10371038    #[test]1039    fn test_prepare_rename_keyword() {1040        check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);1041    }10421043    #[test]1044    fn test_prepare_rename_tuple_field() {1045        check_prepare(1046            r#"1047struct Foo(i32);10481049fn baz() {1050    let mut x = Foo(4);1051    x.0$0 = 5;1052}1053"#,1054            expect![[r#"No references found at position"#]],1055        );1056    }10571058    #[test]1059    fn test_prepare_rename_builtin() {1060        check_prepare(1061            r#"1062fn foo() {1063    let x: i32$0 = 0;1064}1065"#,1066            expect![[r#"No references found at position"#]],1067        );1068    }10691070    #[test]1071    fn test_prepare_rename_self() {1072        check_prepare(1073            r#"1074struct Foo {}10751076impl Foo {1077    fn foo(self) -> Self$0 {1078        self1079    }1080}1081"#,1082            expect![[r#"No references found at position"#]],1083        );1084    }10851086    #[test]1087    fn test_rename_to_underscore() {1088        check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);1089    }10901091    #[test]1092    fn test_rename_to_raw_identifier() {1093        check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);1094    }10951096    #[test]1097    fn test_rename_to_invalid_identifier1() {1098        check(1099            "invalid!",1100            r#"fn main() { let i$0 = 1; }"#,1101            "error: Invalid name `invalid!`: not an identifier",1102        );1103    }11041105    #[test]1106    fn test_rename_to_invalid_identifier2() {1107        check(1108            "multiple tokens",1109            r#"fn main() { let i$0 = 1; }"#,1110            "error: Invalid name `multiple tokens`: not an identifier",1111        );1112    }11131114    #[test]1115    fn test_rename_to_invalid_identifier3() {1116        check(1117            "super",1118            r#"fn main() { let i$0 = 1; }"#,1119            "error: Invalid name `super`: cannot rename to a keyword",1120        );1121    }11221123    #[test]1124    fn test_rename_to_invalid_identifier_lifetime() {1125        cov_mark::check!(rename_not_an_ident_ref);1126        check(1127            "'foo",1128            r#"fn main() { let i$0 = 1; }"#,1129            "error: Invalid name `'foo`: not an identifier",1130        );1131    }11321133    #[test]1134    fn test_rename_to_invalid_identifier_lifetime2() {1135        check(1136            "_",1137            r#"fn main<'a>(_: &'a$0 ()) {}"#,1138            r#"error: Invalid name `_`: not a lifetime identifier"#,1139        );1140    }11411142    #[test]1143    fn test_rename_accepts_lifetime_without_apostrophe() {1144        check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#);1145    }11461147    #[test]1148    fn test_rename_to_underscore_invalid() {1149        cov_mark::check!(rename_underscore_multiple);1150        check(1151            "_",1152            r#"fn main(foo$0: ()) {foo;}"#,1153            "error: Cannot rename reference to `_` as it is being referenced multiple times",1154        );1155    }11561157    #[test]1158    fn test_rename_mod_invalid() {1159        check(1160            "'foo",1161            r#"mod foo$0 {}"#,1162            "error: Invalid name `'foo`: cannot rename module to 'foo",1163        );1164    }11651166    #[test]1167    fn test_rename_mod_invalid_raw_ident() {1168        check(1169            "r#self",1170            r#"mod foo$0 {}"#,1171            "error: Invalid name `self`: cannot rename module to self",1172        );1173    }11741175    #[test]1176    fn test_rename_for_local() {1177        check(1178            "k",1179            r#"1180fn main() {1181    let mut i = 1;1182    let j = 1;1183    i = i$0 + j;11841185    { i = 0; }11861187    i = 5;1188}1189"#,1190            r#"1191fn main() {1192    let mut k = 1;1193    let j = 1;1194    k = k + j;11951196    { k = 0; }11971198    k = 5;1199}1200"#,1201        );1202    }12031204    #[test]1205    fn test_rename_unresolved_reference() {1206        check(1207            "new_name",1208            r#"fn main() { let _ = unresolved_ref$0; }"#,1209            "error: No references found at position",1210        );1211    }12121213    #[test]1214    fn test_rename_macro_multiple_occurrences() {1215        check(1216            "Baaah",1217            r#"macro_rules! foo {1218    ($ident:ident) => {1219        const $ident: () = ();1220        struct $ident {}1221    };1222}12231224foo!($0Foo);1225const _: () = Foo;1226const _: Foo = Foo {};1227    "#,1228            r#"1229macro_rules! foo {1230    ($ident:ident) => {1231        const $ident: () = ();1232        struct $ident {}1233    };1234}12351236foo!(Baaah);1237const _: () = Baaah;1238const _: Baaah = Baaah {};1239    "#,1240        )1241    }12421243    #[test]1244    fn test_rename_for_macro_args() {1245        check(1246            "b",1247            r#"1248macro_rules! foo {($i:ident) => {$i} }1249fn main() {1250    let a$0 = "test";1251    foo!(a);1252}1253"#,1254            r#"1255macro_rules! foo {($i:ident) => {$i} }1256fn main() {1257    let b = "test";1258    foo!(b);1259}1260"#,1261        );1262    }12631264    #[test]1265    fn test_rename_for_macro_args_rev() {1266        check(1267            "b",1268            r#"1269macro_rules! foo {($i:ident) => {$i} }1270fn main() {1271    let a = "test";1272    foo!(a$0);1273}1274"#,1275            r#"1276macro_rules! foo {($i:ident) => {$i} }1277fn main() {1278    let b = "test";1279    foo!(b);1280}1281"#,1282        );1283    }12841285    #[test]1286    fn test_rename_for_macro_define_fn() {1287        check(1288            "bar",1289            r#"1290macro_rules! define_fn {($id:ident) => { fn $id{} }}1291define_fn!(foo);1292fn main() {1293    fo$0o();1294}1295"#,1296            r#"1297macro_rules! define_fn {($id:ident) => { fn $id{} }}1298define_fn!(bar);1299fn main() {1300    bar();1301}1302"#,1303        );1304    }13051306    #[test]1307    fn test_rename_for_macro_define_fn_rev() {1308        check(1309            "bar",1310            r#"1311macro_rules! define_fn {($id:ident) => { fn $id{} }}1312define_fn!(fo$0o);1313fn main() {1314    foo();1315}1316"#,1317            r#"1318macro_rules! define_fn {($id:ident) => { fn $id{} }}1319define_fn!(bar);1320fn main() {1321    bar();1322}1323"#,1324        );1325    }13261327    #[test]1328    fn test_rename_for_param_inside() {1329        check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);1330    }13311332    #[test]1333    fn test_rename_refs_for_fn_param() {1334        check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);1335    }13361337    #[test]1338    fn test_rename_for_mut_param() {1339        check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);1340    }13411342    #[test]1343    fn test_rename_struct_field() {1344        check(1345            "foo",1346            r#"1347struct Foo { field$0: i32 }13481349impl Foo {1350    fn new(i: i32) -> Self {1351        Self { field: i }1352    }1353}1354"#,1355            r#"1356struct Foo { foo: i32 }13571358impl Foo {1359    fn new(i: i32) -> Self {1360        Self { foo: i }1361    }1362}1363"#,1364        );1365    }13661367    #[test]1368    fn test_rename_field_in_field_shorthand() {1369        cov_mark::check!(test_rename_field_in_field_shorthand);1370        check(1371            "field",1372            r#"1373struct Foo { foo$0: i32 }13741375impl Foo {1376    fn foo(foo: i32) {1377        Self { foo };1378    }1379}1380"#,1381            r#"1382struct Foo { field: i32 }13831384impl Foo {1385    fn foo(foo: i32) {1386        Self { field: foo };1387    }1388}1389"#,1390        );1391    }13921393    #[test]1394    fn test_rename_local_in_field_shorthand() {1395        cov_mark::check!(test_rename_local_in_field_shorthand);1396        check(1397            "j",1398            r#"1399struct Foo { i: i32 }14001401impl Foo {1402    fn new(i$0: i32) -> Self {1403        Self { i }1404    }1405}1406"#,1407            r#"1408struct Foo { i: i32 }14091410impl Foo {1411    fn new(j: i32) -> Self {1412        Self { i: j }1413    }1414}1415"#,1416        );1417    }14181419    #[test]1420    fn test_field_shorthand_correct_struct() {1421        check(1422            "j",1423            r#"1424struct Foo { i$0: i32 }1425struct Bar { i: i32 }14261427impl Bar {1428    fn new(i: i32) -> Self {1429        Self { i }1430    }1431}1432"#,1433            r#"1434struct Foo { j: i32 }1435struct Bar { i: i32 }14361437impl Bar {1438    fn new(i: i32) -> Self {1439        Self { i }1440    }1441}1442"#,1443        );1444    }14451446    #[test]1447    fn test_shadow_local_for_struct_shorthand() {1448        check(1449            "j",1450            r#"1451struct Foo { i: i32 }14521453fn baz(i$0: i32) -> Self {1454     let x = Foo { i };1455     {1456         let i = 0;1457         Foo { i }1458     }1459}1460"#,1461            r#"1462struct Foo { i: i32 }14631464fn baz(j: i32) -> Self {1465     let x = Foo { i: j };1466     {1467         let i = 0;1468         Foo { i }1469     }1470}1471"#,1472        );1473    }14741475    #[test]1476    fn test_rename_mod() {1477        check_expect(1478            "foo2",1479            r#"1480//- /lib.rs1481mod bar;14821483//- /bar.rs1484mod foo$0;14851486//- /bar/foo.rs1487// empty1488"#,1489            expect![[r#"1490                source_file_edits: [1491                    (1492                        FileId(1493                            1,1494                        ),1495                        [1496                            Indel {1497                                insert: "foo2",1498                                delete: 4..7,1499                            },1500                        ],1501                    ),1502                ]1503                file_system_edits: [1504                    MoveFile {1505                        src: FileId(1506                            2,1507                        ),1508                        dst: AnchoredPathBuf {1509                            anchor: FileId(1510                                2,1511                            ),1512                            path: "foo2.rs",1513                        },1514                    },1515                ]1516            "#]],1517        );1518    }15191520    #[test]1521    fn test_rename_mod_in_use_tree() {1522        check_expect(1523            "quux",1524            r#"1525//- /main.rs1526pub mod foo;1527pub mod bar;1528fn main() {}15291530//- /foo.rs1531pub struct FooContent;15321533//- /bar.rs1534use crate::foo$0::FooContent;1535"#,1536            expect![[r#"1537                source_file_edits: [1538                    (1539                        FileId(1540                            0,1541                        ),1542                        [1543                            Indel {1544                                insert: "quux",1545                                delete: 8..11,1546                            },1547                        ],1548                    ),1549                    (1550                        FileId(1551                            2,1552                        ),1553                        [1554                            Indel {1555                                insert: "quux",1556                                delete: 11..14,1557                            },1558                        ],1559                    ),1560                ]1561                file_system_edits: [1562                    MoveFile {1563                        src: FileId(1564                            1,1565                        ),1566                        dst: AnchoredPathBuf {1567                            anchor: FileId(1568                                1,1569                            ),1570                            path: "quux.rs",1571                        },1572                    },1573                ]1574            "#]],1575        );1576    }15771578    #[test]1579    fn test_rename_mod_in_dir() {1580        check_expect(1581            "foo2",1582            r#"1583//- /lib.rs1584mod fo$0o;1585//- /foo/mod.rs1586// empty1587"#,1588            expect![[r#"1589                source_file_edits: [1590                    (1591                        FileId(1592                            0,1593                        ),1594                        [1595                            Indel {1596                                insert: "foo2",1597                                delete: 4..7,1598                            },1599                        ],1600                    ),1601                ]1602                file_system_edits: [1603                    MoveDir {1604                        src: AnchoredPathBuf {1605                            anchor: FileId(1606                                1,1607                            ),1608                            path: "../foo",1609                        },1610                        src_id: FileId(1611                            1,1612                        ),1613                        dst: AnchoredPathBuf {1614                            anchor: FileId(1615                                1,1616                            ),1617                            path: "../foo2",1618                        },1619                    },1620                ]1621            "#]],1622        );1623    }16241625    #[test]1626    fn test_rename_unusually_nested_mod() {1627        check_expect(1628            "bar",1629            r#"1630//- /lib.rs1631mod outer { mod fo$0o; }16321633//- /outer/foo.rs1634// empty1635"#,1636            expect![[r#"1637                source_file_edits: [1638                    (1639                        FileId(1640                            0,1641                        ),1642                        [1643                            Indel {1644                                insert: "bar",1645                                delete: 16..19,1646                            },1647                        ],1648                    ),1649                ]1650                file_system_edits: [1651                    MoveFile {1652                        src: FileId(1653                            1,1654                        ),1655                        dst: AnchoredPathBuf {1656                            anchor: FileId(1657                                1,1658                            ),1659                            path: "bar.rs",1660                        },1661                    },1662                ]1663            "#]],1664        );1665    }16661667    #[test]1668    fn test_module_rename_in_path() {1669        check(1670            "baz",1671            r#"1672mod $0foo {1673    pub use self::bar as qux;1674    pub fn bar() {}1675}16761677fn main() { foo::bar(); }1678"#,1679            r#"1680mod baz {1681    pub use self::bar as qux;1682    pub fn bar() {}1683}16841685fn main() { baz::bar(); }1686"#,1687        );1688    }16891690    #[test]1691    fn test_rename_mod_filename_and_path() {1692        check_expect(1693            "foo2",1694            r#"1695//- /lib.rs1696mod bar;1697fn f() {1698    bar::foo::fun()1699}17001701//- /bar.rs1702pub mod foo$0;17031704//- /bar/foo.rs1705// pub fn fun() {}1706"#,1707            expect![[r#"1708                source_file_edits: [1709                    (1710                        FileId(1711                            0,1712                        ),1713                        [1714                            Indel {1715                                insert: "foo2",1716                                delete: 27..30,1717                            },1718                        ],1719                    ),1720                    (1721                        FileId(1722                            1,1723                        ),1724                        [1725                            Indel {1726                                insert: "foo2",1727                                delete: 8..11,1728                            },1729                        ],1730                    ),1731                ]1732                file_system_edits: [1733                    MoveFile {1734                        src: FileId(1735                            2,1736                        ),1737                        dst: AnchoredPathBuf {1738                            anchor: FileId(1739                                2,1740                            ),1741                            path: "foo2.rs",1742                        },1743                    },1744                ]1745            "#]],1746        );1747    }17481749    #[test]1750    fn test_rename_mod_recursive() {1751        check_expect(1752            "foo2",1753            r#"1754//- /lib.rs1755mod foo$0;17561757//- /foo.rs1758mod bar;1759mod corge;17601761//- /foo/bar.rs1762mod qux;17631764//- /foo/bar/qux.rs1765mod quux;17661767//- /foo/bar/qux/quux/mod.rs1768// empty17691770//- /foo/corge.rs1771// empty1772"#,1773            expect![[r#"1774                source_file_edits: [1775                    (1776                        FileId(1777                            0,1778                        ),1779                        [1780                            Indel {1781                                insert: "foo2",1782                                delete: 4..7,1783                            },1784                        ],1785                    ),1786                ]1787                file_system_edits: [1788                    MoveFile {1789                        src: FileId(1790                            1,1791                        ),1792                        dst: AnchoredPathBuf {1793                            anchor: FileId(1794                                1,1795                            ),1796                            path: "foo2.rs",1797                        },1798                    },1799                    MoveDir {1800                        src: AnchoredPathBuf {1801                            anchor: FileId(1802                                1,1803                            ),1804                            path: "foo",1805                        },1806                        src_id: FileId(1807                            1,1808                        ),1809                        dst: AnchoredPathBuf {1810                            anchor: FileId(1811                                1,1812                            ),1813                            path: "foo2",1814                        },1815                    },1816                ]1817            "#]],1818        )1819    }1820    #[test]1821    fn test_rename_mod_ref_by_super() {1822        check(1823            "baz",1824            r#"1825        mod $0foo {1826        struct X;18271828        mod bar {1829            use super::X;1830        }1831    }1832            "#,1833            r#"1834        mod baz {1835        struct X;18361837        mod bar {1838            use super::X;1839        }1840    }1841            "#,1842        )1843    }18441845    #[test]1846    fn test_rename_mod_in_macro() {1847        check(1848            "bar",1849            r#"1850//- /foo.rs18511852//- /lib.rs1853macro_rules! submodule {1854    ($name:ident) => {1855        mod $name;1856    };1857}18581859submodule!($0foo);1860"#,1861            r#"1862macro_rules! submodule {1863    ($name:ident) => {1864        mod $name;1865    };1866}18671868submodule!(bar);1869"#,1870        )1871    }18721873    #[test]1874    fn test_rename_mod_for_crate_root() {1875        check_expect_will_rename_file(1876            "main",1877            r#"1878//- /lib.rs1879use crate::foo as bar;1880fn foo() {}1881mod bar$0;1882"#,1883            expect![[r#"1884                source_file_edits: []1885                file_system_edits: []1886            "#]],1887        )1888    }18891890    #[test]1891    fn test_rename_mod_to_raw_ident() {1892        check_expect(1893            "r#fn",1894            r#"1895//- /lib.rs1896mod foo$0;18971898fn main() { foo::bar::baz(); }18991900//- /foo.rs1901pub mod bar;19021903//- /foo/bar.rs1904pub fn baz() {}1905"#,1906            expect![[r#"1907                source_file_edits: [1908                    (1909                        FileId(1910                            0,1911                        ),1912                        [1913                            Indel {1914                                insert: "r#fn",1915                                delete: 4..7,1916                            },1917                            Indel {1918                                insert: "r#fn",1919                                delete: 22..25,1920                            },1921                        ],1922                    ),1923                ]1924                file_system_edits: [1925                    MoveFile {1926                        src: FileId(1927                            1,1928                        ),1929                        dst: AnchoredPathBuf {1930                            anchor: FileId(1931                                1,1932                            ),1933                            path: "fn.rs",1934                        },1935                    },1936                    MoveDir {1937                        src: AnchoredPathBuf {1938                            anchor: FileId(1939                                1,1940                            ),1941                            path: "foo",1942                        },1943                        src_id: FileId(1944                            1,1945                        ),1946                        dst: AnchoredPathBuf {1947                            anchor: FileId(1948                                1,1949                            ),1950                            path: "fn",1951                        },1952                    },1953                ]1954            "#]],1955        );1956    }19571958    #[test]1959    fn test_rename_mod_from_raw_ident() {1960        check_expect(1961            "foo",1962            r#"1963//- /lib.rs1964mod r#fn$0;19651966fn main() { r#fn::bar::baz(); }19671968//- /fn.rs1969pub mod bar;19701971//- /fn/bar.rs1972pub fn baz() {}1973"#,1974            expect![[r#"1975                source_file_edits: [1976                    (1977                        FileId(1978                            0,1979                        ),1980                        [1981                            Indel {1982                                insert: "foo",1983                                delete: 4..8,1984                            },1985                            Indel {1986                                insert: "foo",1987                                delete: 23..27,1988                            },1989                        ],1990                    ),1991                ]1992                file_system_edits: [1993                    MoveFile {1994                        src: FileId(1995                            1,1996                        ),1997                        dst: AnchoredPathBuf {1998                            anchor: FileId(1999                                1,2000                            ),

Code quality findings 32

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
match self_adjust[i].kind {
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
write!(replacement_text, "{lifetime} ").unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let parent = lifetime_token.parent().unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
&& let Err(err) = analysis.prepare_rename(position).unwrap()
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
1 => source_change.source_file_edits.iter().next().unwrap(),
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let mut result = analysis.file_text(file_id).unwrap().to_string();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
.unwrap()
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("Expect returned a RenameError");
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
.unwrap()
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("Expect returned a RenameError");
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let source = analysis.file_text(position.file_id).unwrap();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
expect.assert_eq(&format!("{range:?}: {}", &source[range]))
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
Ok(match kind {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
.reduce(|acc, cur| match (acc, cur) {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match res {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
.and_then(|class| match class {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let self_ = match &self_arg {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
self_arg = match self_ {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let fn_def = match local.parent(sema.db) {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match (result, mutability) {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let fn_def = match local.parent(sema.db) {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match rename_result {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let (&file_id, edit) = match source_change.source_file_edits.len() {
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
text_edit_builder.replace(indel.delete, indel.insert.clone());
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
let mut result = analysis.file_text(file_id).unwrap().to_string();
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
unimplemented!()
Maintainability Info: `todo!()` or `unimplemented!()` macros indicate incomplete code paths that will panic at runtime if reached. Ensure these are replaced with actual logic before production use.
info correctness todo-unimplemented
unimplemented!()
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match test_variable {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match test_variable {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.

Generated by searchcode.com · powered by code_get_file + code_get_findings