diff options
Diffstat (limited to 'crates/ide_db')
-rw-r--r-- | crates/ide_db/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ide_db/src/assists.rs | 136 | ||||
-rw-r--r-- | crates/ide_db/src/call_info.rs | 2 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/import_assets.rs | 4 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use/tests.rs | 7 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/merge_imports.rs | 2 | ||||
-rw-r--r-- | crates/ide_db/src/lib.rs | 5 | ||||
-rw-r--r-- | crates/ide_db/src/rename.rs | 468 | ||||
-rw-r--r-- | crates/ide_db/src/search.rs | 16 |
9 files changed, 624 insertions, 18 deletions
diff --git a/crates/ide_db/Cargo.toml b/crates/ide_db/Cargo.toml index 6229996ec..e219c577a 100644 --- a/crates/ide_db/Cargo.toml +++ b/crates/ide_db/Cargo.toml | |||
@@ -10,7 +10,7 @@ edition = "2018" | |||
10 | doctest = false | 10 | doctest = false |
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | cov-mark = { version = "1.1", features = ["thread-local"] } | 13 | cov-mark = "2.0.0-pre.1" |
14 | log = "0.4.8" | 14 | log = "0.4.8" |
15 | rayon = "1.5.0" | 15 | rayon = "1.5.0" |
16 | fst = { version = "0.4", default-features = false } | 16 | fst = { version = "0.4", default-features = false } |
diff --git a/crates/ide_db/src/assists.rs b/crates/ide_db/src/assists.rs new file mode 100644 index 000000000..7881d8369 --- /dev/null +++ b/crates/ide_db/src/assists.rs | |||
@@ -0,0 +1,136 @@ | |||
1 | //! This module defines the `Assist` data structure. The actual assist live in | ||
2 | //! the `ide_assists` downstream crate. We want to define the data structures in | ||
3 | //! this low-level crate though, because `ide_diagnostics` also need them | ||
4 | //! (fixits for diagnostics and assists are the same thing under the hood). We | ||
5 | //! want to compile `ide_assists` and `ide_diagnostics` in parallel though, so | ||
6 | //! we pull the common definitions upstream, to this crate. | ||
7 | |||
8 | use std::str::FromStr; | ||
9 | |||
10 | use syntax::TextRange; | ||
11 | |||
12 | use crate::{label::Label, source_change::SourceChange}; | ||
13 | |||
14 | #[derive(Debug, Clone)] | ||
15 | pub struct Assist { | ||
16 | pub id: AssistId, | ||
17 | /// Short description of the assist, as shown in the UI. | ||
18 | pub label: Label, | ||
19 | pub group: Option<GroupLabel>, | ||
20 | /// Target ranges are used to sort assists: the smaller the target range, | ||
21 | /// the more specific assist is, and so it should be sorted first. | ||
22 | pub target: TextRange, | ||
23 | /// Computing source change sometimes is much more costly then computing the | ||
24 | /// other fields. Additionally, the actual change is not required to show | ||
25 | /// the lightbulb UI, it only is needed when the user tries to apply an | ||
26 | /// assist. So, we compute it lazily: the API allow requesting assists with | ||
27 | /// or without source change. We could (and in fact, used to) distinguish | ||
28 | /// between resolved and unresolved assists at the type level, but this is | ||
29 | /// cumbersome, especially if you want to embed an assist into another data | ||
30 | /// structure, such as a diagnostic. | ||
31 | pub source_change: Option<SourceChange>, | ||
32 | } | ||
33 | |||
34 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
35 | pub enum AssistKind { | ||
36 | // FIXME: does the None variant make sense? Probably not. | ||
37 | None, | ||
38 | |||
39 | QuickFix, | ||
40 | Generate, | ||
41 | Refactor, | ||
42 | RefactorExtract, | ||
43 | RefactorInline, | ||
44 | RefactorRewrite, | ||
45 | } | ||
46 | |||
47 | impl AssistKind { | ||
48 | pub fn contains(self, other: AssistKind) -> bool { | ||
49 | if self == other { | ||
50 | return true; | ||
51 | } | ||
52 | |||
53 | match self { | ||
54 | AssistKind::None | AssistKind::Generate => true, | ||
55 | AssistKind::Refactor => match other { | ||
56 | AssistKind::RefactorExtract | ||
57 | | AssistKind::RefactorInline | ||
58 | | AssistKind::RefactorRewrite => true, | ||
59 | _ => false, | ||
60 | }, | ||
61 | _ => false, | ||
62 | } | ||
63 | } | ||
64 | |||
65 | pub fn name(&self) -> &str { | ||
66 | match self { | ||
67 | AssistKind::None => "None", | ||
68 | AssistKind::QuickFix => "QuickFix", | ||
69 | AssistKind::Generate => "Generate", | ||
70 | AssistKind::Refactor => "Refactor", | ||
71 | AssistKind::RefactorExtract => "RefactorExtract", | ||
72 | AssistKind::RefactorInline => "RefactorInline", | ||
73 | AssistKind::RefactorRewrite => "RefactorRewrite", | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | impl FromStr for AssistKind { | ||
79 | type Err = String; | ||
80 | |||
81 | fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
82 | match s { | ||
83 | "None" => Ok(AssistKind::None), | ||
84 | "QuickFix" => Ok(AssistKind::QuickFix), | ||
85 | "Generate" => Ok(AssistKind::Generate), | ||
86 | "Refactor" => Ok(AssistKind::Refactor), | ||
87 | "RefactorExtract" => Ok(AssistKind::RefactorExtract), | ||
88 | "RefactorInline" => Ok(AssistKind::RefactorInline), | ||
89 | "RefactorRewrite" => Ok(AssistKind::RefactorRewrite), | ||
90 | unknown => Err(format!("Unknown AssistKind: '{}'", unknown)), | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | |||
95 | /// Unique identifier of the assist, should not be shown to the user | ||
96 | /// directly. | ||
97 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
98 | pub struct AssistId(pub &'static str, pub AssistKind); | ||
99 | |||
100 | /// A way to control how many asssist to resolve during the assist resolution. | ||
101 | /// When an assist is resolved, its edits are calculated that might be costly to always do by default. | ||
102 | #[derive(Debug)] | ||
103 | pub enum AssistResolveStrategy { | ||
104 | /// No assists should be resolved. | ||
105 | None, | ||
106 | /// All assists should be resolved. | ||
107 | All, | ||
108 | /// Only a certain assist should be resolved. | ||
109 | Single(SingleResolve), | ||
110 | } | ||
111 | |||
112 | /// Hold the [`AssistId`] data of a certain assist to resolve. | ||
113 | /// The original id object cannot be used due to a `'static` lifetime | ||
114 | /// and the requirement to construct this struct dynamically during the resolve handling. | ||
115 | #[derive(Debug)] | ||
116 | pub struct SingleResolve { | ||
117 | /// The id of the assist. | ||
118 | pub assist_id: String, | ||
119 | // The kind of the assist. | ||
120 | pub assist_kind: AssistKind, | ||
121 | } | ||
122 | |||
123 | impl AssistResolveStrategy { | ||
124 | pub fn should_resolve(&self, id: &AssistId) -> bool { | ||
125 | match self { | ||
126 | AssistResolveStrategy::None => false, | ||
127 | AssistResolveStrategy::All => true, | ||
128 | AssistResolveStrategy::Single(single_resolve) => { | ||
129 | single_resolve.assist_id == id.0 && single_resolve.assist_kind == id.1 | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | |||
135 | #[derive(Clone, Debug)] | ||
136 | pub struct GroupLabel(pub String); | ||
diff --git a/crates/ide_db/src/call_info.rs b/crates/ide_db/src/call_info.rs index 933bcad55..4795e2565 100644 --- a/crates/ide_db/src/call_info.rs +++ b/crates/ide_db/src/call_info.rs | |||
@@ -162,7 +162,7 @@ impl ActiveParameter { | |||
162 | } | 162 | } |
163 | 163 | ||
164 | pub fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> { | 164 | pub fn at_token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Self> { |
165 | let (signature, active_parameter) = call_info_impl(&sema, token)?; | 165 | let (signature, active_parameter) = call_info_impl(sema, token)?; |
166 | 166 | ||
167 | let idx = active_parameter?; | 167 | let idx = active_parameter?; |
168 | let mut params = signature.params(sema.db); | 168 | let mut params = signature.params(sema.db); |
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs index ae52dd8bb..9634d872e 100644 --- a/crates/ide_db/src/helpers/import_assets.rs +++ b/crates/ide_db/src/helpers/import_assets.rs | |||
@@ -323,7 +323,7 @@ fn import_for_item( | |||
323 | } | 323 | } |
324 | 324 | ||
325 | let segment_import = | 325 | let segment_import = |
326 | find_import_for_segment(db, original_item_candidate, &unresolved_first_segment)?; | 326 | find_import_for_segment(db, original_item_candidate, unresolved_first_segment)?; |
327 | let trait_item_to_import = item_as_assoc(db, original_item) | 327 | let trait_item_to_import = item_as_assoc(db, original_item) |
328 | .and_then(|assoc| assoc.containing_trait(db)) | 328 | .and_then(|assoc| assoc.containing_trait(db)) |
329 | .map(|trait_| ItemInNs::from(ModuleDef::from(trait_))); | 329 | .map(|trait_| ItemInNs::from(ModuleDef::from(trait_))); |
@@ -383,7 +383,7 @@ fn find_import_for_segment( | |||
383 | original_item | 383 | original_item |
384 | } else { | 384 | } else { |
385 | let matching_module = | 385 | let matching_module = |
386 | module_with_segment_name(db, &unresolved_first_segment, original_item)?; | 386 | module_with_segment_name(db, unresolved_first_segment, original_item)?; |
387 | ItemInNs::from(ModuleDef::from(matching_module)) | 387 | ItemInNs::from(ModuleDef::from(matching_module)) |
388 | }) | 388 | }) |
389 | } | 389 | } |
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs index 70b11bf81..5a88ec742 100644 --- a/crates/ide_db/src/helpers/insert_use/tests.rs +++ b/crates/ide_db/src/helpers/insert_use/tests.rs | |||
@@ -511,13 +511,14 @@ use std::io; | |||
511 | } | 511 | } |
512 | 512 | ||
513 | #[test] | 513 | #[test] |
514 | #[ignore] // FIXME: Support this | ||
515 | fn split_out_merge() { | 514 | fn split_out_merge() { |
515 | // FIXME: This is suboptimal, we want to get `use std::fmt::{self, Result}` | ||
516 | // instead. | ||
516 | check_module( | 517 | check_module( |
517 | "std::fmt::Result", | 518 | "std::fmt::Result", |
518 | r"use std::{fmt, io};", | 519 | r"use std::{fmt, io};", |
519 | r"use std::fmt::{self, Result}; | 520 | r"use std::fmt::Result; |
520 | use std::io;", | 521 | use std::{fmt, io};", |
521 | ) | 522 | ) |
522 | } | 523 | } |
523 | 524 | ||
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs index 0dbabb44f..ec29476a4 100644 --- a/crates/ide_db/src/helpers/merge_imports.rs +++ b/crates/ide_db/src/helpers/merge_imports.rs | |||
@@ -124,7 +124,7 @@ fn recursive_merge( | |||
124 | .map(|tree_list| tree_list.use_trees().any(tree_is_self)) | 124 | .map(|tree_list| tree_list.use_trees().any(tree_is_self)) |
125 | .unwrap_or(false) | 125 | .unwrap_or(false) |
126 | }; | 126 | }; |
127 | match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) { | 127 | match (tree_contains_self(lhs_t), tree_contains_self(&rhs_t)) { |
128 | (true, false) => continue, | 128 | (true, false) => continue, |
129 | (false, true) => { | 129 | (false, true) => { |
130 | *lhs_t = rhs_t; | 130 | *lhs_t = rhs_t; |
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 105607dca..7bbd08d6f 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs | |||
@@ -3,11 +3,11 @@ | |||
3 | //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. | 3 | //! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. |
4 | 4 | ||
5 | mod apply_change; | 5 | mod apply_change; |
6 | pub mod assists; | ||
6 | pub mod label; | 7 | pub mod label; |
7 | pub mod line_index; | 8 | pub mod line_index; |
8 | pub mod symbol_index; | 9 | pub mod symbol_index; |
9 | pub mod defs; | 10 | pub mod defs; |
10 | pub mod search; | ||
11 | pub mod items_locator; | 11 | pub mod items_locator; |
12 | pub mod source_change; | 12 | pub mod source_change; |
13 | pub mod ty_filter; | 13 | pub mod ty_filter; |
@@ -15,6 +15,9 @@ pub mod traits; | |||
15 | pub mod call_info; | 15 | pub mod call_info; |
16 | pub mod helpers; | 16 | pub mod helpers; |
17 | 17 | ||
18 | pub mod search; | ||
19 | pub mod rename; | ||
20 | |||
18 | use std::{fmt, sync::Arc}; | 21 | use std::{fmt, sync::Arc}; |
19 | 22 | ||
20 | use base_db::{ | 23 | use base_db::{ |
diff --git a/crates/ide_db/src/rename.rs b/crates/ide_db/src/rename.rs new file mode 100644 index 000000000..643e67781 --- /dev/null +++ b/crates/ide_db/src/rename.rs | |||
@@ -0,0 +1,468 @@ | |||
1 | //! Rename infrastructure for rust-analyzer. It is used primarily for the | ||
2 | //! literal "rename" in the ide (look for tests there), but it is also available | ||
3 | //! as a general-purpose service. For example, it is used by the fix for the | ||
4 | //! "incorrect case" diagnostic. | ||
5 | //! | ||
6 | //! It leverages the [`crate::search`] functionality to find what needs to be | ||
7 | //! renamed. The actual renames are tricky -- field shorthands need special | ||
8 | //! attention, and, when renaming modules, you also want to rename files on the | ||
9 | //! file system. | ||
10 | //! | ||
11 | //! Another can of worms are macros: | ||
12 | //! | ||
13 | //! ``` | ||
14 | //! macro_rules! m { () => { fn f() {} } } | ||
15 | //! m!(); | ||
16 | //! fn main() { | ||
17 | //! f() // <- rename me | ||
18 | //! } | ||
19 | //! ``` | ||
20 | //! | ||
21 | //! The correct behavior in such cases is probably to show a dialog to the user. | ||
22 | //! Our current behavior is ¯\_(ツ)_/¯. | ||
23 | use std::fmt; | ||
24 | |||
25 | use base_db::{AnchoredPathBuf, FileId, FileRange}; | ||
26 | use either::Either; | ||
27 | use hir::{AsAssocItem, FieldSource, HasSource, InFile, ModuleSource, Semantics}; | ||
28 | use stdx::never; | ||
29 | use syntax::{ | ||
30 | ast::{self, NameOwner}, | ||
31 | lex_single_syntax_kind, AstNode, SyntaxKind, TextRange, T, | ||
32 | }; | ||
33 | use text_edit::TextEdit; | ||
34 | |||
35 | use crate::{ | ||
36 | defs::Definition, | ||
37 | search::FileReference, | ||
38 | source_change::{FileSystemEdit, SourceChange}, | ||
39 | RootDatabase, | ||
40 | }; | ||
41 | |||
42 | pub type Result<T, E = RenameError> = std::result::Result<T, E>; | ||
43 | |||
44 | #[derive(Debug)] | ||
45 | pub struct RenameError(pub String); | ||
46 | |||
47 | impl fmt::Display for RenameError { | ||
48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
49 | fmt::Display::fmt(&self.0, f) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | #[macro_export] | ||
54 | macro_rules! _format_err { | ||
55 | ($fmt:expr) => { RenameError(format!($fmt)) }; | ||
56 | ($fmt:expr, $($arg:tt)+) => { RenameError(format!($fmt, $($arg)+)) } | ||
57 | } | ||
58 | pub use _format_err as format_err; | ||
59 | |||
60 | #[macro_export] | ||
61 | macro_rules! _bail { | ||
62 | ($($tokens:tt)*) => { return Err(format_err!($($tokens)*)) } | ||
63 | } | ||
64 | pub use _bail as bail; | ||
65 | |||
66 | impl Definition { | ||
67 | pub fn rename(&self, sema: &Semantics<RootDatabase>, new_name: &str) -> Result<SourceChange> { | ||
68 | match *self { | ||
69 | Definition::ModuleDef(hir::ModuleDef::Module(module)) => { | ||
70 | rename_mod(sema, module, new_name) | ||
71 | } | ||
72 | Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => { | ||
73 | bail!("Cannot rename builtin type") | ||
74 | } | ||
75 | Definition::SelfType(_) => bail!("Cannot rename `Self`"), | ||
76 | def => rename_reference(sema, def, new_name), | ||
77 | } | ||
78 | } | ||
79 | |||
80 | /// Textual range of the identifier which will change when renaming this | ||
81 | /// `Definition`. Note that some definitions, like buitin types, can't be | ||
82 | /// renamed. | ||
83 | pub fn range_for_rename(self, sema: &Semantics<RootDatabase>) -> Option<FileRange> { | ||
84 | // FIXME: the `original_file_range` calls here are wrong -- they never fail, | ||
85 | // and _fall back_ to the entirety of the macro call. Such fall back is | ||
86 | // incorrect for renames. The safe behavior would be to return an error for | ||
87 | // such cases. The correct behavior would be to return an auxiliary list of | ||
88 | // "can't rename these occurrences in macros" items, and then show some kind | ||
89 | // of a dialog to the user. See: | ||
90 | cov_mark::hit!(macros_are_broken_lol); | ||
91 | |||
92 | let res = match self { | ||
93 | Definition::Macro(mac) => { | ||
94 | let src = mac.source(sema.db)?; | ||
95 | let name = match &src.value { | ||
96 | Either::Left(it) => it.name()?, | ||
97 | Either::Right(it) => it.name()?, | ||
98 | }; | ||
99 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
100 | } | ||
101 | Definition::Field(field) => { | ||
102 | let src = field.source(sema.db)?; | ||
103 | |||
104 | match &src.value { | ||
105 | FieldSource::Named(record_field) => { | ||
106 | let name = record_field.name()?; | ||
107 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
108 | } | ||
109 | FieldSource::Pos(_) => { | ||
110 | return None; | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | Definition::ModuleDef(module_def) => match module_def { | ||
115 | hir::ModuleDef::Module(module) => { | ||
116 | let src = module.declaration_source(sema.db)?; | ||
117 | let name = src.value.name()?; | ||
118 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
119 | } | ||
120 | hir::ModuleDef::Function(it) => name_range(it, sema)?, | ||
121 | hir::ModuleDef::Adt(adt) => match adt { | ||
122 | hir::Adt::Struct(it) => name_range(it, sema)?, | ||
123 | hir::Adt::Union(it) => name_range(it, sema)?, | ||
124 | hir::Adt::Enum(it) => name_range(it, sema)?, | ||
125 | }, | ||
126 | hir::ModuleDef::Variant(it) => name_range(it, sema)?, | ||
127 | hir::ModuleDef::Const(it) => name_range(it, sema)?, | ||
128 | hir::ModuleDef::Static(it) => name_range(it, sema)?, | ||
129 | hir::ModuleDef::Trait(it) => name_range(it, sema)?, | ||
130 | hir::ModuleDef::TypeAlias(it) => name_range(it, sema)?, | ||
131 | hir::ModuleDef::BuiltinType(_) => return None, | ||
132 | }, | ||
133 | Definition::SelfType(_) => return None, | ||
134 | Definition::Local(local) => { | ||
135 | let src = local.source(sema.db); | ||
136 | let name = match &src.value { | ||
137 | Either::Left(bind_pat) => bind_pat.name()?, | ||
138 | Either::Right(_) => return None, | ||
139 | }; | ||
140 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
141 | } | ||
142 | Definition::GenericParam(generic_param) => match generic_param { | ||
143 | hir::GenericParam::TypeParam(type_param) => { | ||
144 | let src = type_param.source(sema.db)?; | ||
145 | let name = match &src.value { | ||
146 | Either::Left(type_param) => type_param.name()?, | ||
147 | Either::Right(_trait) => return None, | ||
148 | }; | ||
149 | src.with_value(name.syntax()).original_file_range(sema.db) | ||
150 | } | ||
151 | hir::GenericParam::LifetimeParam(lifetime_param) => { | ||
152 | let src = lifetime_param.source(sema.db)?; | ||
153 | let lifetime = src.value.lifetime()?; | ||
154 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
155 | } | ||
156 | hir::GenericParam::ConstParam(it) => name_range(it, sema)?, | ||
157 | }, | ||
158 | Definition::Label(label) => { | ||
159 | let src = label.source(sema.db); | ||
160 | let lifetime = src.value.lifetime()?; | ||
161 | src.with_value(lifetime.syntax()).original_file_range(sema.db) | ||
162 | } | ||
163 | }; | ||
164 | return Some(res); | ||
165 | |||
166 | fn name_range<D>(def: D, sema: &Semantics<RootDatabase>) -> Option<FileRange> | ||
167 | where | ||
168 | D: HasSource, | ||
169 | D::Ast: ast::NameOwner, | ||
170 | { | ||
171 | let src = def.source(sema.db)?; | ||
172 | let name = src.value.name()?; | ||
173 | let res = src.with_value(name.syntax()).original_file_range(sema.db); | ||
174 | Some(res) | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | |||
179 | fn rename_mod( | ||
180 | sema: &Semantics<RootDatabase>, | ||
181 | module: hir::Module, | ||
182 | new_name: &str, | ||
183 | ) -> Result<SourceChange> { | ||
184 | if IdentifierKind::classify(new_name)? != IdentifierKind::Ident { | ||
185 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | ||
186 | } | ||
187 | |||
188 | let mut source_change = SourceChange::default(); | ||
189 | |||
190 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); | ||
191 | let file_id = file_id.original_file(sema.db); | ||
192 | if let ModuleSource::SourceFile(..) = def_source { | ||
193 | // mod is defined in path/to/dir/mod.rs | ||
194 | let path = if module.is_mod_rs(sema.db) { | ||
195 | format!("../{}/mod.rs", new_name) | ||
196 | } else { | ||
197 | format!("{}.rs", new_name) | ||
198 | }; | ||
199 | let dst = AnchoredPathBuf { anchor: file_id, path }; | ||
200 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | ||
201 | source_change.push_file_system_edit(move_file); | ||
202 | } | ||
203 | |||
204 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { | ||
205 | let file_id = file_id.original_file(sema.db); | ||
206 | match decl_source.name() { | ||
207 | Some(name) => source_change.insert_source_edit( | ||
208 | file_id, | ||
209 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), | ||
210 | ), | ||
211 | _ => never!("Module source node is missing a name"), | ||
212 | } | ||
213 | } | ||
214 | let def = Definition::ModuleDef(hir::ModuleDef::Module(module)); | ||
215 | let usages = def.usages(sema).all(); | ||
216 | let ref_edits = usages.iter().map(|(&file_id, references)| { | ||
217 | (file_id, source_edit_from_references(references, def, new_name)) | ||
218 | }); | ||
219 | source_change.extend(ref_edits); | ||
220 | |||
221 | Ok(source_change) | ||
222 | } | ||
223 | |||
224 | fn rename_reference( | ||
225 | sema: &Semantics<RootDatabase>, | ||
226 | mut def: Definition, | ||
227 | new_name: &str, | ||
228 | ) -> Result<SourceChange> { | ||
229 | let ident_kind = IdentifierKind::classify(new_name)?; | ||
230 | |||
231 | if matches!( | ||
232 | def, // is target a lifetime? | ||
233 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) | ||
234 | ) { | ||
235 | match ident_kind { | ||
236 | IdentifierKind::Ident | IdentifierKind::Underscore => { | ||
237 | cov_mark::hit!(rename_not_a_lifetime_ident_ref); | ||
238 | bail!("Invalid name `{}`: not a lifetime identifier", new_name); | ||
239 | } | ||
240 | IdentifierKind::Lifetime => cov_mark::hit!(rename_lifetime), | ||
241 | } | ||
242 | } else { | ||
243 | match (ident_kind, def) { | ||
244 | (IdentifierKind::Lifetime, _) => { | ||
245 | cov_mark::hit!(rename_not_an_ident_ref); | ||
246 | bail!("Invalid name `{}`: not an identifier", new_name); | ||
247 | } | ||
248 | (IdentifierKind::Ident, _) => cov_mark::hit!(rename_non_local), | ||
249 | (IdentifierKind::Underscore, _) => (), | ||
250 | } | ||
251 | } | ||
252 | |||
253 | def = match def { | ||
254 | // HACK: resolve trait impl items to the item def of the trait definition | ||
255 | // so that we properly resolve all trait item references | ||
256 | Definition::ModuleDef(mod_def) => mod_def | ||
257 | .as_assoc_item(sema.db) | ||
258 | .and_then(|it| it.containing_trait_impl(sema.db)) | ||
259 | .and_then(|it| { | ||
260 | it.items(sema.db).into_iter().find_map(|it| match (it, mod_def) { | ||
261 | (hir::AssocItem::Function(trait_func), hir::ModuleDef::Function(func)) | ||
262 | if trait_func.name(sema.db) == func.name(sema.db) => | ||
263 | { | ||
264 | Some(Definition::ModuleDef(hir::ModuleDef::Function(trait_func))) | ||
265 | } | ||
266 | (hir::AssocItem::Const(trait_konst), hir::ModuleDef::Const(konst)) | ||
267 | if trait_konst.name(sema.db) == konst.name(sema.db) => | ||
268 | { | ||
269 | Some(Definition::ModuleDef(hir::ModuleDef::Const(trait_konst))) | ||
270 | } | ||
271 | ( | ||
272 | hir::AssocItem::TypeAlias(trait_type_alias), | ||
273 | hir::ModuleDef::TypeAlias(type_alias), | ||
274 | ) if trait_type_alias.name(sema.db) == type_alias.name(sema.db) => { | ||
275 | Some(Definition::ModuleDef(hir::ModuleDef::TypeAlias(trait_type_alias))) | ||
276 | } | ||
277 | _ => None, | ||
278 | }) | ||
279 | }) | ||
280 | .unwrap_or(def), | ||
281 | _ => def, | ||
282 | }; | ||
283 | let usages = def.usages(sema).all(); | ||
284 | |||
285 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
286 | cov_mark::hit!(rename_underscore_multiple); | ||
287 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
288 | } | ||
289 | let mut source_change = SourceChange::default(); | ||
290 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
291 | (file_id, source_edit_from_references(references, def, new_name)) | ||
292 | })); | ||
293 | |||
294 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | ||
295 | source_change.insert_source_edit(file_id, edit); | ||
296 | Ok(source_change) | ||
297 | } | ||
298 | |||
299 | pub fn source_edit_from_references( | ||
300 | references: &[FileReference], | ||
301 | def: Definition, | ||
302 | new_name: &str, | ||
303 | ) -> TextEdit { | ||
304 | let mut edit = TextEdit::builder(); | ||
305 | for reference in references { | ||
306 | let (range, replacement) = match &reference.name { | ||
307 | // if the ranges differ then the node is inside a macro call, we can't really attempt | ||
308 | // to make special rewrites like shorthand syntax and such, so just rename the node in | ||
309 | // the macro input | ||
310 | ast::NameLike::NameRef(name_ref) | ||
311 | if name_ref.syntax().text_range() == reference.range => | ||
312 | { | ||
313 | source_edit_from_name_ref(name_ref, new_name, def) | ||
314 | } | ||
315 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
316 | source_edit_from_name(name, new_name) | ||
317 | } | ||
318 | _ => None, | ||
319 | } | ||
320 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
321 | edit.replace(range, replacement); | ||
322 | } | ||
323 | edit.finish() | ||
324 | } | ||
325 | |||
326 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
327 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
328 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
329 | return Some(( | ||
330 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
331 | [new_name, ": "].concat(), | ||
332 | )); | ||
333 | } | ||
334 | } | ||
335 | None | ||
336 | } | ||
337 | |||
338 | fn source_edit_from_name_ref( | ||
339 | name_ref: &ast::NameRef, | ||
340 | new_name: &str, | ||
341 | def: Definition, | ||
342 | ) -> Option<(TextRange, String)> { | ||
343 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | ||
344 | let rcf_name_ref = record_field.name_ref(); | ||
345 | let rcf_expr = record_field.expr(); | ||
346 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { | ||
347 | // field: init-expr, check if we can use a field init shorthand | ||
348 | (Some(field_name), Some(init)) => { | ||
349 | if field_name == *name_ref { | ||
350 | if init.text() == new_name { | ||
351 | cov_mark::hit!(test_rename_field_put_init_shorthand); | ||
352 | // same names, we can use a shorthand here instead. | ||
353 | // we do not want to erase attributes hence this range start | ||
354 | let s = field_name.syntax().text_range().start(); | ||
355 | let e = record_field.syntax().text_range().end(); | ||
356 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
357 | } | ||
358 | } else if init == *name_ref { | ||
359 | if field_name.text() == new_name { | ||
360 | cov_mark::hit!(test_rename_local_put_init_shorthand); | ||
361 | // same names, we can use a shorthand here instead. | ||
362 | // we do not want to erase attributes hence this range start | ||
363 | let s = field_name.syntax().text_range().start(); | ||
364 | let e = record_field.syntax().text_range().end(); | ||
365 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
366 | } | ||
367 | } | ||
368 | None | ||
369 | } | ||
370 | // init shorthand | ||
371 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
372 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
373 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
374 | cov_mark::hit!(test_rename_field_in_field_shorthand); | ||
375 | let s = name_ref.syntax().text_range().start(); | ||
376 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
377 | } | ||
378 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
379 | cov_mark::hit!(test_rename_local_in_field_shorthand); | ||
380 | let s = name_ref.syntax().text_range().end(); | ||
381 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
382 | } | ||
383 | _ => None, | ||
384 | } | ||
385 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
386 | let rcf_name_ref = record_field.name_ref(); | ||
387 | let rcf_pat = record_field.pat(); | ||
388 | match (rcf_name_ref, rcf_pat) { | ||
389 | // field: rename | ||
390 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
391 | // field name is being renamed | ||
392 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
393 | cov_mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
394 | // same names, we can use a shorthand here instead/ | ||
395 | // we do not want to erase attributes hence this range start | ||
396 | let s = field_name.syntax().text_range().start(); | ||
397 | let e = record_field.syntax().text_range().end(); | ||
398 | Some((TextRange::new(s, e), pat.to_string())) | ||
399 | } else { | ||
400 | None | ||
401 | } | ||
402 | } | ||
403 | _ => None, | ||
404 | } | ||
405 | } else { | ||
406 | None | ||
407 | } | ||
408 | } | ||
409 | |||
410 | fn source_edit_from_def( | ||
411 | sema: &Semantics<RootDatabase>, | ||
412 | def: Definition, | ||
413 | new_name: &str, | ||
414 | ) -> Result<(FileId, TextEdit)> { | ||
415 | let frange = def | ||
416 | .range_for_rename(sema) | ||
417 | .ok_or_else(|| format_err!("No identifier available to rename"))?; | ||
418 | |||
419 | let mut replacement_text = String::new(); | ||
420 | let mut repl_range = frange.range; | ||
421 | if let Definition::Local(local) = def { | ||
422 | if let Either::Left(pat) = local.source(sema.db).value { | ||
423 | if matches!( | ||
424 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
425 | Some(pat_field) if pat_field.name_ref().is_none() | ||
426 | ) { | ||
427 | replacement_text.push_str(": "); | ||
428 | replacement_text.push_str(new_name); | ||
429 | repl_range = TextRange::new( | ||
430 | pat.syntax().text_range().end(), | ||
431 | pat.syntax().text_range().end(), | ||
432 | ); | ||
433 | } | ||
434 | } | ||
435 | } | ||
436 | if replacement_text.is_empty() { | ||
437 | replacement_text.push_str(new_name); | ||
438 | } | ||
439 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
440 | Ok((frange.file_id, edit)) | ||
441 | } | ||
442 | |||
443 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
444 | pub enum IdentifierKind { | ||
445 | Ident, | ||
446 | Lifetime, | ||
447 | Underscore, | ||
448 | } | ||
449 | |||
450 | impl IdentifierKind { | ||
451 | pub fn classify(new_name: &str) -> Result<IdentifierKind> { | ||
452 | match lex_single_syntax_kind(new_name) { | ||
453 | Some(res) => match res { | ||
454 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
455 | (T![_], _) => Ok(IdentifierKind::Underscore), | ||
456 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
457 | Ok(IdentifierKind::Lifetime) | ||
458 | } | ||
459 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
460 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | ||
461 | } | ||
462 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | ||
463 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
464 | }, | ||
465 | None => bail!("Invalid name `{}`: not an identifier", new_name), | ||
466 | } | ||
467 | } | ||
468 | } | ||
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 8152630f5..a840e06a6 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -409,7 +409,7 @@ impl<'a> FindUsages<'a> { | |||
409 | if let Some(ast::NameLike::NameRef(name_ref)) = | 409 | if let Some(ast::NameLike::NameRef(name_ref)) = |
410 | sema.find_node_at_offset_with_descend(&tree, offset) | 410 | sema.find_node_at_offset_with_descend(&tree, offset) |
411 | { | 411 | { |
412 | if self.found_self_ty_name_ref(&self_ty, &name_ref, sink) { | 412 | if self.found_self_ty_name_ref(self_ty, &name_ref, sink) { |
413 | return; | 413 | return; |
414 | } | 414 | } |
415 | } | 415 | } |
@@ -424,7 +424,7 @@ impl<'a> FindUsages<'a> { | |||
424 | name_ref: &ast::NameRef, | 424 | name_ref: &ast::NameRef, |
425 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, | 425 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, |
426 | ) -> bool { | 426 | ) -> bool { |
427 | match NameRefClass::classify(self.sema, &name_ref) { | 427 | match NameRefClass::classify(self.sema, name_ref) { |
428 | Some(NameRefClass::Definition(Definition::SelfType(impl_))) | 428 | Some(NameRefClass::Definition(Definition::SelfType(impl_))) |
429 | if impl_.self_ty(self.sema.db) == *self_ty => | 429 | if impl_.self_ty(self.sema.db) == *self_ty => |
430 | { | 430 | { |
@@ -464,13 +464,13 @@ impl<'a> FindUsages<'a> { | |||
464 | name_ref: &ast::NameRef, | 464 | name_ref: &ast::NameRef, |
465 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, | 465 | sink: &mut dyn FnMut(FileId, FileReference) -> bool, |
466 | ) -> bool { | 466 | ) -> bool { |
467 | match NameRefClass::classify(self.sema, &name_ref) { | 467 | match NameRefClass::classify(self.sema, name_ref) { |
468 | Some(NameRefClass::Definition(def)) if def == self.def => { | 468 | Some(NameRefClass::Definition(def)) if def == self.def => { |
469 | let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); | 469 | let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); |
470 | let reference = FileReference { | 470 | let reference = FileReference { |
471 | range, | 471 | range, |
472 | name: ast::NameLike::NameRef(name_ref.clone()), | 472 | name: ast::NameLike::NameRef(name_ref.clone()), |
473 | access: reference_access(&def, &name_ref), | 473 | access: reference_access(&def, name_ref), |
474 | }; | 474 | }; |
475 | sink(file_id, reference) | 475 | sink(file_id, reference) |
476 | } | 476 | } |
@@ -480,7 +480,7 @@ impl<'a> FindUsages<'a> { | |||
480 | let reference = FileReference { | 480 | let reference = FileReference { |
481 | range, | 481 | range, |
482 | name: ast::NameLike::NameRef(name_ref.clone()), | 482 | name: ast::NameLike::NameRef(name_ref.clone()), |
483 | access: reference_access(&def, &name_ref), | 483 | access: reference_access(&def, name_ref), |
484 | }; | 484 | }; |
485 | sink(file_id, reference) | 485 | sink(file_id, reference) |
486 | } else { | 486 | } else { |
@@ -490,11 +490,9 @@ impl<'a> FindUsages<'a> { | |||
490 | Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { | 490 | Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { |
491 | let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); | 491 | let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); |
492 | let access = match self.def { | 492 | let access = match self.def { |
493 | Definition::Field(_) if field == self.def => { | 493 | Definition::Field(_) if field == self.def => reference_access(&field, name_ref), |
494 | reference_access(&field, &name_ref) | ||
495 | } | ||
496 | Definition::Local(l) if local == l => { | 494 | Definition::Local(l) if local == l => { |
497 | reference_access(&Definition::Local(local), &name_ref) | 495 | reference_access(&Definition::Local(local), name_ref) |
498 | } | 496 | } |
499 | _ => return false, | 497 | _ => return false, |
500 | }; | 498 | }; |