src/tools/rust-analyzer/crates/ide/src/rename.rs RUST 3,934 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 3,934.
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();8283    let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))?84        .filter(|(_, _, def, _, _)| def.range_for_rename(&sema).is_some())85        .map(|(frange, kind, _, _, _)| {86            always!(87                frange.range.contains_inclusive(position.offset)88                    && frange.file_id == position.file_id89            );9091            Ok(match kind {92                SyntaxKind::LIFETIME => {93                    TextRange::new(frange.range.start() + TextSize::from(1), frange.range.end())94                }95                _ => frange.range,96            })97        })98        .reduce(|acc, cur| match (acc, cur) {99            // ensure all ranges are the same100            (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),101            (e @ Err(_), _) | (_, e @ Err(_)) => e,102            _ => bail!("inconsistent text range"),103        });104105    match res {106        // ensure at least one definition was found107        Some(res) => res.map(|range| RangeInfo::new(range, ())),108        None => bail!("No references found at position"),109    }110}111112// Feature: Rename113//114// Renames the item below the cursor and all of its references115//116// | Editor  | Shortcut |117// |---------|----------|118// | VS Code | <kbd>F2</kbd> |119//120// ![Rename](https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif)121pub(crate) fn rename(122    db: &RootDatabase,123    position: FilePosition,124    new_name: &str,125    config: &RenameConfig,126) -> RenameResult<SourceChange> {127    let sema = Semantics::new(db);128    let file_id = sema129        .attach_first_edition_opt(position.file_id)130        .ok_or_else(|| format_err!("No references found at position"))?;131    let source_file = sema.parse(file_id);132    let syntax = source_file.syntax();133134    let edition = file_id.edition(db);135    let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;136137    let defs = find_definitions(&sema, syntax, position, &new_name)?;138    let alias_fallback =139        alias_fallback(syntax, position, &new_name.display(db, edition).to_string());140141    let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {142        Some(_) => ok_if_any(143            defs144                // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can145                // properly find "direct" usages/references.146                .map(|(.., def, new_name, _)| {147                    match kind {148                        IdentifierKind::Ident => (),149                        IdentifierKind::Lifetime => {150                            bail!("Cannot alias reference to a lifetime identifier")151                        }152                        IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),153                        IdentifierKind::LowercaseSelf => {154                            bail!("Cannot rename alias reference to `self`")155                        }156                    };157                    let mut usages = def.usages(&sema).all();158159                    // FIXME: hack - removes the usage that triggered this rename operation.160                    match usages.references.get_mut(&file_id).and_then(|refs| {161                        refs.iter()162                            .position(|ref_| ref_.range.contains_inclusive(position.offset))163                            .map(|idx| refs.remove(idx))164                    }) {165                        Some(_) => (),166                        None => never!(),167                    };168169                    let mut source_change = SourceChange::default();170                    source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {171                        (172                            position.file_id,173                            source_edit_from_references(db, refs, def, &new_name, edition),174                        )175                    }));176177                    Ok(source_change)178                }),179        ),180        None => ok_if_any(defs.map(|(.., def, new_name, rename_def)| {181            if let Definition::Local(local) = def {182                if let Some(self_param) = local.as_self_param(sema.db) {183                    cov_mark::hit!(rename_self_to_param);184                    return rename_self_to_param(185                        &sema,186                        local,187                        self_param,188                        &new_name,189                        kind,190                        config.find_path_config(),191                    );192                }193                if kind == IdentifierKind::LowercaseSelf {194                    cov_mark::hit!(rename_to_self);195                    return rename_to_self(&sema, local);196                }197            }198            def.rename(&sema, new_name.as_str(), rename_def, &config.ide_db_config())199        })),200    };201202    ops?.into_iter()203        .chain(alias_fallback)204        .reduce(|acc, elem| acc.merge(elem))205        .ok_or_else(|| format_err!("No references found at position"))206}207208/// Called by the client when it is about to rename a file.209pub(crate) fn will_rename_file(210    db: &RootDatabase,211    file_id: FileId,212    new_name_stem: &str,213    config: &RenameConfig,214) -> Option<SourceChange> {215    let sema = Semantics::new(db);216    let module = sema.file_to_module_def(file_id)?;217    let def = Definition::Module(module);218    let mut change =219        def.rename(&sema, new_name_stem, RenameDefinition::Yes, &config.ide_db_config()).ok()?;220    change.file_system_edits.clear();221    Some(change)222}223224// FIXME: Should support `extern crate`.225fn alias_fallback(226    syntax: &SyntaxNode,227    FilePosition { file_id, offset }: FilePosition,228    new_name: &str,229) -> Option<SourceChange> {230    let use_tree = syntax231        .token_at_offset(offset)232        .flat_map(|syntax| syntax.parent_ancestors())233        .find_map(ast::UseTree::cast)?;234235    let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;236    if !last_path_segment.syntax().text_range().contains_inclusive(offset) {237        return None;238    };239240    let mut builder = SourceChangeBuilder::new(file_id);241242    match use_tree.rename() {243        Some(rename) => {244            let offset = rename.syntax().text_range();245            builder.replace(offset, format!("as {new_name}"));246        }247        None => {248            let offset = use_tree.syntax().text_range().end();249            builder.insert(offset, format!(" as {new_name}"));250        }251    }252253    Some(builder.finish())254}255256fn find_definitions(257    sema: &Semantics<'_, RootDatabase>,258    syntax: &SyntaxNode,259    FilePosition { file_id, offset }: FilePosition,260    new_name: &Name,261) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition, Name, RenameDefinition)>>262{263    let maybe_format_args =264        syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));265266    if let Some((range, _, _, Some(resolution))) =267        maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset))268    {269        return Ok(vec![(270            FileRange { file_id, range },271            SyntaxKind::STRING,272            Definition::from(resolution),273            new_name.clone(),274            RenameDefinition::Yes,275        )]276        .into_iter());277    }278279    let original_ident = syntax280        .token_at_offset(offset)281        .max_by_key(|t| {282            t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT)283        })284        .map(|t| {285            if t.kind() == SyntaxKind::LIFETIME_IDENT {286                Name::new_lifetime(t.text())287            } else {288                Name::new_root(t.text())289            }290        })291        .ok_or_else(|| format_err!("No references found at position"))?;292    let symbols =293        sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| {294            let kind = name_like.syntax().kind();295            let range = sema296                .original_range_opt(name_like.syntax())297                .ok_or_else(|| format_err!("No references found at position"))?;298            let res = match &name_like {299                // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet300                ast::NameLike::Name(name)301                    if name302                        .syntax()303                        .parent().is_some_and(|it| ast::Rename::can_cast(it.kind()))304                        // FIXME: uncomment this once we resolve to usages to extern crate declarations305                        // && name306                        //     .syntax()307                        //     .ancestors()308                        //     .nth(2)309                        //     .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind()))310                        =>311                {312                    bail!("Renaming aliases is currently unsupported")313                }314                ast::NameLike::Name(name) => NameClass::classify(sema, name)315                    .map(|class| match class {316                        NameClass::Definition(it) | NameClass::ConstReference(it) => it,317                        NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {318                            Definition::Local(local_def)319                        }320                    })321                    .ok_or_else(|| format_err!("No references found at position")),322                ast::NameLike::NameRef(name_ref) => {323                    NameRefClass::classify(sema, name_ref)324                        .map(|class| match class {325                            NameRefClass::Definition(def, _) => def,326                            NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => {327                                Definition::Local(local_ref)328                            }329                            NameRefClass::ExternCrateShorthand { decl, .. } => {330                                Definition::ExternCrateDecl(decl)331                            }332                        })333                        // FIXME: uncomment this once we resolve to usages to extern crate declarations334                        .filter(|def| !matches!(def, Definition::ExternCrateDecl(..)))335                        .ok_or_else(|| format_err!("No references found at position"))336                        .and_then(|def| {337                            // if the name differs from the definitions name it has to be an alias338                            if def339                                .name(sema.db).is_some_and(|it| it.as_str() != name_ref.text().trim_start_matches("r#"))340                            {341                                Err(format_err!("Renaming aliases is currently unsupported"))342                            } else {343                                Ok(def)344                            }345                        })346                }347                ast::NameLike::Lifetime(lifetime) => {348                    NameRefClass::classify_lifetime(sema, lifetime)349                        .and_then(|class| match class {350                            NameRefClass::Definition(def, _) => Some(def),351                            _ => None,352                        })353                        .or_else(|| {354                            NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {355                                NameClass::Definition(it) => Some(it),356                                _ => None,357                            })358                        })359                        .ok_or_else(|| format_err!("No references found at position"))360                }361            };362            res.map(|def| {363                let n = def.name(sema.db)?;364                if n == original_ident {365                    Some((range, kind, def, new_name.clone(), RenameDefinition::Yes))366                } else if let Some(suffix) =  n.as_str().strip_prefix(original_ident.as_str()) {367                    Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No))368                } else {369                     n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\''))370                        .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No))371                }372            })373        });374375    let res: RenameResult<Vec<_>> = ok_if_any(symbols.filter_map(Result::transpose));376    match res {377        Ok(v) => {378            // remove duplicates, comparing `Definition`s379            Ok(v.into_iter()380                .unique_by(|&(.., def, _, _)| def)381                .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e))382                .collect::<Vec<_>>()383                .into_iter())384        }385        Err(e) => Err(e),386    }387}388389fn transform_assoc_fn_into_method_call(390    sema: &Semantics<'_, RootDatabase>,391    source_change: &mut SourceChange,392    f: hir::Function,393) {394    let calls = Definition::Function(f).usages(sema).all();395    for (_file_id, calls) in calls {396        for call in calls {397            let Some(fn_name) = call.name.as_name_ref() else { continue };398            let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {399                continue;400            };401            let path = path.parent_path();402            // The `PathExpr` is the direct parent, above it is the `CallExpr`.403            let Some(call) =404                path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))405            else {406                continue;407            };408409            let Some(arg_list) = call.arg_list() else { continue };410            let mut args = arg_list.args();411            let Some(mut self_arg) = args.next() else { continue };412            let second_arg = args.next();413414            // Strip (de)references, as they will be taken automatically by auto(de)ref.415            loop {416                let self_ = match &self_arg {417                    ast::Expr::RefExpr(self_) => self_.expr(),418                    ast::Expr::ParenExpr(self_) => self_.expr(),419                    ast::Expr::PrefixExpr(self_)420                        if self_.op_kind() == Some(ast::UnaryOp::Deref) =>421                    {422                        self_.expr()423                    }424                    _ => break,425                };426                self_arg = match self_ {427                    Some(it) => it,428                    None => break,429                };430            }431432            let self_needs_parens =433                self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);434435            let replace_start = path.syntax().text_range().start();436            let replace_end = match second_arg {437                Some(second_arg) => second_arg.syntax().text_range().start(),438                None => arg_list439                    .r_paren_token()440                    .map(|it| it.text_range().start())441                    .unwrap_or_else(|| arg_list.syntax().text_range().end()),442            };443            let replace_range = TextRange::new(replace_start, replace_end);444            let macro_file = sema.hir_file_for(fn_name.syntax());445            let Some((replace_range, _)) =446                InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)447            else {448                continue;449            };450451            let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {452                continue;453            };454            let mut replacement = String::new();455            if self_needs_parens {456                replacement.push('(');457            }458            replacement.push_str(macro_mapped_self.text(sema.db));459            if self_needs_parens {460                replacement.push(')');461            }462            replacement.push('.');463            format_to!(replacement, "{fn_name}");464            replacement.push('(');465466            source_change.insert_source_edit(467                replace_range.file_id.file_id(sema.db),468                TextEdit::replace(replace_range.range, replacement),469            );470        }471    }472}473474fn rename_to_self(475    sema: &Semantics<'_, RootDatabase>,476    local: hir::Local,477) -> RenameResult<SourceChange> {478    if never!(local.is_self(sema.db)) {479        bail!("rename_to_self invoked on self");480    }481482    let fn_def = match local.parent(sema.db) {483        hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,484        _ => bail!("Cannot rename local to self outside of function"),485    };486487    if fn_def.self_param(sema.db).is_some() {488        bail!("Method already has a self parameter");489    }490491    let params = fn_def.assoc_fn_params(sema.db);492    let first_param = params493        .first()494        .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;495    match first_param.as_local(sema.db) {496        Some(plocal) => {497            if plocal != local {498                bail!("Only the first parameter may be renamed to self");499            }500        }501        None => bail!("rename_to_self invoked on destructuring parameter"),502    }503504    let assoc_item = fn_def505        .as_assoc_item(sema.db)506        .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;507    let impl_ = match assoc_item.container(sema.db) {508        hir::AssocItemContainer::Trait(_) => {509            bail!("Cannot rename parameter to self for trait functions");510        }511        hir::AssocItemContainer::Impl(impl_) => impl_,512    };513    let first_param_ty = first_param.ty();514    let impl_ty = impl_.self_ty(sema.db);515    let (ty, self_param) = if impl_ty.remove_ref().is_some() {516        // if the impl is a ref to the type we can just match the `&T` with self directly517        (first_param_ty.clone(), "self")518    } else {519        first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {520            (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })521        })522    };523524    if ty != impl_ty {525        bail!("Parameter type differs from impl block type");526    }527528    let InFile { file_id, value: param_source } = sema529        .source(first_param.clone())530        .ok_or_else(|| format_err!("No source for parameter found"))?;531532    let def = Definition::Local(local);533    let usages = def.usages(sema).all();534    let mut source_change = SourceChange::default();535    source_change.extend(usages.iter().map(|(file_id, references)| {536        (537            file_id.file_id(sema.db),538            source_edit_from_references(539                sema.db,540                references,541                def,542                &Name::new_symbol_root(sym::self_),543                file_id.edition(sema.db),544            ),545        )546    }));547    source_change.insert_source_edit(548        file_id.original_file(sema.db).file_id(sema.db),549        TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),550    );551    transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);552    Ok(source_change)553}554555#[derive(Debug, Clone, Copy, PartialEq, Eq)]556enum CallReceiverAdjust {557    Deref,558    Ref,559    RefMut,560    None,561}562563fn method_to_assoc_fn_call_self_adjust(564    sema: &Semantics<'_, RootDatabase>,565    self_arg: &ast::Expr,566) -> CallReceiverAdjust {567    let mut result = CallReceiverAdjust::None;568    let self_adjust = sema.expr_adjustments(self_arg);569    if let Some(self_adjust) = self_adjust {570        let mut i = 0;571        while i < self_adjust.len() {572            if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))573                && matches!(574                    self_adjust.get(i + 1),575                    Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. })576                )577            {578                // Deref then ref (reborrow), skip them.579                i += 2;580                continue;581            }582583            match self_adjust[i].kind {584                hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => {585                    // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref586                    // it automatically.587                    result = CallReceiverAdjust::Deref;588                }589                hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => {590                    match (result, mutability) {591                        (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {}592                        (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut,593                        (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref,594                    }595                }596                _ => {}597            }598599            i += 1;600        }601    }602    result603}604605fn transform_method_call_into_assoc_fn(606    sema: &Semantics<'_, RootDatabase>,607    source_change: &mut SourceChange,608    f: hir::Function,609    find_path_config: FindPathConfig,610) {611    let calls = Definition::Function(f).usages(sema).all();612    for (_file_id, calls) in calls {613        for call in calls {614            let Some(fn_name) = call.name.as_name_ref() else { continue };615            let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast)616            else {617                continue;618            };619            let Some(mut self_arg) = method_call.receiver() else {620                continue;621            };622623            let Some(scope) = sema.scope(fn_name.syntax()) else {624                continue;625            };626            let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg);627628            // Strip parentheses, function arguments have higher precedence than any operator.629            while let ast::Expr::ParenExpr(it) = &self_arg {630                self_arg = match it.expr() {631                    Some(it) => it,632                    None => break,633                };634            }635636            let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some());637638            let self_needs_parens = self_adjust != CallReceiverAdjust::None639                && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix);640641            let replace_start = method_call.syntax().text_range().start();642            let replace_end = method_call643                .arg_list()644                .and_then(|it| it.l_paren_token())645                .map(|it| it.text_range().end())646                .unwrap_or_else(|| method_call.syntax().text_range().end());647            let replace_range = TextRange::new(replace_start, replace_end);648            let macro_file = sema.hir_file_for(fn_name.syntax());649            let Some((replace_range, _)) =650                InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)651            else {652                continue;653            };654655            let fn_container_path = match f.container(sema.db) {656                hir::ItemContainer::Trait(trait_) => {657                    // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but658                    // that could conflict with an inherent method)? Or maybe `<Type as Trait>::function`?659                    // Or let the user decide?660                    let Some(path) = scope.module().find_path(661                        sema.db,662                        hir::ItemInNs::Types(trait_.into()),663                        find_path_config,664                    ) else {665                        continue;666                    };667                    path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string()668                }669                hir::ItemContainer::Impl(impl_) => {670                    let ty = impl_.self_ty(sema.db);671                    match ty.as_adt() {672                        Some(adt) => {673                            let Some(path) = scope.module().find_path(674                                sema.db,675                                hir::ItemInNs::Types(adt.into()),676                                find_path_config,677                            ) else {678                                continue;679                            };680                            path.display(sema.db, replace_range.file_id.edition(sema.db))681                                .to_string()682                        }683                        None => {684                            let Ok(mut ty) =685                                ty.display_source_code(sema.db, scope.module().into(), false)686                            else {687                                continue;688                            };689                            ty.insert(0, '<');690                            ty.push('>');691                            ty692                        }693                    }694                }695                _ => continue,696            };697698            let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {699                continue;700            };701            let mut replacement = String::new();702            replacement.push_str(&fn_container_path);703            replacement.push_str("::");704            format_to!(replacement, "{fn_name}");705            replacement.push('(');706            replacement.push_str(match self_adjust {707                CallReceiverAdjust::Deref => "*",708                CallReceiverAdjust::Ref => "&",709                CallReceiverAdjust::RefMut => "&mut ",710                CallReceiverAdjust::None => "",711            });712            if self_needs_parens {713                replacement.push('(');714            }715            replacement.push_str(macro_mapped_self.text(sema.db));716            if self_needs_parens {717                replacement.push(')');718            }719            if needs_comma {720                replacement.push_str(", ");721            }722723            source_change.insert_source_edit(724                replace_range.file_id.file_id(sema.db),725                TextEdit::replace(replace_range.range, replacement),726            );727        }728    }729}730731fn rename_self_to_param(732    sema: &Semantics<'_, RootDatabase>,733    local: hir::Local,734    self_param: hir::SelfParam,735    new_name: &Name,736    identifier_kind: IdentifierKind,737    find_path_config: FindPathConfig,738) -> RenameResult<SourceChange> {739    if identifier_kind == IdentifierKind::LowercaseSelf {740        // Let's do nothing rather than complain.741        cov_mark::hit!(rename_self_to_self);742        return Ok(SourceChange::default());743    }744745    let fn_def = match local.parent(sema.db) {746        hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,747        _ => bail!("Cannot rename local to self outside of function"),748    };749750    let InFile { file_id, value: self_param } =751        sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;752753    let def = Definition::Local(local);754    let usages = def.usages(sema).all();755    let edit = text_edit_from_self_param(756        &self_param,757        new_name.display(sema.db, file_id.edition(sema.db)).to_string(),758    )759    .ok_or_else(|| format_err!("No target type found"))?;760    if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {761        bail!("Cannot rename reference to `_` as it is being referenced multiple times");762    }763    let mut source_change = SourceChange::default();764    source_change.insert_source_edit(file_id.original_file(sema.db).file_id(sema.db), edit);765    source_change.extend(usages.iter().map(|(file_id, references)| {766        (767            file_id.file_id(sema.db),768            source_edit_from_references(769                sema.db,770                references,771                def,772                new_name,773                file_id.edition(sema.db),774            ),775        )776    }));777    transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);778    Ok(source_change)779}780781fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {782    let mut replacement_text = new_name;783    replacement_text.push_str(": ");784785    if self_param.amp_token().is_some() {786        replacement_text.push('&');787    }788    if let Some(lifetime) = self_param.lifetime() {789        write!(replacement_text, "{lifetime} ").unwrap();790    }791    if self_param.amp_token().and(self_param.mut_token()).is_some() {792        replacement_text.push_str("mut ");793    }794795    replacement_text.push_str("Self");796797    Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))798}799800#[cfg(test)]801mod tests {802    use expect_test::{Expect, expect};803    use ide_db::source_change::SourceChange;804    use ide_db::text_edit::TextEdit;805    use itertools::Itertools;806    use stdx::trim_indent;807    use test_utils::assert_eq_text;808809    use crate::fixture;810811    use super::{RangeInfo, RenameConfig, RenameError};812813    const TEST_CONFIG: RenameConfig = RenameConfig {814        prefer_no_std: false,815        prefer_prelude: true,816        prefer_absolute: false,817        show_conflicts: true,818    };819820    #[track_caller]821    fn check(822        new_name: &str,823        #[rust_analyzer::rust_fixture] ra_fixture_before: &str,824        #[rust_analyzer::rust_fixture] ra_fixture_after: &str,825    ) {826        let ra_fixture_after = &trim_indent(ra_fixture_after);827        let (analysis, position) = fixture::position(ra_fixture_before);828        if !ra_fixture_after.starts_with("error: ")829            && let Err(err) = analysis.prepare_rename(position).unwrap()830        {831            panic!("Prepare rename to '{new_name}' was failed: {err}")832        }833        let rename_result = analysis834            .rename(position, new_name, &TEST_CONFIG)835            .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));836        match rename_result {837            Ok(source_change) => {838                let mut text_edit_builder = TextEdit::builder();839                let (&file_id, edit) = match source_change.source_file_edits.len() {840                    0 => return,841                    1 => source_change.source_file_edits.iter().next().unwrap(),842                    _ => panic!(),843                };844                for indel in edit.0.iter() {845                    text_edit_builder.replace(indel.delete, indel.insert.clone());846                }847                let mut result = analysis.file_text(file_id).unwrap().to_string();848                text_edit_builder.finish().apply(&mut result);849                assert_eq_text!(ra_fixture_after, &*result);850            }851            Err(err) => {852                if ra_fixture_after.starts_with("error:") {853                    let error_message =854                        ra_fixture_after.chars().skip("error:".len()).collect::<String>();855                    assert_eq!(error_message.trim(), err.to_string());856                } else {857                    panic!("Rename to '{new_name}' failed unexpectedly: {err}")858                }859            }860        };861    }862863    #[track_caller]864    fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) {865        let (analysis, position, conflicts) = fixture::annotations(ra_fixture);866        let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();867        let expected_conflicts = conflicts868            .into_iter()869            .map(|(file_range, _)| (file_range.file_id, file_range.range))870            .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))871            .collect_vec();872        let found_conflicts = source_change873            .source_file_edits874            .iter()875            .filter(|(_, (edit, _))| edit.change_annotation().is_some())876            .flat_map(|(file_id, (edit, _))| {877                edit.into_iter().map(move |edit| (*file_id, edit.delete))878            })879            .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))880            .collect_vec();881        assert_eq!(882            expected_conflicts, found_conflicts,883            "rename conflicts mismatch: {source_change:#?}"884        );885    }886887    fn check_expect(888        new_name: &str,889        #[rust_analyzer::rust_fixture] ra_fixture: &str,890        expect: Expect,891    ) {892        let (analysis, position) = fixture::position(ra_fixture);893        let source_change = analysis894            .rename(position, new_name, &TEST_CONFIG)895            .unwrap()896            .expect("Expect returned a RenameError");897        expect.assert_eq(&filter_expect(source_change))898    }899900    fn check_expect_will_rename_file(901        new_name: &str,902        #[rust_analyzer::rust_fixture] ra_fixture: &str,903        expect: Expect,904    ) {905        let (analysis, position) = fixture::position(ra_fixture);906        let source_change = analysis907            .will_rename_file(position.file_id, new_name, &TEST_CONFIG)908            .unwrap()909            .expect("Expect returned a RenameError");910        expect.assert_eq(&filter_expect(source_change))911    }912913    fn check_prepare(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {914        let (analysis, position) = fixture::position(ra_fixture);915        let result = analysis916            .prepare_rename(position)917            .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {err}"));918        match result {919            Ok(RangeInfo { range, info: () }) => {920                let source = analysis.file_text(position.file_id).unwrap();921                expect.assert_eq(&format!("{range:?}: {}", &source[range]))922            }923            Err(RenameError(err)) => expect.assert_eq(&err),924        };925    }926927    fn filter_expect(source_change: SourceChange) -> String {928        let source_file_edits = source_change929            .source_file_edits930            .into_iter()931            .map(|(id, (text_edit, _))| (id, text_edit.into_iter().collect::<Vec<_>>()))932            .collect::<Vec<_>>();933934        format!(935            "source_file_edits: {:#?}\nfile_system_edits: {:#?}\n",936            source_file_edits, source_change.file_system_edits937        )938    }939940    #[test]941    fn rename_will_shadow() {942        check_conflicts(943            "new_name",944            r#"945fn foo() {946    let mut new_name = 123;947    let old_name$0 = 456;948     // ^^^^^^^^949    new_name = 789 + new_name;950}951        "#,952        );953    }954955    #[test]956    fn rename_will_be_shadowed() {957        check_conflicts(958            "new_name",959            r#"960fn foo() {961    let mut old_name$0 = 456;962         // ^^^^^^^^963    let new_name = 123;964    old_name = 789 + old_name;965 // ^^^^^^^^         ^^^^^^^^966}967        "#,968        );969    }970971    #[test]972    fn test_prepare_rename_namelikes() {973        check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);974        check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]);975        check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);976    }977978    #[test]979    fn test_prepare_rename_in_macro() {980        check_prepare(981            r"macro_rules! foo {982    ($ident:ident) => {983        pub struct $ident;984    }985}986foo!(Foo$0);",987            expect![[r#"83..86: Foo"#]],988        );989    }990991    #[test]992    fn test_prepare_rename_keyword() {993        check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);994    }995996    #[test]997    fn test_prepare_rename_tuple_field() {998        check_prepare(999            r#"1000struct Foo(i32);10011002fn baz() {1003    let mut x = Foo(4);1004    x.0$0 = 5;1005}1006"#,1007            expect![[r#"No references found at position"#]],1008        );1009    }10101011    #[test]1012    fn test_prepare_rename_builtin() {1013        check_prepare(1014            r#"1015fn foo() {1016    let x: i32$0 = 0;1017}1018"#,1019            expect![[r#"No references found at position"#]],1020        );1021    }10221023    #[test]1024    fn test_prepare_rename_self() {1025        check_prepare(1026            r#"1027struct Foo {}10281029impl Foo {1030    fn foo(self) -> Self$0 {1031        self1032    }1033}1034"#,1035            expect![[r#"No references found at position"#]],1036        );1037    }10381039    #[test]1040    fn test_rename_to_underscore() {1041        check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);1042    }10431044    #[test]1045    fn test_rename_to_raw_identifier() {1046        check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);1047    }10481049    #[test]1050    fn test_rename_to_invalid_identifier1() {1051        check(1052            "invalid!",1053            r#"fn main() { let i$0 = 1; }"#,1054            "error: Invalid name `invalid!`: not an identifier",1055        );1056    }10571058    #[test]1059    fn test_rename_to_invalid_identifier2() {1060        check(1061            "multiple tokens",1062            r#"fn main() { let i$0 = 1; }"#,1063            "error: Invalid name `multiple tokens`: not an identifier",1064        );1065    }10661067    #[test]1068    fn test_rename_to_invalid_identifier3() {1069        check(1070            "super",1071            r#"fn main() { let i$0 = 1; }"#,1072            "error: Invalid name `super`: cannot rename to a keyword",1073        );1074    }10751076    #[test]1077    fn test_rename_to_invalid_identifier_lifetime() {1078        cov_mark::check!(rename_not_an_ident_ref);1079        check(1080            "'foo",1081            r#"fn main() { let i$0 = 1; }"#,1082            "error: Invalid name `'foo`: not an identifier",1083        );1084    }10851086    #[test]1087    fn test_rename_to_invalid_identifier_lifetime2() {1088        check(1089            "_",1090            r#"fn main<'a>(_: &'a$0 ()) {}"#,1091            r#"error: Invalid name `_`: not a lifetime identifier"#,1092        );1093    }10941095    #[test]1096    fn test_rename_accepts_lifetime_without_apostrophe() {1097        check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#);1098    }10991100    #[test]1101    fn test_rename_to_underscore_invalid() {1102        cov_mark::check!(rename_underscore_multiple);1103        check(1104            "_",1105            r#"fn main(foo$0: ()) {foo;}"#,1106            "error: Cannot rename reference to `_` as it is being referenced multiple times",1107        );1108    }11091110    #[test]1111    fn test_rename_mod_invalid() {1112        check(1113            "'foo",1114            r#"mod foo$0 {}"#,1115            "error: Invalid name `'foo`: cannot rename module to 'foo",1116        );1117    }11181119    #[test]1120    fn test_rename_mod_invalid_raw_ident() {1121        check(1122            "r#self",1123            r#"mod foo$0 {}"#,1124            "error: Invalid name `self`: cannot rename module to self",1125        );1126    }11271128    #[test]1129    fn test_rename_for_local() {1130        check(1131            "k",1132            r#"1133fn main() {1134    let mut i = 1;1135    let j = 1;1136    i = i$0 + j;11371138    { i = 0; }11391140    i = 5;1141}1142"#,1143            r#"1144fn main() {1145    let mut k = 1;1146    let j = 1;1147    k = k + j;11481149    { k = 0; }11501151    k = 5;1152}1153"#,1154        );1155    }11561157    #[test]1158    fn test_rename_unresolved_reference() {1159        check(1160            "new_name",1161            r#"fn main() { let _ = unresolved_ref$0; }"#,1162            "error: No references found at position",1163        );1164    }11651166    #[test]1167    fn test_rename_macro_multiple_occurrences() {1168        check(1169            "Baaah",1170            r#"macro_rules! foo {1171    ($ident:ident) => {1172        const $ident: () = ();1173        struct $ident {}1174    };1175}11761177foo!($0Foo);1178const _: () = Foo;1179const _: Foo = Foo {};1180    "#,1181            r#"1182macro_rules! foo {1183    ($ident:ident) => {1184        const $ident: () = ();1185        struct $ident {}1186    };1187}11881189foo!(Baaah);1190const _: () = Baaah;1191const _: Baaah = Baaah {};1192    "#,1193        )1194    }11951196    #[test]1197    fn test_rename_for_macro_args() {1198        check(1199            "b",1200            r#"1201macro_rules! foo {($i:ident) => {$i} }1202fn main() {1203    let a$0 = "test";1204    foo!(a);1205}1206"#,1207            r#"1208macro_rules! foo {($i:ident) => {$i} }1209fn main() {1210    let b = "test";1211    foo!(b);1212}1213"#,1214        );1215    }12161217    #[test]1218    fn test_rename_for_macro_args_rev() {1219        check(1220            "b",1221            r#"1222macro_rules! foo {($i:ident) => {$i} }1223fn main() {1224    let a = "test";1225    foo!(a$0);1226}1227"#,1228            r#"1229macro_rules! foo {($i:ident) => {$i} }1230fn main() {1231    let b = "test";1232    foo!(b);1233}1234"#,1235        );1236    }12371238    #[test]1239    fn test_rename_for_macro_define_fn() {1240        check(1241            "bar",1242            r#"1243macro_rules! define_fn {($id:ident) => { fn $id{} }}1244define_fn!(foo);1245fn main() {1246    fo$0o();1247}1248"#,1249            r#"1250macro_rules! define_fn {($id:ident) => { fn $id{} }}1251define_fn!(bar);1252fn main() {1253    bar();1254}1255"#,1256        );1257    }12581259    #[test]1260    fn test_rename_for_macro_define_fn_rev() {1261        check(1262            "bar",1263            r#"1264macro_rules! define_fn {($id:ident) => { fn $id{} }}1265define_fn!(fo$0o);1266fn main() {1267    foo();1268}1269"#,1270            r#"1271macro_rules! define_fn {($id:ident) => { fn $id{} }}1272define_fn!(bar);1273fn main() {1274    bar();1275}1276"#,1277        );1278    }12791280    #[test]1281    fn test_rename_for_param_inside() {1282        check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);1283    }12841285    #[test]1286    fn test_rename_refs_for_fn_param() {1287        check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);1288    }12891290    #[test]1291    fn test_rename_for_mut_param() {1292        check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);1293    }12941295    #[test]1296    fn test_rename_struct_field() {1297        check(1298            "foo",1299            r#"1300struct Foo { field$0: i32 }13011302impl Foo {1303    fn new(i: i32) -> Self {1304        Self { field: i }1305    }1306}1307"#,1308            r#"1309struct Foo { foo: i32 }13101311impl Foo {1312    fn new(i: i32) -> Self {1313        Self { foo: i }1314    }1315}1316"#,1317        );1318    }13191320    #[test]1321    fn test_rename_field_in_field_shorthand() {1322        cov_mark::check!(test_rename_field_in_field_shorthand);1323        check(1324            "field",1325            r#"1326struct Foo { foo$0: i32 }13271328impl Foo {1329    fn new(foo: i32) -> Self {1330        Self { foo }1331    }1332}1333"#,1334            r#"1335struct Foo { field: i32 }13361337impl Foo {1338    fn new(foo: i32) -> Self {1339        Self { field: foo }1340    }1341}1342"#,1343        );1344    }13451346    #[test]1347    fn test_rename_local_in_field_shorthand() {1348        cov_mark::check!(test_rename_local_in_field_shorthand);1349        check(1350            "j",1351            r#"1352struct Foo { i: i32 }13531354impl Foo {1355    fn new(i$0: i32) -> Self {1356        Self { i }1357    }1358}1359"#,1360            r#"1361struct Foo { i: i32 }13621363impl Foo {1364    fn new(j: i32) -> Self {1365        Self { i: j }1366    }1367}1368"#,1369        );1370    }13711372    #[test]1373    fn test_field_shorthand_correct_struct() {1374        check(1375            "j",1376            r#"1377struct Foo { i$0: i32 }1378struct Bar { i: i32 }13791380impl Bar {1381    fn new(i: i32) -> Self {1382        Self { i }1383    }1384}1385"#,1386            r#"1387struct Foo { j: i32 }1388struct Bar { i: i32 }13891390impl Bar {1391    fn new(i: i32) -> Self {1392        Self { i }1393    }1394}1395"#,1396        );1397    }13981399    #[test]1400    fn test_shadow_local_for_struct_shorthand() {1401        check(1402            "j",1403            r#"1404struct Foo { i: i32 }14051406fn baz(i$0: i32) -> Self {1407     let x = Foo { i };1408     {1409         let i = 0;1410         Foo { i }1411     }1412}1413"#,1414            r#"1415struct Foo { i: i32 }14161417fn baz(j: i32) -> Self {1418     let x = Foo { i: j };1419     {1420         let i = 0;1421         Foo { i }1422     }1423}1424"#,1425        );1426    }14271428    #[test]1429    fn test_rename_mod() {1430        check_expect(1431            "foo2",1432            r#"1433//- /lib.rs1434mod bar;14351436//- /bar.rs1437mod foo$0;14381439//- /bar/foo.rs1440// empty1441"#,1442            expect![[r#"1443                source_file_edits: [1444                    (1445                        FileId(1446                            1,1447                        ),1448                        [1449                            Indel {1450                                insert: "foo2",1451                                delete: 4..7,1452                            },1453                        ],1454                    ),1455                ]1456                file_system_edits: [1457                    MoveFile {1458                        src: FileId(1459                            2,1460                        ),1461                        dst: AnchoredPathBuf {1462                            anchor: FileId(1463                                2,1464                            ),1465                            path: "foo2.rs",1466                        },1467                    },1468                ]1469            "#]],1470        );1471    }14721473    #[test]1474    fn test_rename_mod_in_use_tree() {1475        check_expect(1476            "quux",1477            r#"1478//- /main.rs1479pub mod foo;1480pub mod bar;1481fn main() {}14821483//- /foo.rs1484pub struct FooContent;14851486//- /bar.rs1487use crate::foo$0::FooContent;1488"#,1489            expect![[r#"1490                source_file_edits: [1491                    (1492                        FileId(1493                            0,1494                        ),1495                        [1496                            Indel {1497                                insert: "quux",1498                                delete: 8..11,1499                            },1500                        ],1501                    ),1502                    (1503                        FileId(1504                            2,1505                        ),1506                        [1507                            Indel {1508                                insert: "quux",1509                                delete: 11..14,1510                            },1511                        ],1512                    ),1513                ]1514                file_system_edits: [1515                    MoveFile {1516                        src: FileId(1517                            1,1518                        ),1519                        dst: AnchoredPathBuf {1520                            anchor: FileId(1521                                1,1522                            ),1523                            path: "quux.rs",1524                        },1525                    },1526                ]1527            "#]],1528        );1529    }15301531    #[test]1532    fn test_rename_mod_in_dir() {1533        check_expect(1534            "foo2",1535            r#"1536//- /lib.rs1537mod fo$0o;1538//- /foo/mod.rs1539// empty1540"#,1541            expect![[r#"1542                source_file_edits: [1543                    (1544                        FileId(1545                            0,1546                        ),1547                        [1548                            Indel {1549                                insert: "foo2",1550                                delete: 4..7,1551                            },1552                        ],1553                    ),1554                ]1555                file_system_edits: [1556                    MoveDir {1557                        src: AnchoredPathBuf {1558                            anchor: FileId(1559                                1,1560                            ),1561                            path: "../foo",1562                        },1563                        src_id: FileId(1564                            1,1565                        ),1566                        dst: AnchoredPathBuf {1567                            anchor: FileId(1568                                1,1569                            ),1570                            path: "../foo2",1571                        },1572                    },1573                ]1574            "#]],1575        );1576    }15771578    #[test]1579    fn test_rename_unusually_nested_mod() {1580        check_expect(1581            "bar",1582            r#"1583//- /lib.rs1584mod outer { mod fo$0o; }15851586//- /outer/foo.rs1587// empty1588"#,1589            expect![[r#"1590                source_file_edits: [1591                    (1592                        FileId(1593                            0,1594                        ),1595                        [1596                            Indel {1597                                insert: "bar",1598                                delete: 16..19,1599                            },1600                        ],1601                    ),1602                ]1603                file_system_edits: [1604                    MoveFile {1605                        src: FileId(1606                            1,1607                        ),1608                        dst: AnchoredPathBuf {1609                            anchor: FileId(1610                                1,1611                            ),1612                            path: "bar.rs",1613                        },1614                    },1615                ]1616            "#]],1617        );1618    }16191620    #[test]1621    fn test_module_rename_in_path() {1622        check(1623            "baz",1624            r#"1625mod $0foo {1626    pub use self::bar as qux;1627    pub fn bar() {}1628}16291630fn main() { foo::bar(); }1631"#,1632            r#"1633mod baz {1634    pub use self::bar as qux;1635    pub fn bar() {}1636}16371638fn main() { baz::bar(); }1639"#,1640        );1641    }16421643    #[test]1644    fn test_rename_mod_filename_and_path() {1645        check_expect(1646            "foo2",1647            r#"1648//- /lib.rs1649mod bar;1650fn f() {1651    bar::foo::fun()1652}16531654//- /bar.rs1655pub mod foo$0;16561657//- /bar/foo.rs1658// pub fn fun() {}1659"#,1660            expect![[r#"1661                source_file_edits: [1662                    (1663                        FileId(1664                            0,1665                        ),1666                        [1667                            Indel {1668                                insert: "foo2",1669                                delete: 27..30,1670                            },1671                        ],1672                    ),1673                    (1674                        FileId(1675                            1,1676                        ),1677                        [1678                            Indel {1679                                insert: "foo2",1680                                delete: 8..11,1681                            },1682                        ],1683                    ),1684                ]1685                file_system_edits: [1686                    MoveFile {1687                        src: FileId(1688                            2,1689                        ),1690                        dst: AnchoredPathBuf {1691                            anchor: FileId(1692                                2,1693                            ),1694                            path: "foo2.rs",1695                        },1696                    },1697                ]1698            "#]],1699        );1700    }17011702    #[test]1703    fn test_rename_mod_recursive() {1704        check_expect(1705            "foo2",1706            r#"1707//- /lib.rs1708mod foo$0;17091710//- /foo.rs1711mod bar;1712mod corge;17131714//- /foo/bar.rs1715mod qux;17161717//- /foo/bar/qux.rs1718mod quux;17191720//- /foo/bar/qux/quux/mod.rs1721// empty17221723//- /foo/corge.rs1724// empty1725"#,1726            expect![[r#"1727                source_file_edits: [1728                    (1729                        FileId(1730                            0,1731                        ),1732                        [1733                            Indel {1734                                insert: "foo2",1735                                delete: 4..7,1736                            },1737                        ],1738                    ),1739                ]1740                file_system_edits: [1741                    MoveFile {1742                        src: FileId(1743                            1,1744                        ),1745                        dst: AnchoredPathBuf {1746                            anchor: FileId(1747                                1,1748                            ),1749                            path: "foo2.rs",1750                        },1751                    },1752                    MoveDir {1753                        src: AnchoredPathBuf {1754                            anchor: FileId(1755                                1,1756                            ),1757                            path: "foo",1758                        },1759                        src_id: FileId(1760                            1,1761                        ),1762                        dst: AnchoredPathBuf {1763                            anchor: FileId(1764                                1,1765                            ),1766                            path: "foo2",1767                        },1768                    },1769                ]1770            "#]],1771        )1772    }1773    #[test]1774    fn test_rename_mod_ref_by_super() {1775        check(1776            "baz",1777            r#"1778        mod $0foo {1779        struct X;17801781        mod bar {1782            use super::X;1783        }1784    }1785            "#,1786            r#"1787        mod baz {1788        struct X;17891790        mod bar {1791            use super::X;1792        }1793    }1794            "#,1795        )1796    }17971798    #[test]1799    fn test_rename_mod_in_macro() {1800        check(1801            "bar",1802            r#"1803//- /foo.rs18041805//- /lib.rs1806macro_rules! submodule {1807    ($name:ident) => {1808        mod $name;1809    };1810}18111812submodule!($0foo);1813"#,1814            r#"1815macro_rules! submodule {1816    ($name:ident) => {1817        mod $name;1818    };1819}18201821submodule!(bar);1822"#,1823        )1824    }18251826    #[test]1827    fn test_rename_mod_for_crate_root() {1828        check_expect_will_rename_file(1829            "main",1830            r#"1831//- /lib.rs1832use crate::foo as bar;1833fn foo() {}1834mod bar$0;1835"#,1836            expect![[r#"1837                source_file_edits: []1838                file_system_edits: []1839            "#]],1840        )1841    }18421843    #[test]1844    fn test_rename_mod_to_raw_ident() {1845        check_expect(1846            "r#fn",1847            r#"1848//- /lib.rs1849mod foo$0;18501851fn main() { foo::bar::baz(); }18521853//- /foo.rs1854pub mod bar;18551856//- /foo/bar.rs1857pub fn baz() {}1858"#,1859            expect![[r#"1860                source_file_edits: [1861                    (1862                        FileId(1863                            0,1864                        ),1865                        [1866                            Indel {1867                                insert: "r#fn",1868                                delete: 4..7,1869                            },1870                            Indel {1871                                insert: "r#fn",1872                                delete: 22..25,1873                            },1874                        ],1875                    ),1876                ]1877                file_system_edits: [1878                    MoveFile {1879                        src: FileId(1880                            1,1881                        ),1882                        dst: AnchoredPathBuf {1883                            anchor: FileId(1884                                1,1885                            ),1886                            path: "fn.rs",1887                        },1888                    },1889                    MoveDir {1890                        src: AnchoredPathBuf {1891                            anchor: FileId(1892                                1,1893                            ),1894                            path: "foo",1895                        },1896                        src_id: FileId(1897                            1,1898                        ),1899                        dst: AnchoredPathBuf {1900                            anchor: FileId(1901                                1,1902                            ),1903                            path: "fn",1904                        },1905                    },1906                ]1907            "#]],1908        );1909    }19101911    #[test]1912    fn test_rename_mod_from_raw_ident() {1913        check_expect(1914            "foo",1915            r#"1916//- /lib.rs1917mod r#fn$0;19181919fn main() { r#fn::bar::baz(); }19201921//- /fn.rs1922pub mod bar;19231924//- /fn/bar.rs1925pub fn baz() {}1926"#,1927            expect![[r#"1928                source_file_edits: [1929                    (1930                        FileId(1931                            0,1932                        ),1933                        [1934                            Indel {1935                                insert: "foo",1936                                delete: 4..8,1937                            },1938                            Indel {1939                                insert: "foo",1940                                delete: 23..27,1941                            },1942                        ],1943                    ),1944                ]1945                file_system_edits: [1946                    MoveFile {1947                        src: FileId(1948                            1,1949                        ),1950                        dst: AnchoredPathBuf {1951                            anchor: FileId(1952                                1,1953                            ),1954                            path: "foo.rs",1955                        },1956                    },1957                    MoveDir {1958                        src: AnchoredPathBuf {1959                            anchor: FileId(1960                                1,1961                            ),1962                            path: "fn",1963                        },1964                        src_id: FileId(1965                            1,1966                        ),1967                        dst: AnchoredPathBuf {1968                            anchor: FileId(1969                                1,1970                            ),1971                            path: "foo",1972                        },1973                    },1974                ]1975            "#]],1976        );1977    }19781979    #[test]1980    fn test_rename_each_usage_gets_appropriate_rawness() {1981        check_expect(1982            "dyn",1983            r#"1984//- /a.rs crate:a edition:20151985pub fn foo() {}19861987//- /b.rs crate:b edition:2018 deps:a new_source_root:local1988fn bar() {1989    a::foo$0();1990}1991    "#,1992            expect![[r#"1993                source_file_edits: [1994                    (1995                        FileId(1996                            0,1997                        ),1998                        [1999                            Indel {2000                                insert: "dyn",

Code quality findings 33

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 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.