diff options
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r-- | crates/ide/src/references/rename.rs | 932 |
1 files changed, 537 insertions, 395 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 4df189c98..22ddeeae3 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -1,25 +1,23 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | use std::fmt::{self, Display}; | 2 | use std::fmt::{self, Display}; |
3 | 3 | ||
4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 4 | use either::Either; |
5 | use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; | ||
5 | use ide_db::{ | 6 | use ide_db::{ |
6 | base_db::{AnchoredPathBuf, FileId, FileRange}, | 7 | base_db::{AnchoredPathBuf, FileId}, |
7 | defs::{Definition, NameClass, NameRefClass}, | 8 | defs::{Definition, NameClass, NameRefClass}, |
8 | search::FileReference, | 9 | search::FileReference, |
9 | RootDatabase, | 10 | RootDatabase, |
10 | }; | 11 | }; |
12 | use stdx::never; | ||
11 | use syntax::{ | 13 | use syntax::{ |
12 | algo::find_node_at_offset, | ||
13 | ast::{self, NameOwner}, | 14 | ast::{self, NameOwner}, |
14 | lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T, | 15 | lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, |
15 | }; | 16 | }; |
16 | use test_utils::mark; | 17 | use test_utils::mark; |
17 | use text_edit::TextEdit; | 18 | use text_edit::TextEdit; |
18 | 19 | ||
19 | use crate::{ | 20 | use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; |
20 | FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, | ||
21 | TextRange, | ||
22 | }; | ||
23 | 21 | ||
24 | type RenameResult<T> = Result<T, RenameError>; | 22 | type RenameResult<T> = Result<T, RenameError>; |
25 | #[derive(Debug)] | 23 | #[derive(Debug)] |
@@ -40,6 +38,8 @@ macro_rules! bail { | |||
40 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | 38 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} |
41 | } | 39 | } |
42 | 40 | ||
41 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is | ||
42 | /// being targeted for a rename. | ||
43 | pub(crate) fn prepare_rename( | 43 | pub(crate) fn prepare_rename( |
44 | db: &RootDatabase, | 44 | db: &RootDatabase, |
45 | position: FilePosition, | 45 | position: FilePosition, |
@@ -47,20 +47,32 @@ pub(crate) fn prepare_rename( | |||
47 | let sema = Semantics::new(db); | 47 | let sema = Semantics::new(db); |
48 | let source_file = sema.parse(position.file_id); | 48 | let source_file = sema.parse(position.file_id); |
49 | let syntax = source_file.syntax(); | 49 | let syntax = source_file.syntax(); |
50 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 50 | let range = match &sema |
51 | rename_mod(&sema, position, module, "dummy") | 51 | .find_node_at_offset_with_descend(&syntax, position.offset) |
52 | } else { | 52 | .ok_or_else(|| format_err!("No references found at position"))? |
53 | let RangeInfo { range, .. } = find_all_refs(&sema, position)?; | 53 | { |
54 | Ok(RangeInfo::new(range, SourceChange::default())) | 54 | ast::NameLike::Name(it) => it.syntax(), |
55 | } | 55 | ast::NameLike::NameRef(it) => it.syntax(), |
56 | .map(|info| RangeInfo::new(info.range, ())) | 56 | ast::NameLike::Lifetime(it) => it.syntax(), |
57 | } | 57 | } |
58 | 58 | .text_range(); | |
59 | Ok(RangeInfo::new(range, ())) | ||
60 | } | ||
61 | |||
62 | // Feature: Rename | ||
63 | // | ||
64 | // Renames the item below the cursor and all of its references | ||
65 | // | ||
66 | // |=== | ||
67 | // | Editor | Shortcut | ||
68 | // | ||
69 | // | VS Code | kbd:[F2] | ||
70 | // |=== | ||
59 | pub(crate) fn rename( | 71 | pub(crate) fn rename( |
60 | db: &RootDatabase, | 72 | db: &RootDatabase, |
61 | position: FilePosition, | 73 | position: FilePosition, |
62 | new_name: &str, | 74 | new_name: &str, |
63 | ) -> RenameResult<RangeInfo<SourceChange>> { | 75 | ) -> RenameResult<SourceChange> { |
64 | let sema = Semantics::new(db); | 76 | let sema = Semantics::new(db); |
65 | rename_with_semantics(&sema, position, new_name) | 77 | rename_with_semantics(&sema, position, new_name) |
66 | } | 78 | } |
@@ -69,14 +81,14 @@ pub(crate) fn rename_with_semantics( | |||
69 | sema: &Semantics<RootDatabase>, | 81 | sema: &Semantics<RootDatabase>, |
70 | position: FilePosition, | 82 | position: FilePosition, |
71 | new_name: &str, | 83 | new_name: &str, |
72 | ) -> RenameResult<RangeInfo<SourceChange>> { | 84 | ) -> RenameResult<SourceChange> { |
73 | let source_file = sema.parse(position.file_id); | 85 | let source_file = sema.parse(position.file_id); |
74 | let syntax = source_file.syntax(); | 86 | let syntax = source_file.syntax(); |
75 | 87 | ||
76 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 88 | let def = find_definition(sema, syntax, position)?; |
77 | rename_mod(&sema, position, module, new_name) | 89 | match def { |
78 | } else { | 90 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), |
79 | rename_reference(&sema, position, new_name) | 91 | def => rename_reference(sema, def, new_name), |
80 | } | 92 | } |
81 | } | 93 | } |
82 | 94 | ||
@@ -87,12 +99,7 @@ pub(crate) fn will_rename_file( | |||
87 | ) -> Option<SourceChange> { | 99 | ) -> Option<SourceChange> { |
88 | let sema = Semantics::new(db); | 100 | let sema = Semantics::new(db); |
89 | let module = sema.to_module_def(file_id)?; | 101 | let module = sema.to_module_def(file_id)?; |
90 | 102 | let mut change = rename_mod(&sema, module, new_name_stem).ok()?; | |
91 | let decl = module.declaration_source(db)?; | ||
92 | let range = decl.value.name()?.syntax().text_range(); | ||
93 | |||
94 | let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() }; | ||
95 | let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info; | ||
96 | change.file_system_edits.clear(); | 103 | change.file_system_edits.clear(); |
97 | Some(change) | 104 | Some(change) |
98 | } | 105 | } |
@@ -124,179 +131,232 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | |||
124 | } | 131 | } |
125 | } | 132 | } |
126 | 133 | ||
127 | fn find_module_at_offset( | 134 | fn find_definition( |
128 | sema: &Semantics<RootDatabase>, | 135 | sema: &Semantics<RootDatabase>, |
129 | position: FilePosition, | ||
130 | syntax: &SyntaxNode, | 136 | syntax: &SyntaxNode, |
131 | ) -> Option<Module> { | ||
132 | let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; | ||
133 | |||
134 | let module = match_ast! { | ||
135 | match (ident.parent()) { | ||
136 | ast::NameRef(name_ref) => { | ||
137 | match NameRefClass::classify(sema, &name_ref)? { | ||
138 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
139 | _ => return None, | ||
140 | } | ||
141 | }, | ||
142 | ast::Name(name) => { | ||
143 | match NameClass::classify(&sema, &name)? { | ||
144 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
145 | _ => return None, | ||
146 | } | ||
147 | }, | ||
148 | _ => return None, | ||
149 | } | ||
150 | }; | ||
151 | |||
152 | Some(module) | ||
153 | } | ||
154 | |||
155 | fn find_all_refs( | ||
156 | sema: &Semantics<RootDatabase>, | ||
157 | position: FilePosition, | 137 | position: FilePosition, |
158 | ) -> RenameResult<RangeInfo<ReferenceSearchResult>> { | 138 | ) -> RenameResult<Definition> { |
159 | crate::references::find_all_refs(sema, position, None) | 139 | match sema |
160 | .ok_or_else(|| format_err!("No references found at position")) | 140 | .find_node_at_offset_with_descend(syntax, position.offset) |
141 | .ok_or_else(|| format_err!("No references found at position"))? | ||
142 | { | ||
143 | // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet | ||
144 | ast::NameLike::Name(name) | ||
145 | if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) => | ||
146 | { | ||
147 | bail!("Renaming aliases is currently unsupported") | ||
148 | } | ||
149 | ast::NameLike::Name(name) => { | ||
150 | NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db)) | ||
151 | } | ||
152 | ast::NameLike::NameRef(name_ref) => { | ||
153 | NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db)) | ||
154 | } | ||
155 | ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) | ||
156 | .map(|class| NameRefClass::referenced(class, sema.db)) | ||
157 | .or_else(|| { | ||
158 | NameClass::classify_lifetime(sema, &lifetime) | ||
159 | .map(|it| it.referenced_or_defined(sema.db)) | ||
160 | }), | ||
161 | } | ||
162 | .ok_or_else(|| format_err!("No references found at position")) | ||
161 | } | 163 | } |
162 | 164 | ||
163 | fn source_edit_from_references( | 165 | fn source_edit_from_references( |
164 | sema: &Semantics<RootDatabase>, | 166 | _sema: &Semantics<RootDatabase>, |
165 | file_id: FileId, | 167 | file_id: FileId, |
166 | references: &[FileReference], | 168 | references: &[FileReference], |
169 | def: Definition, | ||
167 | new_name: &str, | 170 | new_name: &str, |
168 | ) -> (FileId, TextEdit) { | 171 | ) -> (FileId, TextEdit) { |
169 | let mut edit = TextEdit::builder(); | 172 | let mut edit = TextEdit::builder(); |
170 | for reference in references { | 173 | for reference in references { |
171 | let mut replacement_text = String::new(); | 174 | let (range, replacement) = match &reference.name { |
172 | let range = match reference.kind { | 175 | // if the ranges differ then the node is inside a macro call, we can't really attempt |
173 | ReferenceKind::FieldShorthandForField => { | 176 | // to make special rewrites like shorthand syntax and such, so just rename the node in |
174 | mark::hit!(test_rename_struct_field_for_shorthand); | 177 | // the macro input |
175 | replacement_text.push_str(new_name); | 178 | ast::NameLike::NameRef(name_ref) |
176 | replacement_text.push_str(": "); | 179 | if name_ref.syntax().text_range() == reference.range => |
177 | TextRange::new(reference.range.start(), reference.range.start()) | 180 | { |
178 | } | 181 | source_edit_from_name_ref(name_ref, new_name, def) |
179 | ReferenceKind::FieldShorthandForLocal => { | ||
180 | mark::hit!(test_rename_local_for_field_shorthand); | ||
181 | replacement_text.push_str(": "); | ||
182 | replacement_text.push_str(new_name); | ||
183 | TextRange::new(reference.range.end(), reference.range.end()) | ||
184 | } | 182 | } |
185 | ReferenceKind::RecordFieldExprOrPat => { | 183 | ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => { |
186 | mark::hit!(test_rename_field_expr_pat); | 184 | source_edit_from_name(name, new_name) |
187 | replacement_text.push_str(new_name); | ||
188 | edit_text_range_for_record_field_expr_or_pat( | ||
189 | sema, | ||
190 | FileRange { file_id, range: reference.range }, | ||
191 | new_name, | ||
192 | ) | ||
193 | } | 185 | } |
194 | _ => { | 186 | _ => None, |
195 | replacement_text.push_str(new_name); | 187 | } |
196 | reference.range | 188 | .unwrap_or_else(|| (reference.range, new_name.to_string())); |
197 | } | 189 | edit.replace(range, replacement); |
198 | }; | ||
199 | edit.replace(range, replacement_text); | ||
200 | } | 190 | } |
201 | (file_id, edit.finish()) | 191 | (file_id, edit.finish()) |
202 | } | 192 | } |
203 | 193 | ||
204 | fn edit_text_range_for_record_field_expr_or_pat( | 194 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { |
205 | sema: &Semantics<RootDatabase>, | 195 | if let Some(_) = ast::RecordPatField::for_field_name(name) { |
206 | file_range: FileRange, | 196 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { |
197 | return Some(( | ||
198 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
199 | format!("{}: ", new_name), | ||
200 | )); | ||
201 | } | ||
202 | } | ||
203 | None | ||
204 | } | ||
205 | |||
206 | fn source_edit_from_name_ref( | ||
207 | name_ref: &ast::NameRef, | ||
207 | new_name: &str, | 208 | new_name: &str, |
208 | ) -> TextRange { | 209 | def: Definition, |
209 | let source_file = sema.parse(file_range.file_id); | 210 | ) -> Option<(TextRange, String)> { |
210 | let file_syntax = source_file.syntax(); | 211 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { |
211 | let original_range = file_range.range; | 212 | let rcf_name_ref = record_field.name_ref(); |
212 | 213 | let rcf_expr = record_field.expr(); | |
213 | syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range) | 214 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { |
214 | .and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) { | 215 | // field: init-expr, check if we can use a field init shorthand |
215 | Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()), | 216 | (Some(field_name), Some(init)) => { |
216 | _ => None, | 217 | if field_name == *name_ref { |
217 | }) | 218 | if init.text() == new_name { |
218 | .or_else(|| { | 219 | mark::hit!(test_rename_field_put_init_shorthand); |
219 | syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range) | 220 | // same names, we can use a shorthand here instead. |
220 | .and_then(|field_pat| match field_pat.pat() { | 221 | // we do not want to erase attributes hence this range start |
221 | Some(ast::Pat::IdentPat(pat)) | 222 | let s = field_name.syntax().text_range().start(); |
222 | if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) => | 223 | let e = record_field.syntax().text_range().end(); |
223 | { | 224 | return Some((TextRange::new(s, e), new_name.to_owned())); |
224 | Some(field_pat.syntax().text_range()) | ||
225 | } | 225 | } |
226 | _ => None, | 226 | } else if init == *name_ref { |
227 | }) | 227 | if field_name.text() == new_name { |
228 | }) | 228 | mark::hit!(test_rename_local_put_init_shorthand); |
229 | .unwrap_or(original_range) | 229 | // same names, we can use a shorthand here instead. |
230 | // we do not want to erase attributes hence this range start | ||
231 | let s = field_name.syntax().text_range().start(); | ||
232 | let e = record_field.syntax().text_range().end(); | ||
233 | return Some((TextRange::new(s, e), new_name.to_owned())); | ||
234 | } | ||
235 | } | ||
236 | None | ||
237 | } | ||
238 | // init shorthand | ||
239 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
240 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
241 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { | ||
242 | mark::hit!(test_rename_field_in_field_shorthand); | ||
243 | let s = name_ref.syntax().text_range().start(); | ||
244 | Some((TextRange::empty(s), format!("{}: ", new_name))) | ||
245 | } | ||
246 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { | ||
247 | mark::hit!(test_rename_local_in_field_shorthand); | ||
248 | let s = name_ref.syntax().text_range().end(); | ||
249 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
250 | } | ||
251 | _ => None, | ||
252 | } | ||
253 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
254 | let rcf_name_ref = record_field.name_ref(); | ||
255 | let rcf_pat = record_field.pat(); | ||
256 | match (rcf_name_ref, rcf_pat) { | ||
257 | // field: rename | ||
258 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
259 | // field name is being renamed | ||
260 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
261 | mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
262 | // same names, we can use a shorthand here instead/ | ||
263 | // we do not want to erase attributes hence this range start | ||
264 | let s = field_name.syntax().text_range().start(); | ||
265 | let e = record_field.syntax().text_range().end(); | ||
266 | Some((TextRange::new(s, e), pat.to_string())) | ||
267 | } else { | ||
268 | None | ||
269 | } | ||
270 | } | ||
271 | _ => None, | ||
272 | } | ||
273 | } else { | ||
274 | None | ||
275 | } | ||
230 | } | 276 | } |
231 | 277 | ||
232 | fn rename_mod( | 278 | fn rename_mod( |
233 | sema: &Semantics<RootDatabase>, | 279 | sema: &Semantics<RootDatabase>, |
234 | position: FilePosition, | ||
235 | module: Module, | 280 | module: Module, |
236 | new_name: &str, | 281 | new_name: &str, |
237 | ) -> RenameResult<RangeInfo<SourceChange>> { | 282 | ) -> RenameResult<SourceChange> { |
238 | if IdentifierKind::Ident != check_identifier(new_name)? { | 283 | if IdentifierKind::Ident != check_identifier(new_name)? { |
239 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | 284 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); |
240 | } | 285 | } |
241 | 286 | ||
242 | let mut source_change = SourceChange::default(); | 287 | let mut source_change = SourceChange::default(); |
243 | 288 | ||
244 | let src = module.definition_source(sema.db); | 289 | let InFile { file_id, value: def_source } = module.definition_source(sema.db); |
245 | let file_id = src.file_id.original_file(sema.db); | 290 | let file_id = file_id.original_file(sema.db); |
246 | match src.value { | 291 | if let ModuleSource::SourceFile(..) = def_source { |
247 | ModuleSource::SourceFile(..) => { | 292 | // mod is defined in path/to/dir/mod.rs |
248 | // mod is defined in path/to/dir/mod.rs | 293 | let path = if module.is_mod_rs(sema.db) { |
249 | let path = if module.is_mod_rs(sema.db) { | 294 | format!("../{}/mod.rs", new_name) |
250 | format!("../{}/mod.rs", new_name) | 295 | } else { |
251 | } else { | 296 | format!("{}.rs", new_name) |
252 | format!("{}.rs", new_name) | 297 | }; |
253 | }; | 298 | let dst = AnchoredPathBuf { anchor: file_id, path }; |
254 | let dst = AnchoredPathBuf { anchor: file_id, path }; | 299 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; |
255 | let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; | 300 | source_change.push_file_system_edit(move_file); |
256 | source_change.push_file_system_edit(move_file); | 301 | } |
257 | } | 302 | |
258 | ModuleSource::Module(..) => {} | 303 | if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) { |
259 | } | 304 | let file_id = file_id.original_file(sema.db); |
260 | 305 | match decl_source.name() { | |
261 | if let Some(src) = module.declaration_source(sema.db) { | 306 | Some(name) => source_change.insert_source_edit( |
262 | let file_id = src.file_id.original_file(sema.db); | 307 | file_id, |
263 | let name = src.value.name().unwrap(); | 308 | TextEdit::replace(name.syntax().text_range(), new_name.to_string()), |
264 | source_change.insert_source_edit( | 309 | ), |
265 | file_id, | 310 | _ => unreachable!(), |
266 | TextEdit::replace(name.syntax().text_range(), new_name.into()), | 311 | }; |
267 | ); | ||
268 | } | 312 | } |
269 | 313 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | |
270 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 314 | let usages = def.usages(sema).all(); |
271 | let ref_edits = refs.references().iter().map(|(&file_id, references)| { | 315 | let ref_edits = usages.iter().map(|(&file_id, references)| { |
272 | source_edit_from_references(sema, file_id, references, new_name) | 316 | source_edit_from_references(sema, file_id, references, def, new_name) |
273 | }); | 317 | }); |
274 | source_change.extend(ref_edits); | 318 | source_change.extend(ref_edits); |
275 | 319 | ||
276 | Ok(RangeInfo::new(range, source_change)) | 320 | Ok(source_change) |
277 | } | 321 | } |
278 | 322 | ||
279 | fn rename_to_self( | 323 | fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { |
280 | sema: &Semantics<RootDatabase>, | 324 | if never!(local.is_self(sema.db)) { |
281 | position: FilePosition, | 325 | bail!("rename_to_self invoked on self"); |
282 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 326 | } |
283 | let source_file = sema.parse(position.file_id); | 327 | |
284 | let syn = source_file.syntax(); | 328 | let fn_def = match local.parent(sema.db) { |
329 | hir::DefWithBody::Function(func) => func, | ||
330 | _ => bail!("Cannot rename non-param local to self"), | ||
331 | }; | ||
285 | 332 | ||
286 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) | 333 | // FIXME: reimplement this on the hir instead |
287 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) | 334 | // as of the time of this writing params in hir don't keep their names |
288 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; | 335 | let fn_ast = fn_def |
289 | let param_range = fn_ast | 336 | .source(sema.db) |
337 | .ok_or_else(|| format_err!("Cannot rename non-param local to self"))? | ||
338 | .value; | ||
339 | |||
340 | let first_param_range = fn_ast | ||
290 | .param_list() | 341 | .param_list() |
291 | .and_then(|p| p.params().next()) | 342 | .and_then(|p| p.params().next()) |
292 | .ok_or_else(|| format_err!("Method has no parameters"))? | 343 | .ok_or_else(|| format_err!("Method has no parameters"))? |
293 | .syntax() | 344 | .syntax() |
294 | .text_range(); | 345 | .text_range(); |
295 | if !param_range.contains(position.offset) { | 346 | let InFile { file_id, value: local_source } = local.source(sema.db); |
296 | bail!("Only the first parameter can be self"); | 347 | match local_source { |
348 | either::Either::Left(pat) | ||
349 | if !first_param_range.contains_range(pat.syntax().text_range()) => | ||
350 | { | ||
351 | bail!("Only the first parameter can be self"); | ||
352 | } | ||
353 | _ => (), | ||
297 | } | 354 | } |
298 | 355 | ||
299 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) | 356 | let impl_block = fn_ast |
357 | .syntax() | ||
358 | .ancestors() | ||
359 | .find_map(|node| ast::Impl::cast(node)) | ||
300 | .and_then(|def| sema.to_def(&def)) | 360 | .and_then(|def| sema.to_def(&def)) |
301 | .ok_or_else(|| format_err!("No impl block found for function"))?; | 361 | .ok_or_else(|| format_err!("No impl block found for function"))?; |
302 | if fn_def.self_param(sema.db).is_some() { | 362 | if fn_def.self_param(sema.db).is_some() { |
@@ -320,25 +380,21 @@ fn rename_to_self( | |||
320 | bail!("Parameter type differs from impl block type"); | 380 | bail!("Parameter type differs from impl block type"); |
321 | } | 381 | } |
322 | 382 | ||
323 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | 383 | let def = Definition::Local(local); |
324 | 384 | let usages = def.usages(sema).all(); | |
325 | let mut source_change = SourceChange::default(); | 385 | let mut source_change = SourceChange::default(); |
326 | source_change.extend(refs.references().iter().map(|(&file_id, references)| { | 386 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
327 | source_edit_from_references(sema, file_id, references, "self") | 387 | source_edit_from_references(sema, file_id, references, def, "self") |
328 | })); | 388 | })); |
329 | source_change.insert_source_edit( | 389 | source_change.insert_source_edit( |
330 | position.file_id, | 390 | file_id.original_file(sema.db), |
331 | TextEdit::replace(param_range, String::from(self_param)), | 391 | TextEdit::replace(first_param_range, String::from(self_param)), |
332 | ); | 392 | ); |
333 | 393 | ||
334 | Ok(RangeInfo::new(range, source_change)) | 394 | Ok(source_change) |
335 | } | 395 | } |
336 | 396 | ||
337 | fn text_edit_from_self_param( | 397 | fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> { |
338 | syn: &SyntaxNode, | ||
339 | self_param: &ast::SelfParam, | ||
340 | new_name: &str, | ||
341 | ) -> Option<TextEdit> { | ||
342 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { | 398 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { |
343 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | 399 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { |
344 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | 400 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); |
@@ -346,7 +402,7 @@ fn text_edit_from_self_param( | |||
346 | None | 402 | None |
347 | } | 403 | } |
348 | 404 | ||
349 | let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; | 405 | let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?; |
350 | let type_name = target_type_name(&impl_def)?; | 406 | let type_name = target_type_name(&impl_def)?; |
351 | 407 | ||
352 | let mut replacement_text = String::from(new_name); | 408 | let mut replacement_text = String::from(new_name); |
@@ -363,94 +419,119 @@ fn text_edit_from_self_param( | |||
363 | 419 | ||
364 | fn rename_self_to_param( | 420 | fn rename_self_to_param( |
365 | sema: &Semantics<RootDatabase>, | 421 | sema: &Semantics<RootDatabase>, |
366 | position: FilePosition, | 422 | local: hir::Local, |
367 | new_name: &str, | 423 | new_name: &str, |
368 | ident_kind: IdentifierKind, | 424 | identifier_kind: IdentifierKind, |
369 | range: TextRange, | 425 | ) -> RenameResult<SourceChange> { |
370 | refs: ReferenceSearchResult, | 426 | let (file_id, self_param) = match local.source(sema.db) { |
371 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 427 | InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param), |
372 | match ident_kind { | 428 | _ => { |
373 | IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), | 429 | never!(true, "rename_self_to_param invoked on a non-self local"); |
374 | IdentifierKind::ToSelf => { | 430 | bail!("rename_self_to_param invoked on a non-self local"); |
375 | // no-op | ||
376 | mark::hit!(rename_self_to_self); | ||
377 | return Ok(RangeInfo::new(range, SourceChange::default())); | ||
378 | } | 431 | } |
379 | _ => (), | 432 | }; |
380 | } | ||
381 | let source_file = sema.parse(position.file_id); | ||
382 | let syn = source_file.syntax(); | ||
383 | |||
384 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) | ||
385 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; | ||
386 | |||
387 | let mut source_change = SourceChange::default(); | ||
388 | if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) { | ||
389 | if self_param | ||
390 | .syntax() | ||
391 | .text_range() | ||
392 | .contains_range(refs.declaration().nav.focus_or_full_range()) | ||
393 | { | ||
394 | let edit = text_edit_from_self_param(syn, &self_param, new_name) | ||
395 | .ok_or_else(|| format_err!("No target type found"))?; | ||
396 | source_change.insert_source_edit(position.file_id, edit); | ||
397 | |||
398 | source_change.extend(refs.references().iter().map(|(&file_id, references)| { | ||
399 | source_edit_from_references(sema, file_id, &references, new_name) | ||
400 | })); | ||
401 | |||
402 | if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore | ||
403 | { | ||
404 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
405 | } | ||
406 | 433 | ||
407 | return Ok(RangeInfo::new(range, source_change)); | 434 | let def = Definition::Local(local); |
408 | } | 435 | let usages = def.usages(sema).all(); |
436 | let edit = text_edit_from_self_param(&self_param, new_name) | ||
437 | .ok_or_else(|| format_err!("No target type found"))?; | ||
438 | if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { | ||
439 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
409 | } | 440 | } |
410 | Err(format_err!("Method has no self param")) | 441 | let mut source_change = SourceChange::default(); |
442 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); | ||
443 | source_change.extend(usages.iter().map(|(&file_id, references)| { | ||
444 | source_edit_from_references(sema, file_id, &references, def, new_name) | ||
445 | })); | ||
446 | Ok(source_change) | ||
411 | } | 447 | } |
412 | 448 | ||
413 | fn rename_reference( | 449 | fn rename_reference( |
414 | sema: &Semantics<RootDatabase>, | 450 | sema: &Semantics<RootDatabase>, |
415 | position: FilePosition, | 451 | def: Definition, |
416 | new_name: &str, | 452 | new_name: &str, |
417 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 453 | ) -> RenameResult<SourceChange> { |
418 | let ident_kind = check_identifier(new_name)?; | 454 | let ident_kind = check_identifier(new_name)?; |
419 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; | ||
420 | 455 | ||
421 | match (ident_kind, &refs.declaration.kind) { | 456 | let def_is_lbl_or_lt = matches!( |
422 | (IdentifierKind::ToSelf, ReferenceKind::Lifetime) | 457 | def, |
423 | | (IdentifierKind::Underscore, ReferenceKind::Lifetime) | 458 | Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) |
424 | | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { | 459 | ); |
460 | match (ident_kind, def) { | ||
461 | (IdentifierKind::ToSelf, _) | ||
462 | | (IdentifierKind::Underscore, _) | ||
463 | | (IdentifierKind::Ident, _) | ||
464 | if def_is_lbl_or_lt => | ||
465 | { | ||
425 | mark::hit!(rename_not_a_lifetime_ident_ref); | 466 | mark::hit!(rename_not_a_lifetime_ident_ref); |
426 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) | 467 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
427 | } | 468 | } |
428 | (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), | 469 | (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime), |
429 | (IdentifierKind::Lifetime, _) => { | 470 | (IdentifierKind::Lifetime, _) => { |
430 | mark::hit!(rename_not_an_ident_ref); | 471 | mark::hit!(rename_not_an_ident_ref); |
431 | bail!("Invalid name `{}`: not an identifier", new_name) | 472 | bail!("Invalid name `{}`: not an identifier", new_name) |
432 | } | 473 | } |
433 | (_, ReferenceKind::SelfParam) => { | 474 | (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => { |
475 | // no-op | ||
476 | mark::hit!(rename_self_to_self); | ||
477 | return Ok(SourceChange::default()); | ||
478 | } | ||
479 | (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => { | ||
434 | mark::hit!(rename_self_to_param); | 480 | mark::hit!(rename_self_to_param); |
435 | return rename_self_to_param(sema, position, new_name, ident_kind, range, refs); | 481 | return rename_self_to_param(sema, local, new_name, ident_kind); |
436 | } | 482 | } |
437 | (IdentifierKind::ToSelf, _) => { | 483 | (IdentifierKind::ToSelf, Definition::Local(local)) => { |
438 | mark::hit!(rename_to_self); | 484 | mark::hit!(rename_to_self); |
439 | return rename_to_self(sema, position); | 485 | return rename_to_self(sema, local); |
440 | } | ||
441 | (IdentifierKind::Underscore, _) if !refs.references.is_empty() => { | ||
442 | mark::hit!(rename_underscore_multiple); | ||
443 | bail!("Cannot rename reference to `_` as it is being referenced multiple times") | ||
444 | } | 486 | } |
487 | (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
445 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | 488 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), |
446 | } | 489 | } |
447 | 490 | ||
491 | let usages = def.usages(sema).all(); | ||
492 | if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { | ||
493 | mark::hit!(rename_underscore_multiple); | ||
494 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
495 | } | ||
448 | let mut source_change = SourceChange::default(); | 496 | let mut source_change = SourceChange::default(); |
449 | source_change.extend(refs.into_iter().map(|(file_id, references)| { | 497 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
450 | source_edit_from_references(sema, file_id, &references, new_name) | 498 | source_edit_from_references(sema, file_id, &references, def, new_name) |
451 | })); | 499 | })); |
452 | 500 | ||
453 | Ok(RangeInfo::new(range, source_change)) | 501 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; |
502 | source_change.insert_source_edit(file_id, edit); | ||
503 | Ok(source_change) | ||
504 | } | ||
505 | |||
506 | fn source_edit_from_def( | ||
507 | sema: &Semantics<RootDatabase>, | ||
508 | def: Definition, | ||
509 | new_name: &str, | ||
510 | ) -> RenameResult<(FileId, TextEdit)> { | ||
511 | let nav = def.try_to_nav(sema.db).unwrap(); | ||
512 | |||
513 | let mut replacement_text = String::new(); | ||
514 | let mut repl_range = nav.focus_or_full_range(); | ||
515 | if let Definition::Local(local) = def { | ||
516 | if let Either::Left(pat) = local.source(sema.db).value { | ||
517 | if matches!( | ||
518 | pat.syntax().parent().and_then(ast::RecordPatField::cast), | ||
519 | Some(pat_field) if pat_field.name_ref().is_none() | ||
520 | ) { | ||
521 | replacement_text.push_str(": "); | ||
522 | replacement_text.push_str(new_name); | ||
523 | repl_range = TextRange::new( | ||
524 | pat.syntax().text_range().end(), | ||
525 | pat.syntax().text_range().end(), | ||
526 | ); | ||
527 | } | ||
528 | } | ||
529 | } | ||
530 | if replacement_text.is_empty() { | ||
531 | replacement_text.push_str(new_name); | ||
532 | } | ||
533 | let edit = TextEdit::replace(repl_range, replacement_text); | ||
534 | Ok((nav.file_id, edit)) | ||
454 | } | 535 | } |
455 | 536 | ||
456 | #[cfg(test)] | 537 | #[cfg(test)] |
@@ -472,7 +553,7 @@ mod tests { | |||
472 | Ok(source_change) => { | 553 | Ok(source_change) => { |
473 | let mut text_edit_builder = TextEdit::builder(); | 554 | let mut text_edit_builder = TextEdit::builder(); |
474 | let mut file_id: Option<FileId> = None; | 555 | let mut file_id: Option<FileId> = None; |
475 | for edit in source_change.info.source_file_edits { | 556 | for edit in source_change.source_file_edits { |
476 | file_id = Some(edit.0); | 557 | file_id = Some(edit.0); |
477 | for indel in edit.1.into_iter() { | 558 | for indel in edit.1.into_iter() { |
478 | text_edit_builder.replace(indel.delete, indel.insert); | 559 | text_edit_builder.replace(indel.delete, indel.insert); |
@@ -502,10 +583,8 @@ mod tests { | |||
502 | 583 | ||
503 | fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { | 584 | fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { |
504 | let (analysis, position) = fixture::position(ra_fixture); | 585 | let (analysis, position) = fixture::position(ra_fixture); |
505 | let source_change = analysis | 586 | let source_change = |
506 | .rename(position, new_name) | 587 | analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); |
507 | .unwrap() | ||
508 | .expect("Expect returned RangeInfo to be Some, but was None"); | ||
509 | expect.assert_debug_eq(&source_change) | 588 | expect.assert_debug_eq(&source_change) |
510 | } | 589 | } |
511 | 590 | ||
@@ -749,8 +828,8 @@ impl Foo { | |||
749 | } | 828 | } |
750 | 829 | ||
751 | #[test] | 830 | #[test] |
752 | fn test_rename_struct_field_for_shorthand() { | 831 | fn test_rename_field_in_field_shorthand() { |
753 | mark::check!(test_rename_struct_field_for_shorthand); | 832 | mark::check!(test_rename_field_in_field_shorthand); |
754 | check( | 833 | check( |
755 | "j", | 834 | "j", |
756 | r#" | 835 | r#" |
@@ -775,8 +854,8 @@ impl Foo { | |||
775 | } | 854 | } |
776 | 855 | ||
777 | #[test] | 856 | #[test] |
778 | fn test_rename_local_for_field_shorthand() { | 857 | fn test_rename_local_in_field_shorthand() { |
779 | mark::check!(test_rename_local_for_field_shorthand); | 858 | mark::check!(test_rename_local_in_field_shorthand); |
780 | check( | 859 | check( |
781 | "j", | 860 | "j", |
782 | r#" | 861 | r#" |
@@ -871,36 +950,33 @@ mod foo$0; | |||
871 | // empty | 950 | // empty |
872 | "#, | 951 | "#, |
873 | expect![[r#" | 952 | expect![[r#" |
874 | RangeInfo { | 953 | SourceChange { |
875 | range: 4..7, | 954 | source_file_edits: { |
876 | info: SourceChange { | 955 | FileId( |
877 | source_file_edits: { | 956 | 1, |
878 | FileId( | 957 | ): TextEdit { |
879 | 1, | 958 | indels: [ |
880 | ): TextEdit { | 959 | Indel { |
881 | indels: [ | 960 | insert: "foo2", |
882 | Indel { | 961 | delete: 4..7, |
883 | insert: "foo2", | 962 | }, |
884 | delete: 4..7, | 963 | ], |
885 | }, | ||
886 | ], | ||
887 | }, | ||
888 | }, | 964 | }, |
889 | file_system_edits: [ | 965 | }, |
890 | MoveFile { | 966 | file_system_edits: [ |
891 | src: FileId( | 967 | MoveFile { |
968 | src: FileId( | ||
969 | 2, | ||
970 | ), | ||
971 | dst: AnchoredPathBuf { | ||
972 | anchor: FileId( | ||
892 | 2, | 973 | 2, |
893 | ), | 974 | ), |
894 | dst: AnchoredPathBuf { | 975 | path: "foo2.rs", |
895 | anchor: FileId( | ||
896 | 2, | ||
897 | ), | ||
898 | path: "foo2.rs", | ||
899 | }, | ||
900 | }, | 976 | }, |
901 | ], | 977 | }, |
902 | is_snippet: false, | 978 | ], |
903 | }, | 979 | is_snippet: false, |
904 | } | 980 | } |
905 | "#]], | 981 | "#]], |
906 | ); | 982 | ); |
@@ -923,46 +999,43 @@ pub struct FooContent; | |||
923 | use crate::foo$0::FooContent; | 999 | use crate::foo$0::FooContent; |
924 | "#, | 1000 | "#, |
925 | expect![[r#" | 1001 | expect![[r#" |
926 | RangeInfo { | 1002 | SourceChange { |
927 | range: 11..14, | 1003 | source_file_edits: { |
928 | info: SourceChange { | 1004 | FileId( |
929 | source_file_edits: { | 1005 | 0, |
930 | FileId( | 1006 | ): TextEdit { |
931 | 0, | 1007 | indels: [ |
932 | ): TextEdit { | 1008 | Indel { |
933 | indels: [ | 1009 | insert: "quux", |
934 | Indel { | 1010 | delete: 8..11, |
935 | insert: "quux", | 1011 | }, |
936 | delete: 8..11, | 1012 | ], |
937 | }, | ||
938 | ], | ||
939 | }, | ||
940 | FileId( | ||
941 | 2, | ||
942 | ): TextEdit { | ||
943 | indels: [ | ||
944 | Indel { | ||
945 | insert: "quux", | ||
946 | delete: 11..14, | ||
947 | }, | ||
948 | ], | ||
949 | }, | ||
950 | }, | 1013 | }, |
951 | file_system_edits: [ | 1014 | FileId( |
952 | MoveFile { | 1015 | 2, |
953 | src: FileId( | 1016 | ): TextEdit { |
1017 | indels: [ | ||
1018 | Indel { | ||
1019 | insert: "quux", | ||
1020 | delete: 11..14, | ||
1021 | }, | ||
1022 | ], | ||
1023 | }, | ||
1024 | }, | ||
1025 | file_system_edits: [ | ||
1026 | MoveFile { | ||
1027 | src: FileId( | ||
1028 | 1, | ||
1029 | ), | ||
1030 | dst: AnchoredPathBuf { | ||
1031 | anchor: FileId( | ||
954 | 1, | 1032 | 1, |
955 | ), | 1033 | ), |
956 | dst: AnchoredPathBuf { | 1034 | path: "quux.rs", |
957 | anchor: FileId( | ||
958 | 1, | ||
959 | ), | ||
960 | path: "quux.rs", | ||
961 | }, | ||
962 | }, | 1035 | }, |
963 | ], | 1036 | }, |
964 | is_snippet: false, | 1037 | ], |
965 | }, | 1038 | is_snippet: false, |
966 | } | 1039 | } |
967 | "#]], | 1040 | "#]], |
968 | ); | 1041 | ); |
@@ -979,36 +1052,33 @@ mod fo$0o; | |||
979 | // empty | 1052 | // empty |
980 | "#, | 1053 | "#, |
981 | expect![[r#" | 1054 | expect![[r#" |
982 | RangeInfo { | 1055 | SourceChange { |
983 | range: 4..7, | 1056 | source_file_edits: { |
984 | info: SourceChange { | 1057 | FileId( |
985 | source_file_edits: { | 1058 | 0, |
986 | FileId( | 1059 | ): TextEdit { |
987 | 0, | 1060 | indels: [ |
988 | ): TextEdit { | 1061 | Indel { |
989 | indels: [ | 1062 | insert: "foo2", |
990 | Indel { | 1063 | delete: 4..7, |
991 | insert: "foo2", | 1064 | }, |
992 | delete: 4..7, | 1065 | ], |
993 | }, | ||
994 | ], | ||
995 | }, | ||
996 | }, | 1066 | }, |
997 | file_system_edits: [ | 1067 | }, |
998 | MoveFile { | 1068 | file_system_edits: [ |
999 | src: FileId( | 1069 | MoveFile { |
1070 | src: FileId( | ||
1071 | 1, | ||
1072 | ), | ||
1073 | dst: AnchoredPathBuf { | ||
1074 | anchor: FileId( | ||
1000 | 1, | 1075 | 1, |
1001 | ), | 1076 | ), |
1002 | dst: AnchoredPathBuf { | 1077 | path: "../foo2/mod.rs", |
1003 | anchor: FileId( | ||
1004 | 1, | ||
1005 | ), | ||
1006 | path: "../foo2/mod.rs", | ||
1007 | }, | ||
1008 | }, | 1078 | }, |
1009 | ], | 1079 | }, |
1010 | is_snippet: false, | 1080 | ], |
1011 | }, | 1081 | is_snippet: false, |
1012 | } | 1082 | } |
1013 | "#]], | 1083 | "#]], |
1014 | ); | 1084 | ); |
@@ -1026,36 +1096,33 @@ mod outer { mod fo$0o; } | |||
1026 | // empty | 1096 | // empty |
1027 | "#, | 1097 | "#, |
1028 | expect![[r#" | 1098 | expect![[r#" |
1029 | RangeInfo { | 1099 | SourceChange { |
1030 | range: 16..19, | 1100 | source_file_edits: { |
1031 | info: SourceChange { | 1101 | FileId( |
1032 | source_file_edits: { | 1102 | 0, |
1033 | FileId( | 1103 | ): TextEdit { |
1034 | 0, | 1104 | indels: [ |
1035 | ): TextEdit { | 1105 | Indel { |
1036 | indels: [ | 1106 | insert: "bar", |
1037 | Indel { | 1107 | delete: 16..19, |
1038 | insert: "bar", | 1108 | }, |
1039 | delete: 16..19, | 1109 | ], |
1040 | }, | ||
1041 | ], | ||
1042 | }, | ||
1043 | }, | 1110 | }, |
1044 | file_system_edits: [ | 1111 | }, |
1045 | MoveFile { | 1112 | file_system_edits: [ |
1046 | src: FileId( | 1113 | MoveFile { |
1114 | src: FileId( | ||
1115 | 1, | ||
1116 | ), | ||
1117 | dst: AnchoredPathBuf { | ||
1118 | anchor: FileId( | ||
1047 | 1, | 1119 | 1, |
1048 | ), | 1120 | ), |
1049 | dst: AnchoredPathBuf { | 1121 | path: "bar.rs", |
1050 | anchor: FileId( | ||
1051 | 1, | ||
1052 | ), | ||
1053 | path: "bar.rs", | ||
1054 | }, | ||
1055 | }, | 1122 | }, |
1056 | ], | 1123 | }, |
1057 | is_snippet: false, | 1124 | ], |
1058 | }, | 1125 | is_snippet: false, |
1059 | } | 1126 | } |
1060 | "#]], | 1127 | "#]], |
1061 | ); | 1128 | ); |
@@ -1096,46 +1163,43 @@ pub mod foo$0; | |||
1096 | // pub fn fun() {} | 1163 | // pub fn fun() {} |
1097 | "#, | 1164 | "#, |
1098 | expect![[r#" | 1165 | expect![[r#" |
1099 | RangeInfo { | 1166 | SourceChange { |
1100 | range: 8..11, | 1167 | source_file_edits: { |
1101 | info: SourceChange { | 1168 | FileId( |
1102 | source_file_edits: { | 1169 | 0, |
1103 | FileId( | 1170 | ): TextEdit { |
1104 | 0, | 1171 | indels: [ |
1105 | ): TextEdit { | 1172 | Indel { |
1106 | indels: [ | 1173 | insert: "foo2", |
1107 | Indel { | 1174 | delete: 27..30, |
1108 | insert: "foo2", | 1175 | }, |
1109 | delete: 27..30, | 1176 | ], |
1110 | }, | 1177 | }, |
1111 | ], | 1178 | FileId( |
1112 | }, | 1179 | 1, |
1113 | FileId( | 1180 | ): TextEdit { |
1114 | 1, | 1181 | indels: [ |
1115 | ): TextEdit { | 1182 | Indel { |
1116 | indels: [ | 1183 | insert: "foo2", |
1117 | Indel { | 1184 | delete: 8..11, |
1118 | insert: "foo2", | 1185 | }, |
1119 | delete: 8..11, | 1186 | ], |
1120 | }, | ||
1121 | ], | ||
1122 | }, | ||
1123 | }, | 1187 | }, |
1124 | file_system_edits: [ | 1188 | }, |
1125 | MoveFile { | 1189 | file_system_edits: [ |
1126 | src: FileId( | 1190 | MoveFile { |
1191 | src: FileId( | ||
1192 | 2, | ||
1193 | ), | ||
1194 | dst: AnchoredPathBuf { | ||
1195 | anchor: FileId( | ||
1127 | 2, | 1196 | 2, |
1128 | ), | 1197 | ), |
1129 | dst: AnchoredPathBuf { | 1198 | path: "foo2.rs", |
1130 | anchor: FileId( | ||
1131 | 2, | ||
1132 | ), | ||
1133 | path: "foo2.rs", | ||
1134 | }, | ||
1135 | }, | 1199 | }, |
1136 | ], | 1200 | }, |
1137 | is_snippet: false, | 1201 | ], |
1138 | }, | 1202 | is_snippet: false, |
1139 | } | 1203 | } |
1140 | "#]], | 1204 | "#]], |
1141 | ); | 1205 | ); |
@@ -1389,8 +1453,8 @@ impl Foo { | |||
1389 | } | 1453 | } |
1390 | 1454 | ||
1391 | #[test] | 1455 | #[test] |
1392 | fn test_initializer_use_field_init_shorthand() { | 1456 | fn test_rename_field_put_init_shorthand() { |
1393 | mark::check!(test_rename_field_expr_pat); | 1457 | mark::check!(test_rename_field_put_init_shorthand); |
1394 | check( | 1458 | check( |
1395 | "bar", | 1459 | "bar", |
1396 | r#" | 1460 | r#" |
@@ -1411,23 +1475,46 @@ fn foo(bar: i32) -> Foo { | |||
1411 | } | 1475 | } |
1412 | 1476 | ||
1413 | #[test] | 1477 | #[test] |
1414 | fn test_struct_field_destructure_into_shorthand() { | 1478 | fn test_rename_local_put_init_shorthand() { |
1479 | mark::check!(test_rename_local_put_init_shorthand); | ||
1480 | check( | ||
1481 | "i", | ||
1482 | r#" | ||
1483 | struct Foo { i: i32 } | ||
1484 | |||
1485 | fn foo(bar$0: i32) -> Foo { | ||
1486 | Foo { i: bar } | ||
1487 | } | ||
1488 | "#, | ||
1489 | r#" | ||
1490 | struct Foo { i: i32 } | ||
1491 | |||
1492 | fn foo(i: i32) -> Foo { | ||
1493 | Foo { i } | ||
1494 | } | ||
1495 | "#, | ||
1496 | ); | ||
1497 | } | ||
1498 | |||
1499 | #[test] | ||
1500 | fn test_struct_field_pat_into_shorthand() { | ||
1501 | mark::check!(test_rename_field_put_init_shorthand_pat); | ||
1415 | check( | 1502 | check( |
1416 | "baz", | 1503 | "baz", |
1417 | r#" | 1504 | r#" |
1418 | struct Foo { i$0: i32 } | 1505 | struct Foo { i$0: i32 } |
1419 | 1506 | ||
1420 | fn foo(foo: Foo) { | 1507 | fn foo(foo: Foo) { |
1421 | let Foo { i: baz } = foo; | 1508 | let Foo { i: ref baz @ qux } = foo; |
1422 | let _ = baz; | 1509 | let _ = qux; |
1423 | } | 1510 | } |
1424 | "#, | 1511 | "#, |
1425 | r#" | 1512 | r#" |
1426 | struct Foo { baz: i32 } | 1513 | struct Foo { baz: i32 } |
1427 | 1514 | ||
1428 | fn foo(foo: Foo) { | 1515 | fn foo(foo: Foo) { |
1429 | let Foo { baz } = foo; | 1516 | let Foo { ref baz @ qux } = foo; |
1430 | let _ = baz; | 1517 | let _ = qux; |
1431 | } | 1518 | } |
1432 | "#, | 1519 | "#, |
1433 | ); | 1520 | ); |
@@ -1501,6 +1588,27 @@ fn foo(Foo { i: bar }: foo) -> i32 { | |||
1501 | } | 1588 | } |
1502 | 1589 | ||
1503 | #[test] | 1590 | #[test] |
1591 | fn test_struct_field_complex_ident_pat() { | ||
1592 | check( | ||
1593 | "baz", | ||
1594 | r#" | ||
1595 | struct Foo { i$0: i32 } | ||
1596 | |||
1597 | fn foo(foo: Foo) { | ||
1598 | let Foo { ref i } = foo; | ||
1599 | } | ||
1600 | "#, | ||
1601 | r#" | ||
1602 | struct Foo { baz: i32 } | ||
1603 | |||
1604 | fn foo(foo: Foo) { | ||
1605 | let Foo { baz: ref i } = foo; | ||
1606 | } | ||
1607 | "#, | ||
1608 | ); | ||
1609 | } | ||
1610 | |||
1611 | #[test] | ||
1504 | fn test_rename_lifetimes() { | 1612 | fn test_rename_lifetimes() { |
1505 | mark::check!(rename_lifetime); | 1613 | mark::check!(rename_lifetime); |
1506 | check( | 1614 | check( |
@@ -1607,4 +1715,38 @@ impl Foo { | |||
1607 | "#, | 1715 | "#, |
1608 | ) | 1716 | ) |
1609 | } | 1717 | } |
1718 | |||
1719 | #[test] | ||
1720 | fn test_rename_field_in_pat_in_macro_doesnt_shorthand() { | ||
1721 | // ideally we would be able to make this emit a short hand, but I doubt this is easily possible | ||
1722 | check( | ||
1723 | "baz", | ||
1724 | r#" | ||
1725 | macro_rules! foo { | ||
1726 | ($pattern:pat) => { | ||
1727 | let $pattern = loop {}; | ||
1728 | }; | ||
1729 | } | ||
1730 | struct Foo { | ||
1731 | bar$0: u32, | ||
1732 | } | ||
1733 | fn foo() { | ||
1734 | foo!(Foo { bar: baz }); | ||
1735 | } | ||
1736 | "#, | ||
1737 | r#" | ||
1738 | macro_rules! foo { | ||
1739 | ($pattern:pat) => { | ||
1740 | let $pattern = loop {}; | ||
1741 | }; | ||
1742 | } | ||
1743 | struct Foo { | ||
1744 | baz: u32, | ||
1745 | } | ||
1746 | fn foo() { | ||
1747 | foo!(Foo { baz: baz }); | ||
1748 | } | ||
1749 | "#, | ||
1750 | ) | ||
1751 | } | ||
1610 | } | 1752 | } |