src/tools/rust-analyzer/crates/ide/src/rename.rs RUST 4,163 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 4,163.
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)124pub(crate) fn rename(125    db: &RootDatabase,126    position: FilePosition,127    new_name: &str,128    config: &RenameConfig,129) -> RenameResult<SourceChange> {130    let sema = Semantics::new(db);131    let file_id = sema132        .attach_first_edition_opt(position.file_id)133        .ok_or_else(|| format_err!("No references found at position"))?;134    let source_file = sema.parse(file_id);135    let syntax = source_file.syntax();136137    let edition = file_id.edition(db);138    let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;139    if kind == IdentifierKind::Lifetime140        && let Some(lifetime_token) =141            syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")142    {143        let new_name_str = new_name.display(db, edition).to_string();144        return rename_elided_lifetime(position, lifetime_token, &new_name_str);145    }146147    let defs = find_definitions(&sema, syntax, position, &new_name)?;148    let alias_fallback =149        alias_fallback(syntax, position, &new_name.display(db, edition).to_string());150151    let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {152        Some(_) => ok_if_any(153            defs154                // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can155                // properly find "direct" usages/references.156                .map(|(.., def, new_name, _)| {157                    match kind {158                        IdentifierKind::Ident => (),159                        IdentifierKind::Lifetime => {160                            bail!("Cannot alias reference to a lifetime identifier")161                        }162                        IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),163                        IdentifierKind::LowercaseSelf => {164                            bail!("Cannot rename alias reference to `self`")165                        }166                    };167                    let mut usages = def.usages(&sema).all();168169                    // FIXME: hack - removes the usage that triggered this rename operation.170                    match usages.references.get_mut(&file_id).and_then(|refs| {171                        refs.iter()172                            .position(|ref_| ref_.range.contains_inclusive(position.offset))173                            .map(|idx| refs.remove(idx))174                    }) {175                        Some(_) => (),176                        None => never!(),177                    };178179                    let mut source_change = SourceChange::default();180                    source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {181                        (182                            position.file_id,183                            source_edit_from_references(db, refs, def, &new_name, edition),184                        )185                    }));186187                    Ok(source_change)188                }),189        ),190        None => ok_if_any(defs.map(|(.., def, new_name, rename_def)| {191            if let Definition::Local(local) = def {192                if let Some(self_param) = local.as_self_param(sema.db) {193                    cov_mark::hit!(rename_self_to_param);194                    return rename_self_to_param(195                        &sema,196                        local,197                        self_param,198                        &new_name,199                        kind,200                        config.find_path_config(),201                    );202                }203                if kind == IdentifierKind::LowercaseSelf {204                    cov_mark::hit!(rename_to_self);205                    return rename_to_self(&sema, local);206                }207            }208            def.rename(&sema, new_name.as_str(), rename_def, &config.ide_db_config())209        })),210    };211212    ops?.into_iter()213        .chain(alias_fallback)214        .reduce(|acc, elem| acc.merge(elem))215        .ok_or_else(|| format_err!("No references found at position"))216}217218/// Called by the client when it is about to rename a file.219pub(crate) fn will_rename_file(220    db: &RootDatabase,221    file_id: FileId,222    new_name_stem: &str,223    config: &RenameConfig,224) -> Option<SourceChange> {225    let sema = Semantics::new(db);226    let module = sema.file_to_module_def(file_id)?;227    let def = Definition::Module(module);228    let mut change =229        def.rename(&sema, new_name_stem, RenameDefinition::Yes, &config.ide_db_config()).ok()?;230    change.file_system_edits.clear();231    Some(change)232}233234// FIXME: Should support `extern crate`.235fn alias_fallback(236    syntax: &SyntaxNode,237    FilePosition { file_id, offset }: FilePosition,238    new_name: &str,239) -> Option<SourceChange> {240    let use_tree = syntax241        .token_at_offset(offset)242        .flat_map(|syntax| syntax.parent_ancestors())243        .find_map(ast::UseTree::cast)?;244245    let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;246    if !last_path_segment.syntax().text_range().contains_inclusive(offset) {247        return None;248    };249250    let mut builder = SourceChangeBuilder::new(file_id);251252    match use_tree.rename() {253        Some(rename) => {254            let offset = rename.syntax().text_range();255            builder.replace(offset, format!("as {new_name}"));256        }257        None => {258            let offset = use_tree.syntax().text_range().end();259            builder.insert(offset, format!(" as {new_name}"));260        }261    }262263    Some(builder.finish())264}265266fn find_definitions(267    sema: &Semantics<'_, RootDatabase>,268    syntax: &SyntaxNode,269    FilePosition { file_id, offset }: FilePosition,270    new_name: &Name,271) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition, Name, RenameDefinition)>>272{273    let maybe_format_args =274        syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));275276    if let Some((range, _, _, Some(resolution))) =277        maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset))278    {279        return Ok(vec![(280            FileRange { file_id, range },281            SyntaxKind::STRING,282            Definition::from(resolution),283            new_name.clone(),284            RenameDefinition::Yes,285        )]286        .into_iter());287    }288289    let original_ident = syntax290        .token_at_offset(offset)291        .max_by_key(|t| {292            t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT)293        })294        .map(|t| {295            if t.kind() == SyntaxKind::LIFETIME_IDENT {296                Name::new_lifetime(t.text())297            } else {298                Name::new_root(t.text())299            }300        })301        .ok_or_else(|| format_err!("No references found at position"))?;302    let symbols =303        sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| {304            let kind = name_like.syntax().kind();305            let range = sema306                .original_range_opt(name_like.syntax())307                .ok_or_else(|| format_err!("No references found at position"))?;308            let res = match &name_like {309                // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet310                ast::NameLike::Name(name)311                    if name312                        .syntax()313                        .parent().is_some_and(|it| ast::Rename::can_cast(it.kind()))314                        // FIXME: uncomment this once we resolve to usages to extern crate declarations315                        // && name316                        //     .syntax()317                        //     .ancestors()318                        //     .nth(2)319                        //     .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind()))320                        =>321                {322                    bail!("Renaming aliases is currently unsupported")323                }324                ast::NameLike::Name(name) => NameClass::classify(sema, name)325                    .map(|class| match class {326                        NameClass::Definition(it) | NameClass::ConstReference(it) => it,327                        NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {328                            Definition::Local(local_def)329                        }330                    })331                    .ok_or_else(|| format_err!("No references found at position")),332                ast::NameLike::NameRef(name_ref) => {333                    NameRefClass::classify(sema, name_ref)334                        .map(|class| match class {335                            NameRefClass::Definition(def, _) => def,336                            NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => {337                                Definition::Local(local_ref)338                            }339                            NameRefClass::ExternCrateShorthand { decl, .. } => {340                                Definition::ExternCrateDecl(decl)341                            }342                        })343                        // FIXME: uncomment this once we resolve to usages to extern crate declarations344                        .filter(|def| !matches!(def, Definition::ExternCrateDecl(..)))345                        .ok_or_else(|| format_err!("No references found at position"))346                        .and_then(|def| {347                            // if the name differs from the definitions name it has to be an alias348                            if def349                                .name(sema.db).is_some_and(|it| it.as_str() != name_ref.text().trim_start_matches("r#"))350                            {351                                Err(format_err!("Renaming aliases is currently unsupported"))352                            } else {353                                Ok(def)354                            }355                        })356                }357                ast::NameLike::Lifetime(lifetime) => {358                    NameRefClass::classify_lifetime(sema, lifetime)359                        .and_then(|class| match class {360                            NameRefClass::Definition(def, _) => Some(def),361                            _ => None,362                        })363                        .or_else(|| {364                            NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {365                                NameClass::Definition(it) => Some(it),366                                _ => None,367                            })368                        })369                        .ok_or_else(|| format_err!("No references found at position"))370                }371            };372            res.map(|def| {373                let n = def.name(sema.db)?;374                if n == original_ident {375                    Some((range, kind, def, new_name.clone(), RenameDefinition::Yes))376                } else if let Some(suffix) =  n.as_str().strip_prefix(original_ident.as_str()) {377                    Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No))378                } else {379                     n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\''))380                        .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No))381                }382            })383        });384385    let res: RenameResult<Vec<_>> = ok_if_any(symbols.filter_map(Result::transpose));386    match res {387        Ok(v) => {388            // remove duplicates, comparing `Definition`s389            Ok(v.into_iter()390                .unique_by(|&(.., def, _, _)| def)391                .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e))392                .collect::<Vec<_>>()393                .into_iter())394        }395        Err(e) => Err(e),396    }397}398399fn transform_assoc_fn_into_method_call(400    sema: &Semantics<'_, RootDatabase>,401    source_change: &mut SourceChange,402    f: hir::Function,403) {404    let calls = Definition::Function(f).usages(sema).all();405    for (_file_id, calls) in calls {406        for call in calls {407            let Some(fn_name) = call.name.as_name_ref() else { continue };408            let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {409                continue;410            };411            let path = path.parent_path();412            // The `PathExpr` is the direct parent, above it is the `CallExpr`.413            let Some(call) =414                path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))415            else {416                continue;417            };418419            let Some(arg_list) = call.arg_list() else { continue };420            let mut args = arg_list.args();421            let Some(mut self_arg) = args.next() else { continue };422            let second_arg = args.next();423424            // Strip (de)references, as they will be taken automatically by auto(de)ref.425            loop {426                let self_ = match &self_arg {427                    ast::Expr::RefExpr(self_) => self_.expr(),428                    ast::Expr::ParenExpr(self_) => self_.expr(),429                    ast::Expr::PrefixExpr(self_)430                        if self_.op_kind() == Some(ast::UnaryOp::Deref) =>431                    {432                        self_.expr()433                    }434                    _ => break,435                };436                self_arg = match self_ {437                    Some(it) => it,438                    None => break,439                };440            }441442            let self_needs_parens =443                self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);444445            let replace_start = path.syntax().text_range().start();446            let replace_end = match second_arg {447                Some(second_arg) => second_arg.syntax().text_range().start(),448                None => arg_list449                    .r_paren_token()450                    .map(|it| it.text_range().start())451                    .unwrap_or_else(|| arg_list.syntax().text_range().end()),452            };453            let replace_range = TextRange::new(replace_start, replace_end);454            let macro_file = sema.hir_file_for(fn_name.syntax());455            let Some((replace_range, _)) =456                InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)457            else {458                continue;459            };460461            let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {462                continue;463            };464            let mut replacement = String::new();465            if self_needs_parens {466                replacement.push('(');467            }468            replacement.push_str(macro_mapped_self.text(sema.db));469            if self_needs_parens {470                replacement.push(')');471            }472            replacement.push('.');473            format_to!(replacement, "{fn_name}");474            replacement.push('(');475476            source_change.insert_source_edit(477                replace_range.file_id.file_id(sema.db),478                TextEdit::replace(replace_range.range, replacement),479            );480        }481    }482}483484fn rename_to_self(485    sema: &Semantics<'_, RootDatabase>,486    local: hir::Local,487) -> RenameResult<SourceChange> {488    if never!(local.is_self(sema.db)) {489        bail!("rename_to_self invoked on self");490    }491492    let fn_def = match local.parent(sema.db) {493        hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,494        _ => bail!("Cannot rename local to self outside of function"),495    };496497    if fn_def.self_param(sema.db).is_some() {498        bail!("Method already has a self parameter");499    }500501    let params = fn_def.assoc_fn_params(sema.db);502    let first_param = params503        .first()504        .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;505    match first_param.as_local(sema.db) {506        Some(plocal) => {507            if plocal != local {508                bail!("Only the first parameter may be renamed to self");509            }510        }511        None => bail!("rename_to_self invoked on destructuring parameter"),512    }513514    let assoc_item = fn_def515        .as_assoc_item(sema.db)516        .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;517    let impl_ = match assoc_item.container(sema.db) {518        hir::AssocItemContainer::Trait(_) => {519            bail!("Cannot rename parameter to self for trait functions");520        }521        hir::AssocItemContainer::Impl(impl_) => impl_,522    };523    let first_param_ty = first_param.ty();524    let impl_ty = impl_.self_ty(sema.db);525    let (ty, self_param) = if impl_ty.is_reference() {526        // if the impl is a ref to the type we can just match the `&T` with self directly527        (first_param_ty.clone(), "self")528    } else {529        first_param_ty.as_reference_inner().map_or((first_param_ty.clone(), "self"), |ty| {530            (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })531        })532    };533534    if ty != impl_ty {535        bail!("Parameter type differs from impl block type");536    }537538    let InFile { file_id, value: param_source } = sema539        .source(first_param.clone())540        .ok_or_else(|| format_err!("No source for parameter found"))?;541542    let def = Definition::Local(local);543    let usages = def.usages(sema).all();544    let mut source_change = SourceChange::default();545    source_change.extend(usages.iter().map(|(file_id, references)| {546        (547            file_id.file_id(sema.db),548            source_edit_from_references(549                sema.db,550                references,551                def,552                &Name::new_symbol_root(sym::self_),553                file_id.edition(sema.db),554            ),555        )556    }));557    source_change.insert_source_edit(558        file_id.original_file(sema.db).file_id(sema.db),559        TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),560    );561    transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);562    Ok(source_change)563}564565#[derive(Debug, Clone, Copy, PartialEq, Eq)]566enum CallReceiverAdjust {567    Deref,568    Ref,569    RefMut,570    None,571}572573fn method_to_assoc_fn_call_self_adjust(574    sema: &Semantics<'_, RootDatabase>,575    self_arg: &ast::Expr,576) -> CallReceiverAdjust {577    let mut result = CallReceiverAdjust::None;578    let self_adjust = sema.expr_adjustments(self_arg);579    if let Some(self_adjust) = self_adjust {580        let mut i = 0;581        while i < self_adjust.len() {582            if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))583                && matches!(584                    self_adjust.get(i + 1),585                    Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. })586                )587            {588                // Deref then ref (reborrow), skip them.589                i += 2;590                continue;591            }592593            match self_adjust[i].kind {594                hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => {595                    // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref596                    // it automatically.597                    result = CallReceiverAdjust::Deref;598                }599                hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => {600                    match (result, mutability) {601                        (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {}602                        (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut,603                        (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref,604                    }605                }606                _ => {}607            }608609            i += 1;610        }611    }612    result613}614615fn transform_method_call_into_assoc_fn(616    sema: &Semantics<'_, RootDatabase>,617    source_change: &mut SourceChange,618    f: hir::Function,619    find_path_config: FindPathConfig,620) {621    let calls = Definition::Function(f).usages(sema).all();622    for (_file_id, calls) in calls {623        for call in calls {624            let Some(fn_name) = call.name.as_name_ref() else { continue };625            let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast)626            else {627                continue;628            };629            let Some(mut self_arg) = method_call.receiver() else {630                continue;631            };632633            let Some(scope) = sema.scope(fn_name.syntax()) else {634                continue;635            };636            let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg);637638            // Strip parentheses, function arguments have higher precedence than any operator.639            while let ast::Expr::ParenExpr(it) = &self_arg {640                self_arg = match it.expr() {641                    Some(it) => it,642                    None => break,643                };644            }645646            let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some());647648            let self_needs_parens = self_adjust != CallReceiverAdjust::None649                && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix);650651            let replace_start = method_call.syntax().text_range().start();652            let replace_end = method_call653                .arg_list()654                .and_then(|it| it.l_paren_token())655                .map(|it| it.text_range().end())656                .unwrap_or_else(|| method_call.syntax().text_range().end());657            let replace_range = TextRange::new(replace_start, replace_end);658            let macro_file = sema.hir_file_for(fn_name.syntax());659            let Some((replace_range, _)) =660                InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)661            else {662                continue;663            };664665            let fn_container_path = match f.container(sema.db) {666                hir::ItemContainer::Trait(trait_) => {667                    // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but668                    // that could conflict with an inherent method)? Or maybe `<Type as Trait>::function`?669                    // Or let the user decide?670                    let Some(path) = scope.module().find_path(671                        sema.db,672                        hir::ItemInNs::Types(trait_.into()),673                        find_path_config,674                    ) else {675                        continue;676                    };677                    path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string()678                }679                hir::ItemContainer::Impl(impl_) => {680                    let ty = impl_.self_ty(sema.db);681                    match ty.as_adt() {682                        Some(adt) => {683                            let Some(path) = scope.module().find_path(684                                sema.db,685                                hir::ItemInNs::Types(adt.into()),686                                find_path_config,687                            ) else {688                                continue;689                            };690                            path.display(sema.db, replace_range.file_id.edition(sema.db))691                                .to_string()692                        }693                        None => {694                            let Ok(mut ty) =695                                ty.display_source_code(sema.db, scope.module().into(), false)696                            else {697                                continue;698                            };699                            ty.insert(0, '<');700                            ty.push('>');701                            ty702                        }703                    }704                }705                _ => continue,706            };707708            let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {709                continue;710            };711            let mut replacement = String::new();712            replacement.push_str(&fn_container_path);713            replacement.push_str("::");714            format_to!(replacement, "{fn_name}");715            replacement.push('(');716            replacement.push_str(match self_adjust {717                CallReceiverAdjust::Deref => "*",718                CallReceiverAdjust::Ref => "&",719                CallReceiverAdjust::RefMut => "&mut ",720                CallReceiverAdjust::None => "",721            });722            if self_needs_parens {723                replacement.push('(');724            }725            replacement.push_str(macro_mapped_self.text(sema.db));726            if self_needs_parens {727                replacement.push(')');728            }729            if needs_comma {730                replacement.push_str(", ");731            }732733            source_change.insert_source_edit(734                replace_range.file_id.file_id(sema.db),735                TextEdit::replace(replace_range.range, replacement),736            );737        }738    }739}740741fn rename_self_to_param(742    sema: &Semantics<'_, RootDatabase>,743    local: hir::Local,744    self_param: hir::SelfParam,745    new_name: &Name,746    identifier_kind: IdentifierKind,747    find_path_config: FindPathConfig,748) -> RenameResult<SourceChange> {749    if identifier_kind == IdentifierKind::LowercaseSelf {750        // Let's do nothing rather than complain.751        cov_mark::hit!(rename_self_to_self);752        return Ok(SourceChange::default());753    }754755    let fn_def = match local.parent(sema.db) {756        hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,757        _ => bail!("Cannot rename local to self outside of function"),758    };759760    let InFile { file_id, value: self_param } =761        sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;762763    let def = Definition::Local(local);764    let usages = def.usages(sema).all();765    let edit = text_edit_from_self_param(766        &self_param,767        new_name.display(sema.db, file_id.edition(sema.db)).to_string(),768    )769    .ok_or_else(|| format_err!("No target type found"))?;770    if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {771        bail!("Cannot rename reference to `_` as it is being referenced multiple times");772    }773    let mut source_change = SourceChange::default();774    source_change.insert_source_edit(file_id.original_file(sema.db).file_id(sema.db), edit);775    source_change.extend(usages.iter().map(|(file_id, references)| {776        (777            file_id.file_id(sema.db),778            source_edit_from_references(779                sema.db,780                references,781                def,782                new_name,783                file_id.edition(sema.db),784            ),785        )786    }));787    transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);788    Ok(source_change)789}790791fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {792    let mut replacement_text = new_name;793    replacement_text.push_str(": ");794795    if self_param.amp_token().is_some() {796        replacement_text.push('&');797    }798    if let Some(lifetime) = self_param.lifetime() {799        write!(replacement_text, "{lifetime} ").unwrap();800    }801    if self_param.amp_token().and(self_param.mut_token()).is_some() {802        replacement_text.push_str("mut ");803    }804805    replacement_text.push_str("Self");806807    Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))808}809810fn rename_elided_lifetime(811    position: FilePosition,812    lifetime_token: syntax::SyntaxToken,813    new_name: &str,814) -> RenameResult<SourceChange> {815    let parent = lifetime_token.parent().unwrap();816    let root = parent.ancestors().last().unwrap();817818    let mut builder = SourceChangeBuilder::new(position.file_id);819820    let editor = builder.make_editor(&root);821    let make = editor.make();822823    editor.replace(lifetime_token, make.lifetime(new_name).syntax().clone());824825    if let Some(has_generic_params) = parent.ancestors().find_map(ast::AnyHasGenericParams::cast) {826        let lifetime_param = make.lifetime_param(make.lifetime(new_name));827        editor.add_generic_param(&has_generic_params, lifetime_param.into());828    }829830    builder.add_file_edits(position.file_id, editor);831832    Ok(builder.finish())833}834835#[cfg(test)]836mod tests {837    use expect_test::{Expect, expect};838    use ide_db::source_change::SourceChange;839    use ide_db::text_edit::TextEdit;840    use itertools::Itertools;841    use stdx::trim_indent;842    use test_utils::assert_eq_text;843844    use crate::fixture;845846    use super::{RangeInfo, RenameConfig, RenameError};847848    const TEST_CONFIG: RenameConfig = RenameConfig {849        prefer_no_std: false,850        prefer_prelude: true,851        prefer_absolute: false,852        show_conflicts: true,853    };854855    #[track_caller]856    fn check(857        new_name: &str,858        #[rust_analyzer::rust_fixture] ra_fixture_before: &str,859        #[rust_analyzer::rust_fixture] ra_fixture_after: &str,860    ) {861        let ra_fixture_after = &trim_indent(ra_fixture_after);862        let (analysis, position) = fixture::position(ra_fixture_before);863        if !ra_fixture_after.starts_with("error: ")864            && let Err(err) = analysis.prepare_rename(position).unwrap()865        {866            panic!("Prepare rename to '{new_name}' was failed: {err}")867        }868        let rename_result = analysis869            .rename(position, new_name, &TEST_CONFIG)870            .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));871        match rename_result {872            Ok(source_change) => {873                let mut text_edit_builder = TextEdit::builder();874                let (&file_id, edit) = match source_change.source_file_edits.len() {875                    0 => return,876                    1 => source_change.source_file_edits.iter().next().unwrap(),877                    _ => panic!(),878                };879                for indel in edit.0.iter() {880                    text_edit_builder.replace(indel.delete, indel.insert.clone());881                }882                let mut result = analysis.file_text(file_id).unwrap().to_string();883                text_edit_builder.finish().apply(&mut result);884                assert_eq_text!(ra_fixture_after, &*result);885            }886            Err(err) => {887                if ra_fixture_after.starts_with("error:") {888                    let error_message =889                        ra_fixture_after.chars().skip("error:".len()).collect::<String>();890                    assert_eq!(error_message.trim(), err.to_string());891                } else {892                    panic!("Rename to '{new_name}' failed unexpectedly: {err}")893                }894            }895        };896    }897898    #[track_caller]899    fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) {900        let (analysis, position, conflicts) = fixture::annotations(ra_fixture);901        let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();902        let expected_conflicts = conflicts903            .into_iter()904            .map(|(file_range, _)| (file_range.file_id, file_range.range))905            .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))906            .collect_vec();907        let found_conflicts = source_change908            .source_file_edits909            .iter()910            .filter(|(_, (edit, _))| edit.change_annotation().is_some())911            .flat_map(|(file_id, (edit, _))| {912                edit.into_iter().map(move |edit| (*file_id, edit.delete))913            })914            .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))915            .collect_vec();916        assert_eq!(917            expected_conflicts, found_conflicts,918            "rename conflicts mismatch: {source_change:#?}"919        );920    }921922    fn check_expect(923        new_name: &str,924        #[rust_analyzer::rust_fixture] ra_fixture: &str,925        expect: Expect,926    ) {927        let (analysis, position) = fixture::position(ra_fixture);928        let source_change = analysis929            .rename(position, new_name, &TEST_CONFIG)930            .unwrap()931            .expect("Expect returned a RenameError");932        expect.assert_eq(&filter_expect(source_change))933    }934935    fn check_expect_will_rename_file(936        new_name: &str,937        #[rust_analyzer::rust_fixture] ra_fixture: &str,938        expect: Expect,939    ) {940        let (analysis, position) = fixture::position(ra_fixture);941        let source_change = analysis942            .will_rename_file(position.file_id, new_name, &TEST_CONFIG)943            .unwrap()944            .expect("Expect returned a RenameError");945        expect.assert_eq(&filter_expect(source_change))946    }947948    fn check_prepare(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {949        let (analysis, position) = fixture::position(ra_fixture);950        let result = analysis951            .prepare_rename(position)952            .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {err}"));953        match result {954            Ok(RangeInfo { range, info: () }) => {955                let source = analysis.file_text(position.file_id).unwrap();956                expect.assert_eq(&format!("{range:?}: {}", &source[range]))957            }958            Err(RenameError(err)) => expect.assert_eq(&err),959        };960    }961962    fn filter_expect(source_change: SourceChange) -> String {963        let source_file_edits = source_change964            .source_file_edits965            .into_iter()966            .map(|(id, (text_edit, _))| (id, text_edit.into_iter().collect::<Vec<_>>()))967            .collect::<Vec<_>>();968969        format!(970            "source_file_edits: {:#?}\nfile_system_edits: {:#?}\n",971            source_file_edits, source_change.file_system_edits972        )973    }974975    #[test]976    fn rename_will_shadow() {977        check_conflicts(978            "new_name",979            r#"980fn foo() {981    let mut new_name = 123;982    let old_name$0 = 456;983     // ^^^^^^^^984    new_name = 789 + new_name;985}986        "#,987        );988    }989990    #[test]991    fn rename_will_be_shadowed() {992        check_conflicts(993            "new_name",994            r#"995fn foo() {996    let mut old_name$0 = 456;997         // ^^^^^^^^998    let new_name = 123;999    old_name = 789 + old_name;1000 // ^^^^^^^^         ^^^^^^^^1001}1002        "#,1003        );1004    }10051006    #[test]1007    fn test_prepare_rename_namelikes() {1008        check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);1009        check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]);1010        check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);1011    }10121013    #[test]1014    fn test_prepare_rename_in_macro() {1015        check_prepare(1016            r"macro_rules! foo {1017    ($ident:ident) => {1018        pub struct $ident;1019    }1020}1021foo!(Foo$0);",1022            expect![[r#"83..86: Foo"#]],1023        );1024    }10251026    #[test]1027    fn test_prepare_rename_keyword() {1028        check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);1029    }10301031    #[test]1032    fn test_prepare_rename_tuple_field() {1033        check_prepare(1034            r#"1035struct Foo(i32);10361037fn baz() {1038    let mut x = Foo(4);1039    x.0$0 = 5;1040}1041"#,1042            expect![[r#"No references found at position"#]],1043        );1044    }10451046    #[test]1047    fn test_prepare_rename_builtin() {1048        check_prepare(1049            r#"1050fn foo() {1051    let x: i32$0 = 0;1052}1053"#,1054            expect![[r#"No references found at position"#]],1055        );1056    }10571058    #[test]1059    fn test_prepare_rename_self() {1060        check_prepare(1061            r#"1062struct Foo {}10631064impl Foo {1065    fn foo(self) -> Self$0 {1066        self1067    }1068}1069"#,1070            expect![[r#"No references found at position"#]],1071        );1072    }10731074    #[test]1075    fn test_rename_to_underscore() {1076        check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);1077    }10781079    #[test]1080    fn test_rename_to_raw_identifier() {1081        check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);1082    }10831084    #[test]1085    fn test_rename_to_invalid_identifier1() {1086        check(1087            "invalid!",1088            r#"fn main() { let i$0 = 1; }"#,1089            "error: Invalid name `invalid!`: not an identifier",1090        );1091    }10921093    #[test]1094    fn test_rename_to_invalid_identifier2() {1095        check(1096            "multiple tokens",1097            r#"fn main() { let i$0 = 1; }"#,1098            "error: Invalid name `multiple tokens`: not an identifier",1099        );1100    }11011102    #[test]1103    fn test_rename_to_invalid_identifier3() {1104        check(1105            "super",1106            r#"fn main() { let i$0 = 1; }"#,1107            "error: Invalid name `super`: cannot rename to a keyword",1108        );1109    }11101111    #[test]1112    fn test_rename_to_invalid_identifier_lifetime() {1113        cov_mark::check!(rename_not_an_ident_ref);1114        check(1115            "'foo",1116            r#"fn main() { let i$0 = 1; }"#,1117            "error: Invalid name `'foo`: not an identifier",1118        );1119    }11201121    #[test]1122    fn test_rename_to_invalid_identifier_lifetime2() {1123        check(1124            "_",1125            r#"fn main<'a>(_: &'a$0 ()) {}"#,1126            r#"error: Invalid name `_`: not a lifetime identifier"#,1127        );1128    }11291130    #[test]1131    fn test_rename_accepts_lifetime_without_apostrophe() {1132        check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#);1133    }11341135    #[test]1136    fn test_rename_to_underscore_invalid() {1137        cov_mark::check!(rename_underscore_multiple);1138        check(1139            "_",1140            r#"fn main(foo$0: ()) {foo;}"#,1141            "error: Cannot rename reference to `_` as it is being referenced multiple times",1142        );1143    }11441145    #[test]1146    fn test_rename_mod_invalid() {1147        check(1148            "'foo",1149            r#"mod foo$0 {}"#,1150            "error: Invalid name `'foo`: cannot rename module to 'foo",1151        );1152    }11531154    #[test]1155    fn test_rename_mod_invalid_raw_ident() {1156        check(1157            "r#self",1158            r#"mod foo$0 {}"#,1159            "error: Invalid name `self`: cannot rename module to self",1160        );1161    }11621163    #[test]1164    fn test_rename_for_local() {1165        check(1166            "k",1167            r#"1168fn main() {1169    let mut i = 1;1170    let j = 1;1171    i = i$0 + j;11721173    { i = 0; }11741175    i = 5;1176}1177"#,1178            r#"1179fn main() {1180    let mut k = 1;1181    let j = 1;1182    k = k + j;11831184    { k = 0; }11851186    k = 5;1187}1188"#,1189        );1190    }11911192    #[test]1193    fn test_rename_unresolved_reference() {1194        check(1195            "new_name",1196            r#"fn main() { let _ = unresolved_ref$0; }"#,1197            "error: No references found at position",1198        );1199    }12001201    #[test]1202    fn test_rename_macro_multiple_occurrences() {1203        check(1204            "Baaah",1205            r#"macro_rules! foo {1206    ($ident:ident) => {1207        const $ident: () = ();1208        struct $ident {}1209    };1210}12111212foo!($0Foo);1213const _: () = Foo;1214const _: Foo = Foo {};1215    "#,1216            r#"1217macro_rules! foo {1218    ($ident:ident) => {1219        const $ident: () = ();1220        struct $ident {}1221    };1222}12231224foo!(Baaah);1225const _: () = Baaah;1226const _: Baaah = Baaah {};1227    "#,1228        )1229    }12301231    #[test]1232    fn test_rename_for_macro_args() {1233        check(1234            "b",1235            r#"1236macro_rules! foo {($i:ident) => {$i} }1237fn main() {1238    let a$0 = "test";1239    foo!(a);1240}1241"#,1242            r#"1243macro_rules! foo {($i:ident) => {$i} }1244fn main() {1245    let b = "test";1246    foo!(b);1247}1248"#,1249        );1250    }12511252    #[test]1253    fn test_rename_for_macro_args_rev() {1254        check(1255            "b",1256            r#"1257macro_rules! foo {($i:ident) => {$i} }1258fn main() {1259    let a = "test";1260    foo!(a$0);1261}1262"#,1263            r#"1264macro_rules! foo {($i:ident) => {$i} }1265fn main() {1266    let b = "test";1267    foo!(b);1268}1269"#,1270        );1271    }12721273    #[test]1274    fn test_rename_for_macro_define_fn() {1275        check(1276            "bar",1277            r#"1278macro_rules! define_fn {($id:ident) => { fn $id{} }}1279define_fn!(foo);1280fn main() {1281    fo$0o();1282}1283"#,1284            r#"1285macro_rules! define_fn {($id:ident) => { fn $id{} }}1286define_fn!(bar);1287fn main() {1288    bar();1289}1290"#,1291        );1292    }12931294    #[test]1295    fn test_rename_for_macro_define_fn_rev() {1296        check(1297            "bar",1298            r#"1299macro_rules! define_fn {($id:ident) => { fn $id{} }}1300define_fn!(fo$0o);1301fn main() {1302    foo();1303}1304"#,1305            r#"1306macro_rules! define_fn {($id:ident) => { fn $id{} }}1307define_fn!(bar);1308fn main() {1309    bar();1310}1311"#,1312        );1313    }13141315    #[test]1316    fn test_rename_for_param_inside() {1317        check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);1318    }13191320    #[test]1321    fn test_rename_refs_for_fn_param() {1322        check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);1323    }13241325    #[test]1326    fn test_rename_for_mut_param() {1327        check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);1328    }13291330    #[test]1331    fn test_rename_struct_field() {1332        check(1333            "foo",1334            r#"1335struct Foo { field$0: i32 }13361337impl Foo {1338    fn new(i: i32) -> Self {1339        Self { field: i }1340    }1341}1342"#,1343            r#"1344struct Foo { foo: i32 }13451346impl Foo {1347    fn new(i: i32) -> Self {1348        Self { foo: i }1349    }1350}1351"#,1352        );1353    }13541355    #[test]1356    fn test_rename_field_in_field_shorthand() {1357        cov_mark::check!(test_rename_field_in_field_shorthand);1358        check(1359            "field",1360            r#"1361struct Foo { foo$0: i32 }13621363impl Foo {1364    fn foo(foo: i32) {1365        Self { foo };1366    }1367}1368"#,1369            r#"1370struct Foo { field: i32 }13711372impl Foo {1373    fn foo(foo: i32) {1374        Self { field: foo };1375    }1376}1377"#,1378        );1379    }13801381    #[test]1382    fn test_rename_local_in_field_shorthand() {1383        cov_mark::check!(test_rename_local_in_field_shorthand);1384        check(1385            "j",1386            r#"1387struct Foo { i: i32 }13881389impl Foo {1390    fn new(i$0: i32) -> Self {1391        Self { i }1392    }1393}1394"#,1395            r#"1396struct Foo { i: i32 }13971398impl Foo {1399    fn new(j: i32) -> Self {1400        Self { i: j }1401    }1402}1403"#,1404        );1405    }14061407    #[test]1408    fn test_field_shorthand_correct_struct() {1409        check(1410            "j",1411            r#"1412struct Foo { i$0: i32 }1413struct Bar { i: i32 }14141415impl Bar {1416    fn new(i: i32) -> Self {1417        Self { i }1418    }1419}1420"#,1421            r#"1422struct Foo { j: i32 }1423struct Bar { i: i32 }14241425impl Bar {1426    fn new(i: i32) -> Self {1427        Self { i }1428    }1429}1430"#,1431        );1432    }14331434    #[test]1435    fn test_shadow_local_for_struct_shorthand() {1436        check(1437            "j",1438            r#"1439struct Foo { i: i32 }14401441fn baz(i$0: i32) -> Self {1442     let x = Foo { i };1443     {1444         let i = 0;1445         Foo { i }1446     }1447}1448"#,1449            r#"1450struct Foo { i: i32 }14511452fn baz(j: i32) -> Self {1453     let x = Foo { i: j };1454     {1455         let i = 0;1456         Foo { i }1457     }1458}1459"#,1460        );1461    }14621463    #[test]1464    fn test_rename_mod() {1465        check_expect(1466            "foo2",1467            r#"1468//- /lib.rs1469mod bar;14701471//- /bar.rs1472mod foo$0;14731474//- /bar/foo.rs1475// empty1476"#,1477            expect![[r#"1478                source_file_edits: [1479                    (1480                        FileId(1481                            1,1482                        ),1483                        [1484                            Indel {1485                                insert: "foo2",1486                                delete: 4..7,1487                            },1488                        ],1489                    ),1490                ]1491                file_system_edits: [1492                    MoveFile {1493                        src: FileId(1494                            2,1495                        ),1496                        dst: AnchoredPathBuf {1497                            anchor: FileId(1498                                2,1499                            ),1500                            path: "foo2.rs",1501                        },1502                    },1503                ]1504            "#]],1505        );1506    }15071508    #[test]1509    fn test_rename_mod_in_use_tree() {1510        check_expect(1511            "quux",1512            r#"1513//- /main.rs1514pub mod foo;1515pub mod bar;1516fn main() {}15171518//- /foo.rs1519pub struct FooContent;15201521//- /bar.rs1522use crate::foo$0::FooContent;1523"#,1524            expect![[r#"1525                source_file_edits: [1526                    (1527                        FileId(1528                            0,1529                        ),1530                        [1531                            Indel {1532                                insert: "quux",1533                                delete: 8..11,1534                            },1535                        ],1536                    ),1537                    (1538                        FileId(1539                            2,1540                        ),1541                        [1542                            Indel {1543                                insert: "quux",1544                                delete: 11..14,1545                            },1546                        ],1547                    ),1548                ]1549                file_system_edits: [1550                    MoveFile {1551                        src: FileId(1552                            1,1553                        ),1554                        dst: AnchoredPathBuf {1555                            anchor: FileId(1556                                1,1557                            ),1558                            path: "quux.rs",1559                        },1560                    },1561                ]1562            "#]],1563        );1564    }15651566    #[test]1567    fn test_rename_mod_in_dir() {1568        check_expect(1569            "foo2",1570            r#"1571//- /lib.rs1572mod fo$0o;1573//- /foo/mod.rs1574// empty1575"#,1576            expect![[r#"1577                source_file_edits: [1578                    (1579                        FileId(1580                            0,1581                        ),1582                        [1583                            Indel {1584                                insert: "foo2",1585                                delete: 4..7,1586                            },1587                        ],1588                    ),1589                ]1590                file_system_edits: [1591                    MoveDir {1592                        src: AnchoredPathBuf {1593                            anchor: FileId(1594                                1,1595                            ),1596                            path: "../foo",1597                        },1598                        src_id: FileId(1599                            1,1600                        ),1601                        dst: AnchoredPathBuf {1602                            anchor: FileId(1603                                1,1604                            ),1605                            path: "../foo2",1606                        },1607                    },1608                ]1609            "#]],1610        );1611    }16121613    #[test]1614    fn test_rename_unusually_nested_mod() {1615        check_expect(1616            "bar",1617            r#"1618//- /lib.rs1619mod outer { mod fo$0o; }16201621//- /outer/foo.rs1622// empty1623"#,1624            expect![[r#"1625                source_file_edits: [1626                    (1627                        FileId(1628                            0,1629                        ),1630                        [1631                            Indel {1632                                insert: "bar",1633                                delete: 16..19,1634                            },1635                        ],1636                    ),1637                ]1638                file_system_edits: [1639                    MoveFile {1640                        src: FileId(1641                            1,1642                        ),1643                        dst: AnchoredPathBuf {1644                            anchor: FileId(1645                                1,1646                            ),1647                            path: "bar.rs",1648                        },1649                    },1650                ]1651            "#]],1652        );1653    }16541655    #[test]1656    fn test_module_rename_in_path() {1657        check(1658            "baz",1659            r#"1660mod $0foo {1661    pub use self::bar as qux;1662    pub fn bar() {}1663}16641665fn main() { foo::bar(); }1666"#,1667            r#"1668mod baz {1669    pub use self::bar as qux;1670    pub fn bar() {}1671}16721673fn main() { baz::bar(); }1674"#,1675        );1676    }16771678    #[test]1679    fn test_rename_mod_filename_and_path() {1680        check_expect(1681            "foo2",1682            r#"1683//- /lib.rs1684mod bar;1685fn f() {1686    bar::foo::fun()1687}16881689//- /bar.rs1690pub mod foo$0;16911692//- /bar/foo.rs1693// pub fn fun() {}1694"#,1695            expect![[r#"1696                source_file_edits: [1697                    (1698                        FileId(1699                            0,1700                        ),1701                        [1702                            Indel {1703                                insert: "foo2",1704                                delete: 27..30,1705                            },1706                        ],1707                    ),1708                    (1709                        FileId(1710                            1,1711                        ),1712                        [1713                            Indel {1714                                insert: "foo2",1715                                delete: 8..11,1716                            },1717                        ],1718                    ),1719                ]1720                file_system_edits: [1721                    MoveFile {1722                        src: FileId(1723                            2,1724                        ),1725                        dst: AnchoredPathBuf {1726                            anchor: FileId(1727                                2,1728                            ),1729                            path: "foo2.rs",1730                        },1731                    },1732                ]1733            "#]],1734        );1735    }17361737    #[test]1738    fn test_rename_mod_recursive() {1739        check_expect(1740            "foo2",1741            r#"1742//- /lib.rs1743mod foo$0;17441745//- /foo.rs1746mod bar;1747mod corge;17481749//- /foo/bar.rs1750mod qux;17511752//- /foo/bar/qux.rs1753mod quux;17541755//- /foo/bar/qux/quux/mod.rs1756// empty17571758//- /foo/corge.rs1759// empty1760"#,1761            expect![[r#"1762                source_file_edits: [1763                    (1764                        FileId(1765                            0,1766                        ),1767                        [1768                            Indel {1769                                insert: "foo2",1770                                delete: 4..7,1771                            },1772                        ],1773                    ),1774                ]1775                file_system_edits: [1776                    MoveFile {1777                        src: FileId(1778                            1,1779                        ),1780                        dst: AnchoredPathBuf {1781                            anchor: FileId(1782                                1,1783                            ),1784                            path: "foo2.rs",1785                        },1786                    },1787                    MoveDir {1788                        src: AnchoredPathBuf {1789                            anchor: FileId(1790                                1,1791                            ),1792                            path: "foo",1793                        },1794                        src_id: FileId(1795                            1,1796                        ),1797                        dst: AnchoredPathBuf {1798                            anchor: FileId(1799                                1,1800                            ),1801                            path: "foo2",1802                        },1803                    },1804                ]1805            "#]],1806        )1807    }1808    #[test]1809    fn test_rename_mod_ref_by_super() {1810        check(1811            "baz",1812            r#"1813        mod $0foo {1814        struct X;18151816        mod bar {1817            use super::X;1818        }1819    }1820            "#,1821            r#"1822        mod baz {1823        struct X;18241825        mod bar {1826            use super::X;1827        }1828    }1829            "#,1830        )1831    }18321833    #[test]1834    fn test_rename_mod_in_macro() {1835        check(1836            "bar",1837            r#"1838//- /foo.rs18391840//- /lib.rs1841macro_rules! submodule {1842    ($name:ident) => {1843        mod $name;1844    };1845}18461847submodule!($0foo);1848"#,1849            r#"1850macro_rules! submodule {1851    ($name:ident) => {1852        mod $name;1853    };1854}18551856submodule!(bar);1857"#,1858        )1859    }18601861    #[test]1862    fn test_rename_mod_for_crate_root() {1863        check_expect_will_rename_file(1864            "main",1865            r#"1866//- /lib.rs1867use crate::foo as bar;1868fn foo() {}1869mod bar$0;1870"#,1871            expect![[r#"1872                source_file_edits: []1873                file_system_edits: []1874            "#]],1875        )1876    }18771878    #[test]1879    fn test_rename_mod_to_raw_ident() {1880        check_expect(1881            "r#fn",1882            r#"1883//- /lib.rs1884mod foo$0;18851886fn main() { foo::bar::baz(); }18871888//- /foo.rs1889pub mod bar;18901891//- /foo/bar.rs1892pub fn baz() {}1893"#,1894            expect![[r#"1895                source_file_edits: [1896                    (1897                        FileId(1898                            0,1899                        ),1900                        [1901                            Indel {1902                                insert: "r#fn",1903                                delete: 4..7,1904                            },1905                            Indel {1906                                insert: "r#fn",1907                                delete: 22..25,1908                            },1909                        ],1910                    ),1911                ]1912                file_system_edits: [1913                    MoveFile {1914                        src: FileId(1915                            1,1916                        ),1917                        dst: AnchoredPathBuf {1918                            anchor: FileId(1919                                1,1920                            ),1921                            path: "fn.rs",1922                        },1923                    },1924                    MoveDir {1925                        src: AnchoredPathBuf {1926                            anchor: FileId(1927                                1,1928                            ),1929                            path: "foo",1930                        },1931                        src_id: FileId(1932                            1,1933                        ),1934                        dst: AnchoredPathBuf {1935                            anchor: FileId(1936                                1,1937                            ),1938                            path: "fn",1939                        },1940                    },1941                ]1942            "#]],1943        );1944    }19451946    #[test]1947    fn test_rename_mod_from_raw_ident() {1948        check_expect(1949            "foo",1950            r#"1951//- /lib.rs1952mod r#fn$0;19531954fn main() { r#fn::bar::baz(); }19551956//- /fn.rs1957pub mod bar;19581959//- /fn/bar.rs1960pub fn baz() {}1961"#,1962            expect![[r#"1963                source_file_edits: [1964                    (1965                        FileId(1966                            0,1967                        ),1968                        [1969                            Indel {1970                                insert: "foo",1971                                delete: 4..8,1972                            },1973                            Indel {1974                                insert: "foo",1975                                delete: 23..27,1976                            },1977                        ],1978                    ),1979                ]1980                file_system_edits: [1981                    MoveFile {1982                        src: FileId(1983                            1,1984                        ),1985                        dst: AnchoredPathBuf {1986                            anchor: FileId(1987                                1,1988                            ),1989                            path: "foo.rs",1990                        },1991                    },1992                    MoveDir {1993                        src: AnchoredPathBuf {1994                            anchor: FileId(1995                                1,1996                            ),1997                            path: "fn",1998                        },1999                        src_id: FileId(2000                            1,

Code quality findings 35

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
//! This is mostly front-end for [`ide_db::rename`], but it also includes the
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 root = parent.ancestors().last().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]))
Warning: Ignoring a Result or Option using 'let _ =' can hide errors or unexpected None values. Ensure the value is handled appropriately (match, if let, ?, expect) unless intentionally discarded with justification.
warning correctness discarded-result
check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);
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.