diff options
Diffstat (limited to 'crates/ide_db/src')
-rw-r--r-- | crates/ide_db/src/assists.rs | 136 | ||||
-rw-r--r-- | crates/ide_db/src/helpers.rs | 11 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/famous_defs_fixture.rs | 153 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use.rs | 78 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use/tests.rs | 172 | ||||
-rw-r--r-- | crates/ide_db/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/ide_db/src/path_transform.rs | 160 | ||||
-rw-r--r-- | crates/ide_db/src/rename.rs | 468 |
8 files changed, 963 insertions, 221 deletions
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/helpers.rs b/crates/ide_db/src/helpers.rs index 00900cdc2..d96028cbc 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs | |||
@@ -74,12 +74,19 @@ pub fn visit_file_defs( | |||
74 | /// somewhat similar to the known paths infra inside hir, but it different; We | 74 | /// somewhat similar to the known paths infra inside hir, but it different; We |
75 | /// want to make sure that IDE specific paths don't become interesting inside | 75 | /// want to make sure that IDE specific paths don't become interesting inside |
76 | /// the compiler itself as well. | 76 | /// the compiler itself as well. |
77 | /// | ||
78 | /// Note that, by default, rust-analyzer tests **do not** include core or std | ||
79 | /// libraries. If you are writing tests for functionality using [`FamousDefs`], | ||
80 | /// you'd want to include [minicore](test_utils::MiniCore) declaration at the | ||
81 | /// start of your tests: | ||
82 | /// | ||
83 | /// ``` | ||
84 | /// //- minicore: iterator, ord, derive | ||
85 | /// ``` | ||
77 | pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Crate>); | 86 | pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Crate>); |
78 | 87 | ||
79 | #[allow(non_snake_case)] | 88 | #[allow(non_snake_case)] |
80 | impl FamousDefs<'_, '_> { | 89 | impl FamousDefs<'_, '_> { |
81 | pub const FIXTURE: &'static str = include_str!("helpers/famous_defs_fixture.rs"); | ||
82 | |||
83 | pub fn std(&self) -> Option<Crate> { | 90 | pub fn std(&self) -> Option<Crate> { |
84 | self.find_crate("std") | 91 | self.find_crate("std") |
85 | } | 92 | } |
diff --git a/crates/ide_db/src/helpers/famous_defs_fixture.rs b/crates/ide_db/src/helpers/famous_defs_fixture.rs deleted file mode 100644 index 312851966..000000000 --- a/crates/ide_db/src/helpers/famous_defs_fixture.rs +++ /dev/null | |||
@@ -1,153 +0,0 @@ | |||
1 | //- /libcore.rs crate:core | ||
2 | //! Signatures of traits, types and functions from the core lib for use in tests. | ||
3 | pub mod cmp { | ||
4 | |||
5 | pub trait Ord { | ||
6 | fn cmp(&self, other: &Self) -> Ordering; | ||
7 | fn max(self, other: Self) -> Self; | ||
8 | fn min(self, other: Self) -> Self; | ||
9 | fn clamp(self, min: Self, max: Self) -> Self; | ||
10 | } | ||
11 | } | ||
12 | |||
13 | pub mod convert { | ||
14 | pub trait From<T> { | ||
15 | fn from(t: T) -> Self; | ||
16 | } | ||
17 | |||
18 | pub trait Into<T> { | ||
19 | pub fn into(self) -> T; | ||
20 | } | ||
21 | } | ||
22 | |||
23 | pub mod default { | ||
24 | pub trait Default { | ||
25 | fn default() -> Self; | ||
26 | } | ||
27 | } | ||
28 | |||
29 | pub mod iter { | ||
30 | pub use self::traits::{collect::IntoIterator, iterator::Iterator}; | ||
31 | mod traits { | ||
32 | pub(crate) mod iterator { | ||
33 | use crate::option::Option; | ||
34 | pub trait Iterator { | ||
35 | type Item; | ||
36 | fn next(&mut self) -> Option<Self::Item>; | ||
37 | fn by_ref(&mut self) -> &mut Self { | ||
38 | self | ||
39 | } | ||
40 | fn take(self, n: usize) -> crate::iter::Take<Self> { | ||
41 | crate::iter::Take { inner: self } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | impl<I: Iterator> Iterator for &mut I { | ||
46 | type Item = I::Item; | ||
47 | fn next(&mut self) -> Option<I::Item> { | ||
48 | (**self).next() | ||
49 | } | ||
50 | } | ||
51 | } | ||
52 | pub(crate) mod collect { | ||
53 | pub trait IntoIterator { | ||
54 | type Item; | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | |||
59 | pub use self::sources::*; | ||
60 | pub(crate) mod sources { | ||
61 | use super::Iterator; | ||
62 | use crate::option::Option::{self, *}; | ||
63 | pub struct Repeat<A> { | ||
64 | element: A, | ||
65 | } | ||
66 | |||
67 | pub fn repeat<T>(elt: T) -> Repeat<T> { | ||
68 | Repeat { element: elt } | ||
69 | } | ||
70 | |||
71 | impl<A> Iterator for Repeat<A> { | ||
72 | type Item = A; | ||
73 | |||
74 | fn next(&mut self) -> Option<A> { | ||
75 | None | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | pub use self::adapters::*; | ||
81 | pub(crate) mod adapters { | ||
82 | use super::Iterator; | ||
83 | use crate::option::Option::{self, *}; | ||
84 | pub struct Take<I> { | ||
85 | pub(crate) inner: I, | ||
86 | } | ||
87 | impl<I> Iterator for Take<I> | ||
88 | where | ||
89 | I: Iterator, | ||
90 | { | ||
91 | type Item = <I as Iterator>::Item; | ||
92 | fn next(&mut self) -> Option<<I as Iterator>::Item> { | ||
93 | None | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | pub mod ops { | ||
100 | #[lang = "fn"] | ||
101 | pub trait Fn<Args>: FnMut<Args> { | ||
102 | extern "rust-call" fn call(&self, args: Args) -> Self::Output; | ||
103 | } | ||
104 | |||
105 | #[lang = "fn_mut"] | ||
106 | pub trait FnMut<Args>: FnOnce<Args> { | ||
107 | extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output; | ||
108 | } | ||
109 | #[lang = "fn_once"] | ||
110 | pub trait FnOnce<Args> { | ||
111 | #[lang = "fn_once_output"] | ||
112 | type Output; | ||
113 | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; | ||
114 | } | ||
115 | |||
116 | #[lang = "deref"] | ||
117 | pub trait Deref { | ||
118 | type Target: ?Sized; | ||
119 | fn deref(&self) -> &Self::Target; | ||
120 | } | ||
121 | } | ||
122 | |||
123 | pub mod option { | ||
124 | pub enum Option<T> { | ||
125 | None, | ||
126 | Some(T), | ||
127 | } | ||
128 | } | ||
129 | |||
130 | pub mod prelude { | ||
131 | pub mod rust_2018 { | ||
132 | pub use crate::{ | ||
133 | cmp::Ord, | ||
134 | convert::{From, Into}, | ||
135 | default::Default, | ||
136 | iter::{IntoIterator, Iterator}, | ||
137 | ops::{Fn, FnMut, FnOnce}, | ||
138 | option::Option::{self, *}, | ||
139 | }; | ||
140 | } | ||
141 | } | ||
142 | #[prelude_import] | ||
143 | pub use prelude::rust_2018::*; | ||
144 | //- /libstd.rs crate:std deps:core | ||
145 | //! Signatures of traits, types and functions from the std lib for use in tests. | ||
146 | |||
147 | /// Docs for return_keyword | ||
148 | mod return_keyword {} | ||
149 | |||
150 | /// Docs for prim_str | ||
151 | mod prim_str {} | ||
152 | |||
153 | pub use core::ops; | ||
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index 10bbafe77..e6b4832e7 100644 --- a/crates/ide_db/src/helpers/insert_use.rs +++ b/crates/ide_db/src/helpers/insert_use.rs | |||
@@ -5,7 +5,7 @@ use hir::Semantics; | |||
5 | use syntax::{ | 5 | use syntax::{ |
6 | algo, | 6 | algo, |
7 | ast::{self, make, AstNode, AttrsOwner, ModuleItemOwner, PathSegmentKind, VisibilityOwner}, | 7 | ast::{self, make, AstNode, AttrsOwner, ModuleItemOwner, PathSegmentKind, VisibilityOwner}, |
8 | ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken, | 8 | match_ast, ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken, |
9 | }; | 9 | }; |
10 | 10 | ||
11 | use crate::{ | 11 | use crate::{ |
@@ -36,22 +36,39 @@ pub struct InsertUseConfig { | |||
36 | pub enforce_granularity: bool, | 36 | pub enforce_granularity: bool, |
37 | pub prefix_kind: PrefixKind, | 37 | pub prefix_kind: PrefixKind, |
38 | pub group: bool, | 38 | pub group: bool, |
39 | pub skip_glob_imports: bool, | ||
39 | } | 40 | } |
40 | 41 | ||
41 | #[derive(Debug, Clone)] | 42 | #[derive(Debug, Clone)] |
42 | pub enum ImportScope { | 43 | pub enum ImportScope { |
43 | File(ast::SourceFile), | 44 | File(ast::SourceFile), |
44 | Module(ast::ItemList), | 45 | Module(ast::ItemList), |
46 | Block(ast::BlockExpr), | ||
45 | } | 47 | } |
46 | 48 | ||
47 | impl ImportScope { | 49 | impl ImportScope { |
48 | pub fn from(syntax: SyntaxNode) -> Option<Self> { | 50 | fn from(syntax: SyntaxNode) -> Option<Self> { |
49 | if let Some(module) = ast::Module::cast(syntax.clone()) { | 51 | fn contains_cfg_attr(attrs: &dyn AttrsOwner) -> bool { |
50 | module.item_list().map(ImportScope::Module) | 52 | attrs |
51 | } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) { | 53 | .attrs() |
52 | this.map(ImportScope::File) | 54 | .any(|attr| attr.as_simple_call().map_or(false, |(ident, _)| ident == "cfg")) |
53 | } else { | 55 | } |
54 | ast::ItemList::cast(syntax).map(ImportScope::Module) | 56 | match_ast! { |
57 | match syntax { | ||
58 | ast::Module(module) => module.item_list().map(ImportScope::Module), | ||
59 | ast::SourceFile(file) => Some(ImportScope::File(file)), | ||
60 | ast::Fn(func) => contains_cfg_attr(&func).then(|| func.body().map(ImportScope::Block)).flatten(), | ||
61 | ast::Const(konst) => contains_cfg_attr(&konst).then(|| match konst.body()? { | ||
62 | ast::Expr::BlockExpr(block) => Some(block), | ||
63 | _ => None, | ||
64 | }).flatten().map(ImportScope::Block), | ||
65 | ast::Static(statik) => contains_cfg_attr(&statik).then(|| match statik.body()? { | ||
66 | ast::Expr::BlockExpr(block) => Some(block), | ||
67 | _ => None, | ||
68 | }).flatten().map(ImportScope::Block), | ||
69 | _ => None, | ||
70 | |||
71 | } | ||
55 | } | 72 | } |
56 | } | 73 | } |
57 | 74 | ||
@@ -72,6 +89,7 @@ impl ImportScope { | |||
72 | match self { | 89 | match self { |
73 | ImportScope::File(file) => file.syntax(), | 90 | ImportScope::File(file) => file.syntax(), |
74 | ImportScope::Module(item_list) => item_list.syntax(), | 91 | ImportScope::Module(item_list) => item_list.syntax(), |
92 | ImportScope::Block(block) => block.syntax(), | ||
75 | } | 93 | } |
76 | } | 94 | } |
77 | 95 | ||
@@ -79,6 +97,7 @@ impl ImportScope { | |||
79 | match self { | 97 | match self { |
80 | ImportScope::File(file) => ImportScope::File(file.clone_for_update()), | 98 | ImportScope::File(file) => ImportScope::File(file.clone_for_update()), |
81 | ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), | 99 | ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), |
100 | ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()), | ||
82 | } | 101 | } |
83 | } | 102 | } |
84 | 103 | ||
@@ -95,6 +114,7 @@ impl ImportScope { | |||
95 | let mut use_stmts = match self { | 114 | let mut use_stmts = match self { |
96 | ImportScope::File(f) => f.items(), | 115 | ImportScope::File(f) => f.items(), |
97 | ImportScope::Module(m) => m.items(), | 116 | ImportScope::Module(m) => m.items(), |
117 | ImportScope::Block(b) => b.items(), | ||
98 | } | 118 | } |
99 | .filter_map(use_stmt); | 119 | .filter_map(use_stmt); |
100 | let mut res = ImportGranularityGuess::Unknown; | 120 | let mut res = ImportGranularityGuess::Unknown; |
@@ -153,7 +173,7 @@ enum ImportGranularityGuess { | |||
153 | } | 173 | } |
154 | 174 | ||
155 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. | 175 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. |
156 | pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { | 176 | pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) { |
157 | let _p = profile::span("insert_use"); | 177 | let _p = profile::span("insert_use"); |
158 | let mut mb = match cfg.granularity { | 178 | let mut mb = match cfg.granularity { |
159 | ImportGranularity::Crate => Some(MergeBehavior::Crate), | 179 | ImportGranularity::Crate => Some(MergeBehavior::Crate), |
@@ -175,7 +195,10 @@ pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig | |||
175 | make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); | 195 | make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); |
176 | // merge into existing imports if possible | 196 | // merge into existing imports if possible |
177 | if let Some(mb) = mb { | 197 | if let Some(mb) = mb { |
178 | for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { | 198 | let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it)); |
199 | for existing_use in | ||
200 | scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter) | ||
201 | { | ||
179 | if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { | 202 | if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { |
180 | ted::replace(existing_use.syntax(), merged.syntax()); | 203 | ted::replace(existing_use.syntax(), merged.syntax()); |
181 | return; | 204 | return; |
@@ -315,28 +338,29 @@ fn insert_use_( | |||
315 | ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline()); | 338 | ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline()); |
316 | return; | 339 | return; |
317 | } | 340 | } |
318 | match scope { | 341 | let l_curly = match scope { |
319 | ImportScope::File(_) => { | 342 | ImportScope::File(_) => { |
320 | cov_mark::hit!(insert_group_empty_file); | 343 | cov_mark::hit!(insert_group_empty_file); |
321 | ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line()); | 344 | ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line()); |
322 | ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()) | 345 | ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()); |
346 | return; | ||
323 | } | 347 | } |
348 | // don't insert the imports before the item list/block expr's opening curly brace | ||
349 | ImportScope::Module(item_list) => item_list.l_curly_token(), | ||
324 | // don't insert the imports before the item list's opening curly brace | 350 | // don't insert the imports before the item list's opening curly brace |
325 | ImportScope::Module(item_list) => match item_list.l_curly_token() { | 351 | ImportScope::Block(block) => block.l_curly_token(), |
326 | Some(b) => { | 352 | }; |
327 | cov_mark::hit!(insert_group_empty_module); | 353 | match l_curly { |
328 | ted::insert(ted::Position::after(&b), make::tokens::single_newline()); | 354 | Some(b) => { |
329 | ted::insert(ted::Position::after(&b), use_item.syntax()); | 355 | cov_mark::hit!(insert_group_empty_module); |
330 | } | 356 | ted::insert(ted::Position::after(&b), make::tokens::single_newline()); |
331 | None => { | 357 | ted::insert(ted::Position::after(&b), use_item.syntax()); |
332 | // This should never happens, broken module syntax node | 358 | } |
333 | ted::insert( | 359 | None => { |
334 | ted::Position::first_child_of(scope_syntax), | 360 | // This should never happens, broken module syntax node |
335 | make::tokens::blank_line(), | 361 | ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line()); |
336 | ); | 362 | ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()); |
337 | ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()); | 363 | } |
338 | } | ||
339 | }, | ||
340 | } | 364 | } |
341 | } | 365 | } |
342 | 366 | ||
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs index 70b11bf81..01894630a 100644 --- a/crates/ide_db/src/helpers/insert_use/tests.rs +++ b/crates/ide_db/src/helpers/insert_use/tests.rs | |||
@@ -1,12 +1,63 @@ | |||
1 | use super::*; | 1 | use super::*; |
2 | 2 | ||
3 | use hir::PrefixKind; | 3 | use hir::PrefixKind; |
4 | use test_utils::assert_eq_text; | 4 | use test_utils::{assert_eq_text, extract_range_or_offset, CURSOR_MARKER}; |
5 | |||
6 | #[test] | ||
7 | fn respects_cfg_attr_fn() { | ||
8 | check( | ||
9 | r"bar::Bar", | ||
10 | r#" | ||
11 | #[cfg(test)] | ||
12 | fn foo() {$0} | ||
13 | "#, | ||
14 | r#" | ||
15 | #[cfg(test)] | ||
16 | fn foo() { | ||
17 | use bar::Bar; | ||
18 | } | ||
19 | "#, | ||
20 | ImportGranularity::Crate, | ||
21 | ); | ||
22 | } | ||
23 | |||
24 | #[test] | ||
25 | fn respects_cfg_attr_const() { | ||
26 | check( | ||
27 | r"bar::Bar", | ||
28 | r#" | ||
29 | #[cfg(test)] | ||
30 | const FOO: Bar = {$0}; | ||
31 | "#, | ||
32 | r#" | ||
33 | #[cfg(test)] | ||
34 | const FOO: Bar = { | ||
35 | use bar::Bar; | ||
36 | }; | ||
37 | "#, | ||
38 | ImportGranularity::Crate, | ||
39 | ); | ||
40 | } | ||
41 | |||
42 | #[test] | ||
43 | fn insert_skips_lone_glob_imports() { | ||
44 | check( | ||
45 | "use foo::baz::A", | ||
46 | r" | ||
47 | use foo::bar::*; | ||
48 | ", | ||
49 | r" | ||
50 | use foo::bar::*; | ||
51 | use foo::baz::A; | ||
52 | ", | ||
53 | ImportGranularity::Crate, | ||
54 | ); | ||
55 | } | ||
5 | 56 | ||
6 | #[test] | 57 | #[test] |
7 | fn insert_not_group() { | 58 | fn insert_not_group() { |
8 | cov_mark::check!(insert_no_grouping_last); | 59 | cov_mark::check!(insert_no_grouping_last); |
9 | check( | 60 | check_with_config( |
10 | "use external_crate2::bar::A", | 61 | "use external_crate2::bar::A", |
11 | r" | 62 | r" |
12 | use std::bar::B; | 63 | use std::bar::B; |
@@ -21,24 +72,32 @@ use crate::bar::A; | |||
21 | use self::bar::A; | 72 | use self::bar::A; |
22 | use super::bar::A; | 73 | use super::bar::A; |
23 | use external_crate2::bar::A;", | 74 | use external_crate2::bar::A;", |
24 | ImportGranularity::Item, | 75 | &InsertUseConfig { |
25 | false, | 76 | granularity: ImportGranularity::Item, |
26 | false, | 77 | enforce_granularity: true, |
78 | prefix_kind: PrefixKind::Plain, | ||
79 | group: false, | ||
80 | skip_glob_imports: true, | ||
81 | }, | ||
27 | ); | 82 | ); |
28 | } | 83 | } |
29 | 84 | ||
30 | #[test] | 85 | #[test] |
31 | fn insert_not_group_empty() { | 86 | fn insert_not_group_empty() { |
32 | cov_mark::check!(insert_no_grouping_last2); | 87 | cov_mark::check!(insert_no_grouping_last2); |
33 | check( | 88 | check_with_config( |
34 | "use external_crate2::bar::A", | 89 | "use external_crate2::bar::A", |
35 | r"", | 90 | r"", |
36 | r"use external_crate2::bar::A; | 91 | r"use external_crate2::bar::A; |
37 | 92 | ||
38 | ", | 93 | ", |
39 | ImportGranularity::Item, | 94 | &InsertUseConfig { |
40 | false, | 95 | granularity: ImportGranularity::Item, |
41 | false, | 96 | enforce_granularity: true, |
97 | prefix_kind: PrefixKind::Plain, | ||
98 | group: false, | ||
99 | skip_glob_imports: true, | ||
100 | }, | ||
42 | ); | 101 | ); |
43 | } | 102 | } |
44 | 103 | ||
@@ -277,13 +336,15 @@ fn insert_empty_module() { | |||
277 | cov_mark::check!(insert_group_empty_module); | 336 | cov_mark::check!(insert_group_empty_module); |
278 | check( | 337 | check( |
279 | "foo::bar", | 338 | "foo::bar", |
280 | "mod x {}", | 339 | r" |
281 | r"{ | 340 | mod x {$0} |
341 | ", | ||
342 | r" | ||
343 | mod x { | ||
282 | use foo::bar; | 344 | use foo::bar; |
283 | }", | 345 | } |
346 | ", | ||
284 | ImportGranularity::Item, | 347 | ImportGranularity::Item, |
285 | true, | ||
286 | true, | ||
287 | ) | 348 | ) |
288 | } | 349 | } |
289 | 350 | ||
@@ -511,13 +572,14 @@ use std::io; | |||
511 | } | 572 | } |
512 | 573 | ||
513 | #[test] | 574 | #[test] |
514 | #[ignore] // FIXME: Support this | ||
515 | fn split_out_merge() { | 575 | fn split_out_merge() { |
576 | // FIXME: This is suboptimal, we want to get `use std::fmt::{self, Result}` | ||
577 | // instead. | ||
516 | check_module( | 578 | check_module( |
517 | "std::fmt::Result", | 579 | "std::fmt::Result", |
518 | r"use std::{fmt, io};", | 580 | r"use std::{fmt, io};", |
519 | r"use std::fmt::{self, Result}; | 581 | r"use std::fmt::Result; |
520 | use std::io;", | 582 | use std::{fmt, io};", |
521 | ) | 583 | ) |
522 | } | 584 | } |
523 | 585 | ||
@@ -533,17 +595,35 @@ fn merge_groups_self() { | |||
533 | 595 | ||
534 | #[test] | 596 | #[test] |
535 | fn merge_mod_into_glob() { | 597 | fn merge_mod_into_glob() { |
536 | check_crate( | 598 | check_with_config( |
537 | "token::TokenKind", | 599 | "token::TokenKind", |
538 | r"use token::TokenKind::*;", | 600 | r"use token::TokenKind::*;", |
539 | r"use token::TokenKind::{*, self};", | 601 | r"use token::TokenKind::{*, self};", |
602 | &InsertUseConfig { | ||
603 | granularity: ImportGranularity::Crate, | ||
604 | enforce_granularity: true, | ||
605 | prefix_kind: PrefixKind::Plain, | ||
606 | group: false, | ||
607 | skip_glob_imports: false, | ||
608 | }, | ||
540 | ) | 609 | ) |
541 | // FIXME: have it emit `use token::TokenKind::{self, *}`? | 610 | // FIXME: have it emit `use token::TokenKind::{self, *}`? |
542 | } | 611 | } |
543 | 612 | ||
544 | #[test] | 613 | #[test] |
545 | fn merge_self_glob() { | 614 | fn merge_self_glob() { |
546 | check_crate("self", r"use self::*;", r"use self::{*, self};") | 615 | check_with_config( |
616 | "self", | ||
617 | r"use self::*;", | ||
618 | r"use self::{*, self};", | ||
619 | &InsertUseConfig { | ||
620 | granularity: ImportGranularity::Crate, | ||
621 | enforce_granularity: true, | ||
622 | prefix_kind: PrefixKind::Plain, | ||
623 | group: false, | ||
624 | skip_glob_imports: false, | ||
625 | }, | ||
626 | ) | ||
547 | // FIXME: have it emit `use {self, *}`? | 627 | // FIXME: have it emit `use {self, *}`? |
548 | } | 628 | } |
549 | 629 | ||
@@ -756,19 +836,24 @@ use foo::bar::qux; | |||
756 | ); | 836 | ); |
757 | } | 837 | } |
758 | 838 | ||
759 | fn check( | 839 | fn check_with_config( |
760 | path: &str, | 840 | path: &str, |
761 | ra_fixture_before: &str, | 841 | ra_fixture_before: &str, |
762 | ra_fixture_after: &str, | 842 | ra_fixture_after: &str, |
763 | granularity: ImportGranularity, | 843 | config: &InsertUseConfig, |
764 | module: bool, | ||
765 | group: bool, | ||
766 | ) { | 844 | ) { |
767 | let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); | 845 | let (text, pos) = if ra_fixture_before.contains(CURSOR_MARKER) { |
768 | if module { | 846 | let (range_or_offset, text) = extract_range_or_offset(ra_fixture_before); |
769 | syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone(); | 847 | (text, Some(range_or_offset)) |
770 | } | 848 | } else { |
771 | let file = super::ImportScope::from(syntax.clone_for_update()).unwrap(); | 849 | (ra_fixture_before.to_owned(), None) |
850 | }; | ||
851 | let syntax = ast::SourceFile::parse(&text).tree().syntax().clone_for_update(); | ||
852 | let file = pos | ||
853 | .and_then(|pos| syntax.token_at_offset(pos.expect_offset()).next()?.parent()) | ||
854 | .and_then(|it| super::ImportScope::find_insert_use_container(&it)) | ||
855 | .or_else(|| super::ImportScope::from(syntax)) | ||
856 | .unwrap(); | ||
772 | let path = ast::SourceFile::parse(&format!("use {};", path)) | 857 | let path = ast::SourceFile::parse(&format!("use {};", path)) |
773 | .tree() | 858 | .tree() |
774 | .syntax() | 859 | .syntax() |
@@ -776,30 +861,41 @@ fn check( | |||
776 | .find_map(ast::Path::cast) | 861 | .find_map(ast::Path::cast) |
777 | .unwrap(); | 862 | .unwrap(); |
778 | 863 | ||
779 | insert_use( | 864 | insert_use(&file, path, config); |
780 | &file, | 865 | let result = file.as_syntax_node().ancestors().last().unwrap().to_string(); |
866 | assert_eq_text!(ra_fixture_after, &result); | ||
867 | } | ||
868 | |||
869 | fn check( | ||
870 | path: &str, | ||
871 | ra_fixture_before: &str, | ||
872 | ra_fixture_after: &str, | ||
873 | granularity: ImportGranularity, | ||
874 | ) { | ||
875 | check_with_config( | ||
781 | path, | 876 | path, |
782 | InsertUseConfig { | 877 | ra_fixture_before, |
878 | ra_fixture_after, | ||
879 | &InsertUseConfig { | ||
783 | granularity, | 880 | granularity, |
784 | enforce_granularity: true, | 881 | enforce_granularity: true, |
785 | prefix_kind: PrefixKind::Plain, | 882 | prefix_kind: PrefixKind::Plain, |
786 | group, | 883 | group: true, |
884 | skip_glob_imports: true, | ||
787 | }, | 885 | }, |
788 | ); | 886 | ) |
789 | let result = file.as_syntax_node().to_string(); | ||
790 | assert_eq_text!(ra_fixture_after, &result); | ||
791 | } | 887 | } |
792 | 888 | ||
793 | fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 889 | fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
794 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Crate, false, true) | 890 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Crate) |
795 | } | 891 | } |
796 | 892 | ||
797 | fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 893 | fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
798 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Module, false, true) | 894 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Module) |
799 | } | 895 | } |
800 | 896 | ||
801 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 897 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { |
802 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item, false, true) | 898 | check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item) |
803 | } | 899 | } |
804 | 900 | ||
805 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { | 901 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { |
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs index 105607dca..bde8767dd 100644 --- a/crates/ide_db/src/lib.rs +++ b/crates/ide_db/src/lib.rs | |||
@@ -3,17 +3,21 @@ | |||
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; |
14 | pub mod traits; | 14 | pub mod traits; |
15 | pub mod call_info; | 15 | pub mod call_info; |
16 | pub mod helpers; | 16 | pub mod helpers; |
17 | pub mod path_transform; | ||
18 | |||
19 | pub mod search; | ||
20 | pub mod rename; | ||
17 | 21 | ||
18 | use std::{fmt, sync::Arc}; | 22 | use std::{fmt, sync::Arc}; |
19 | 23 | ||
diff --git a/crates/ide_db/src/path_transform.rs b/crates/ide_db/src/path_transform.rs new file mode 100644 index 000000000..f3d7aa920 --- /dev/null +++ b/crates/ide_db/src/path_transform.rs | |||
@@ -0,0 +1,160 @@ | |||
1 | //! See [`PathTransform`]. | ||
2 | |||
3 | use crate::helpers::mod_path_to_ast; | ||
4 | use hir::{HirDisplay, SemanticsScope}; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | use syntax::{ | ||
7 | ast::{self, AstNode}, | ||
8 | ted, | ||
9 | }; | ||
10 | |||
11 | /// `PathTransform` substitutes path in SyntaxNodes in bulk. | ||
12 | /// | ||
13 | /// This is mostly useful for IDE code generation. If you paste some existing | ||
14 | /// code into a new context (for example, to add method overrides to an `impl` | ||
15 | /// block), you generally want to appropriately qualify the names, and sometimes | ||
16 | /// you might want to substitute generic parameters as well: | ||
17 | /// | ||
18 | /// ``` | ||
19 | /// mod x { | ||
20 | /// pub struct A<V>; | ||
21 | /// pub trait T<U> { fn foo(&self, _: U) -> A<U>; } | ||
22 | /// } | ||
23 | /// | ||
24 | /// mod y { | ||
25 | /// use x::T; | ||
26 | /// | ||
27 | /// impl T<()> for () { | ||
28 | /// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`. | ||
29 | /// // But we want a slightly-modified version of it: | ||
30 | /// fn foo(&self, _: ()) -> x::A<()> {} | ||
31 | /// } | ||
32 | /// } | ||
33 | /// ``` | ||
34 | pub struct PathTransform<'a> { | ||
35 | pub subst: (hir::Trait, ast::Impl), | ||
36 | pub target_scope: &'a SemanticsScope<'a>, | ||
37 | pub source_scope: &'a SemanticsScope<'a>, | ||
38 | } | ||
39 | |||
40 | impl<'a> PathTransform<'a> { | ||
41 | pub fn apply(&self, item: ast::AssocItem) { | ||
42 | if let Some(ctx) = self.build_ctx() { | ||
43 | ctx.apply(item) | ||
44 | } | ||
45 | } | ||
46 | fn build_ctx(&self) -> Option<Ctx<'a>> { | ||
47 | let db = self.source_scope.db; | ||
48 | let target_module = self.target_scope.module()?; | ||
49 | let source_module = self.source_scope.module()?; | ||
50 | |||
51 | let substs = get_syntactic_substs(self.subst.1.clone()).unwrap_or_default(); | ||
52 | let generic_def: hir::GenericDef = self.subst.0.into(); | ||
53 | let substs_by_param: FxHashMap<_, _> = generic_def | ||
54 | .type_params(db) | ||
55 | .into_iter() | ||
56 | // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky | ||
57 | .skip(1) | ||
58 | // The actual list of trait type parameters may be longer than the one | ||
59 | // used in the `impl` block due to trailing default type parameters. | ||
60 | // For that case we extend the `substs` with an empty iterator so we | ||
61 | // can still hit those trailing values and check if they actually have | ||
62 | // a default type. If they do, go for that type from `hir` to `ast` so | ||
63 | // the resulting change can be applied correctly. | ||
64 | .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None))) | ||
65 | .filter_map(|(k, v)| match v { | ||
66 | Some(v) => Some((k, v)), | ||
67 | None => { | ||
68 | let default = k.default(db)?; | ||
69 | Some(( | ||
70 | k, | ||
71 | ast::make::ty(&default.display_source_code(db, source_module.into()).ok()?), | ||
72 | )) | ||
73 | } | ||
74 | }) | ||
75 | .collect(); | ||
76 | |||
77 | let res = Ctx { substs: substs_by_param, target_module, source_scope: self.source_scope }; | ||
78 | Some(res) | ||
79 | } | ||
80 | } | ||
81 | |||
82 | struct Ctx<'a> { | ||
83 | substs: FxHashMap<hir::TypeParam, ast::Type>, | ||
84 | target_module: hir::Module, | ||
85 | source_scope: &'a SemanticsScope<'a>, | ||
86 | } | ||
87 | |||
88 | impl<'a> Ctx<'a> { | ||
89 | fn apply(&self, item: ast::AssocItem) { | ||
90 | for event in item.syntax().preorder() { | ||
91 | let node = match event { | ||
92 | syntax::WalkEvent::Enter(_) => continue, | ||
93 | syntax::WalkEvent::Leave(it) => it, | ||
94 | }; | ||
95 | if let Some(path) = ast::Path::cast(node.clone()) { | ||
96 | self.transform_path(path); | ||
97 | } | ||
98 | } | ||
99 | } | ||
100 | fn transform_path(&self, path: ast::Path) -> Option<()> { | ||
101 | if path.qualifier().is_some() { | ||
102 | return None; | ||
103 | } | ||
104 | if path.segment().and_then(|s| s.param_list()).is_some() { | ||
105 | // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway | ||
106 | return None; | ||
107 | } | ||
108 | |||
109 | let resolution = self.source_scope.speculative_resolve(&path)?; | ||
110 | |||
111 | match resolution { | ||
112 | hir::PathResolution::TypeParam(tp) => { | ||
113 | if let Some(subst) = self.substs.get(&tp) { | ||
114 | ted::replace(path.syntax(), subst.clone_subtree().clone_for_update().syntax()) | ||
115 | } | ||
116 | } | ||
117 | hir::PathResolution::Def(def) => { | ||
118 | let found_path = | ||
119 | self.target_module.find_use_path(self.source_scope.db.upcast(), def)?; | ||
120 | let res = mod_path_to_ast(&found_path).clone_for_update(); | ||
121 | if let Some(args) = path.segment().and_then(|it| it.generic_arg_list()) { | ||
122 | if let Some(segment) = res.segment() { | ||
123 | let old = segment.get_or_create_generic_arg_list(); | ||
124 | ted::replace(old.syntax(), args.clone_subtree().syntax().clone_for_update()) | ||
125 | } | ||
126 | } | ||
127 | ted::replace(path.syntax(), res.syntax()) | ||
128 | } | ||
129 | hir::PathResolution::Local(_) | ||
130 | | hir::PathResolution::ConstParam(_) | ||
131 | | hir::PathResolution::SelfType(_) | ||
132 | | hir::PathResolution::Macro(_) | ||
133 | | hir::PathResolution::AssocItem(_) => (), | ||
134 | } | ||
135 | Some(()) | ||
136 | } | ||
137 | } | ||
138 | |||
139 | // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the | ||
140 | // trait ref, and then go from the types in the substs back to the syntax). | ||
141 | fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> { | ||
142 | let target_trait = impl_def.trait_()?; | ||
143 | let path_type = match target_trait { | ||
144 | ast::Type::PathType(path) => path, | ||
145 | _ => return None, | ||
146 | }; | ||
147 | let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?; | ||
148 | |||
149 | let mut result = Vec::new(); | ||
150 | for generic_arg in generic_arg_list.generic_args() { | ||
151 | match generic_arg { | ||
152 | ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?), | ||
153 | ast::GenericArg::AssocTypeArg(_) | ||
154 | | ast::GenericArg::LifetimeArg(_) | ||
155 | | ast::GenericArg::ConstArg(_) => (), | ||
156 | } | ||
157 | } | ||
158 | |||
159 | Some(result) | ||
160 | } | ||
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 | } | ||