aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/goto_definition.rs57
-rw-r--r--crates/ide/src/hover.rs7
-rw-r--r--crates/ide/src/references/rename.rs240
-rw-r--r--crates/ide/src/runnables.rs37
4 files changed, 216 insertions, 125 deletions
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index c20185b16..cd4afc804 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -6,15 +6,13 @@ use ide_db::{
6 symbol_index, RootDatabase, 6 symbol_index, RootDatabase,
7}; 7};
8use syntax::{ 8use syntax::{
9 ast::{self, NameOwner}, 9 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T,
10 match_ast, AstNode, AstToken,
11 SyntaxKind::*,
12 SyntaxToken, TextSize, TokenAtOffset, T,
13}; 10};
14 11
15use crate::{ 12use crate::{
16 display::{ToNav, TryToNav}, 13 display::{ToNav, TryToNav},
17 doc_links::extract_definitions_from_markdown, 14 doc_links::extract_definitions_from_markdown,
15 runnables::doc_owner_to_def,
18 FilePosition, NavigationTarget, RangeInfo, SymbolKind, 16 FilePosition, NavigationTarget, RangeInfo, SymbolKind,
19}; 17};
20 18
@@ -84,31 +82,23 @@ fn def_for_doc_comment(
84 doc_comment: &ast::Comment, 82 doc_comment: &ast::Comment,
85) -> Option<hir::ModuleDef> { 83) -> Option<hir::ModuleDef> {
86 let parent = doc_comment.syntax().parent(); 84 let parent = doc_comment.syntax().parent();
87 let db = sema.db;
88 let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?; 85 let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?;
89 let link = &link; 86
90 let name = match_ast! { 87 let def = doc_owner_to_def(sema, parent)?;
91 match parent { 88 match def {
92 ast::Name(name) => Some(name),
93 ast::Fn(func) => func.name(),
94 _ => None,
95 }
96 }?;
97 let definition = NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db))?;
98 match definition {
99 Definition::ModuleDef(def) => match def { 89 Definition::ModuleDef(def) => match def {
100 ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns), 90 ModuleDef::Module(it) => it.resolve_doc_path(sema.db, &link, ns),
101 ModuleDef::Function(it) => it.resolve_doc_path(db, link, ns), 91 ModuleDef::Function(it) => it.resolve_doc_path(sema.db, &link, ns),
102 ModuleDef::Adt(it) => it.resolve_doc_path(db, link, ns), 92 ModuleDef::Adt(it) => it.resolve_doc_path(sema.db, &link, ns),
103 ModuleDef::Variant(it) => it.resolve_doc_path(db, link, ns), 93 ModuleDef::Variant(it) => it.resolve_doc_path(sema.db, &link, ns),
104 ModuleDef::Const(it) => it.resolve_doc_path(db, link, ns), 94 ModuleDef::Const(it) => it.resolve_doc_path(sema.db, &link, ns),
105 ModuleDef::Static(it) => it.resolve_doc_path(db, link, ns), 95 ModuleDef::Static(it) => it.resolve_doc_path(sema.db, &link, ns),
106 ModuleDef::Trait(it) => it.resolve_doc_path(db, link, ns), 96 ModuleDef::Trait(it) => it.resolve_doc_path(sema.db, &link, ns),
107 ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, link, ns), 97 ModuleDef::TypeAlias(it) => it.resolve_doc_path(sema.db, &link, ns),
108 ModuleDef::BuiltinType(_) => return None, 98 ModuleDef::BuiltinType(_) => return None,
109 }, 99 },
110 Definition::Macro(it) => it.resolve_doc_path(db, link, ns), 100 Definition::Macro(it) => it.resolve_doc_path(sema.db, &link, ns),
111 Definition::Field(it) => it.resolve_doc_path(db, link, ns), 101 Definition::Field(it) => it.resolve_doc_path(sema.db, &link, ns),
112 Definition::SelfType(_) 102 Definition::SelfType(_)
113 | Definition::Local(_) 103 | Definition::Local(_)
114 | Definition::GenericParam(_) 104 | Definition::GenericParam(_)
@@ -1212,7 +1202,7 @@ fn foo<'foo>(_: &'foo ()) {
1212 } 1202 }
1213 1203
1214 #[test] 1204 #[test]
1215 fn goto_def_for_intra_rustdoc_link_same_file() { 1205 fn goto_def_for_intra_doc_link_same_file() {
1216 check( 1206 check(
1217 r#" 1207 r#"
1218/// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar) 1208/// Blah, [`bar`](bar) .. [`foo`](foo)$0 has [`bar`](bar)
@@ -1225,4 +1215,19 @@ pub fn foo() { }
1225}"#, 1215}"#,
1226 ) 1216 )
1227 } 1217 }
1218
1219 #[test]
1220 fn goto_def_for_intra_doc_link_inner() {
1221 check(
1222 r#"
1223//- /main.rs
1224mod m;
1225struct S;
1226 //^
1227
1228//- /m.rs
1229//! [`super::S$0`]
1230"#,
1231 )
1232 }
1228} 1233}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index e892d5588..317b6f011 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -175,12 +175,7 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
175 Definition::SelfType(it) => it.target_ty(db).as_adt(), 175 Definition::SelfType(it) => it.target_ty(db).as_adt(),
176 _ => None, 176 _ => None,
177 }?; 177 }?;
178 match adt { 178 adt.try_to_nav(db).map(to_action)
179 Adt::Struct(it) => it.try_to_nav(db),
180 Adt::Union(it) => it.try_to_nav(db),
181 Adt::Enum(it) => it.try_to_nav(db),
182 }
183 .map(to_action)
184} 179}
185 180
186fn runnable_action( 181fn runnable_action(
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 099900673..3edc43e08 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -14,16 +14,17 @@ use ide_db::{
14use syntax::{ 14use syntax::{
15 algo::find_node_at_offset, 15 algo::find_node_at_offset,
16 ast::{self, NameOwner}, 16 ast::{self, NameOwner},
17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T,
18}; 18};
19use test_utils::mark; 19use test_utils::mark;
20use text_edit::TextEdit; 20use text_edit::TextEdit;
21 21
22use crate::{ 22use crate::{
23 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, 23 FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult,
24 SourceChange, SourceFileEdit, TextRange, TextSize, 24 SourceChange, SourceFileEdit, TextRange, TextSize,
25}; 25};
26 26
27type RenameResult<T> = Result<T, RenameError>;
27#[derive(Debug)] 28#[derive(Debug)]
28pub struct RenameError(pub(crate) String); 29pub struct RenameError(pub(crate) String);
29 30
@@ -35,24 +36,30 @@ impl fmt::Display for RenameError {
35 36
36impl Error for RenameError {} 37impl Error for RenameError {}
37 38
39macro_rules! format_err {
40 ($fmt:expr) => {RenameError(format!($fmt))};
41 ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))}
42}
43
44macro_rules! bail {
45 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))}
46}
47
38pub(crate) fn prepare_rename( 48pub(crate) fn prepare_rename(
39 db: &RootDatabase, 49 db: &RootDatabase,
40 position: FilePosition, 50 position: FilePosition,
41) -> Result<RangeInfo<()>, RenameError> { 51) -> RenameResult<RangeInfo<()>> {
42 let sema = Semantics::new(db); 52 let sema = Semantics::new(db);
43 let source_file = sema.parse(position.file_id); 53 let source_file = sema.parse(position.file_id);
44 let syntax = source_file.syntax(); 54 let syntax = source_file.syntax();
45 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 55 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
46 rename_mod(&sema, position, module, "dummy") 56 rename_mod(&sema, position, module, "dummy")
47 } else if let Some(self_token) = 57 } else if let Some(self_token) =
48 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 58 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
49 { 59 {
50 rename_self_to_param(&sema, position, self_token, "dummy") 60 rename_self_to_param(&sema, position, self_token, "dummy")
51 } else { 61 } else {
52 let range = match find_all_refs(&sema, position, None) { 62 let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
53 Some(RangeInfo { range, .. }) => range,
54 None => return Err(RenameError("No references found at position".to_string())),
55 };
56 Ok(RangeInfo::new(range, SourceChange::from(vec![]))) 63 Ok(RangeInfo::new(range, SourceChange::from(vec![])))
57 } 64 }
58 .map(|info| RangeInfo::new(info.range, ())) 65 .map(|info| RangeInfo::new(info.range, ()))
@@ -62,7 +69,7 @@ pub(crate) fn rename(
62 db: &RootDatabase, 69 db: &RootDatabase,
63 position: FilePosition, 70 position: FilePosition,
64 new_name: &str, 71 new_name: &str,
65) -> Result<RangeInfo<SourceChange>, RenameError> { 72) -> RenameResult<RangeInfo<SourceChange>> {
66 let sema = Semantics::new(db); 73 let sema = Semantics::new(db);
67 rename_with_semantics(&sema, position, new_name) 74 rename_with_semantics(&sema, position, new_name)
68} 75}
@@ -71,42 +78,18 @@ pub(crate) fn rename_with_semantics(
71 sema: &Semantics<RootDatabase>, 78 sema: &Semantics<RootDatabase>,
72 position: FilePosition, 79 position: FilePosition,
73 new_name: &str, 80 new_name: &str,
74) -> Result<RangeInfo<SourceChange>, RenameError> { 81) -> RenameResult<RangeInfo<SourceChange>> {
75 let is_lifetime_name = match lex_single_syntax_kind(new_name) {
76 Some(res) => match res {
77 (SyntaxKind::IDENT, _) => false,
78 (SyntaxKind::UNDERSCORE, _) => false,
79 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
80 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true,
81 (SyntaxKind::LIFETIME_IDENT, _) => {
82 return Err(RenameError(format!(
83 "Invalid name `{0}`: Cannot rename lifetime to {0}",
84 new_name
85 )))
86 }
87 (_, Some(syntax_error)) => {
88 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
89 }
90 (_, None) => {
91 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
92 }
93 },
94 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
95 };
96
97 let source_file = sema.parse(position.file_id); 82 let source_file = sema.parse(position.file_id);
98 let syntax = source_file.syntax(); 83 let syntax = source_file.syntax();
99 // this is here to prevent lifetime renames from happening on modules and self 84
100 if is_lifetime_name { 85 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
101 rename_reference(&sema, position, new_name, is_lifetime_name)
102 } else if let Some(module) = find_module_at_offset(&sema, position, syntax) {
103 rename_mod(&sema, position, module, new_name) 86 rename_mod(&sema, position, module, new_name)
104 } else if let Some(self_token) = 87 } else if let Some(self_token) =
105 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 88 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
106 { 89 {
107 rename_self_to_param(&sema, position, self_token, new_name) 90 rename_self_to_param(&sema, position, self_token, new_name)
108 } else { 91 } else {
109 rename_reference(&sema, position, new_name, is_lifetime_name) 92 rename_reference(&sema, position, new_name)
110 } 93 }
111} 94}
112 95
@@ -127,6 +110,33 @@ pub(crate) fn will_rename_file(
127 Some(change) 110 Some(change)
128} 111}
129 112
113#[derive(Debug, PartialEq)]
114enum IdentifierKind {
115 Ident,
116 Lifetime,
117 ToSelf,
118 Underscore,
119}
120
121fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
122 match lex_single_syntax_kind(new_name) {
123 Some(res) => match res {
124 (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident),
125 (T![_], _) => Ok(IdentifierKind::Underscore),
126 (T![self], _) => Ok(IdentifierKind::ToSelf),
127 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
128 Ok(IdentifierKind::Lifetime)
129 }
130 (SyntaxKind::LIFETIME_IDENT, _) => {
131 bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name)
132 }
133 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
134 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
135 },
136 None => bail!("Invalid name `{}`: not an identifier", new_name),
137 }
138}
139
130fn find_module_at_offset( 140fn find_module_at_offset(
131 sema: &Semantics<RootDatabase>, 141 sema: &Semantics<RootDatabase>,
132 position: FilePosition, 142 position: FilePosition,
@@ -155,6 +165,14 @@ fn find_module_at_offset(
155 Some(module) 165 Some(module)
156} 166}
157 167
168fn find_all_refs(
169 sema: &Semantics<RootDatabase>,
170 position: FilePosition,
171) -> RenameResult<RangeInfo<ReferenceSearchResult>> {
172 crate::references::find_all_refs(sema, position, None)
173 .ok_or_else(|| format_err!("No references found at position"))
174}
175
158fn source_edit_from_reference( 176fn source_edit_from_reference(
159 sema: &Semantics<RootDatabase>, 177 sema: &Semantics<RootDatabase>,
160 reference: Reference, 178 reference: Reference,
@@ -223,7 +241,10 @@ fn rename_mod(
223 position: FilePosition, 241 position: FilePosition,
224 module: Module, 242 module: Module,
225 new_name: &str, 243 new_name: &str,
226) -> Result<RangeInfo<SourceChange>, RenameError> { 244) -> RenameResult<RangeInfo<SourceChange>> {
245 if IdentifierKind::Ident != check_identifier(new_name)? {
246 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
247 }
227 let mut source_file_edits = Vec::new(); 248 let mut source_file_edits = Vec::new();
228 let mut file_system_edits = Vec::new(); 249 let mut file_system_edits = Vec::new();
229 250
@@ -254,8 +275,7 @@ fn rename_mod(
254 source_file_edits.push(edit); 275 source_file_edits.push(edit);
255 } 276 }
256 277
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 278 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
258 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
259 let ref_edits = refs 279 let ref_edits = refs
260 .references 280 .references
261 .into_iter() 281 .into_iter()
@@ -274,27 +294,26 @@ fn rename_to_self(
274 294
275 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 295 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
276 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) 296 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
277 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 297 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
278 let param_range = fn_ast 298 let param_range = fn_ast
279 .param_list() 299 .param_list()
280 .and_then(|p| p.params().next()) 300 .and_then(|p| p.params().next())
281 .ok_or_else(|| RenameError("Method has no parameters".to_string()))? 301 .ok_or_else(|| format_err!("Method has no parameters"))?
282 .syntax() 302 .syntax()
283 .text_range(); 303 .text_range();
284 if !param_range.contains(position.offset) { 304 if !param_range.contains(position.offset) {
285 return Err(RenameError("Only the first parameter can be self".to_string())); 305 bail!("Only the first parameter can be self");
286 } 306 }
287 307
288 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 308 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
289 .and_then(|def| sema.to_def(&def)) 309 .and_then(|def| sema.to_def(&def))
290 .ok_or_else(|| RenameError("No impl block found for function".to_string()))?; 310 .ok_or_else(|| format_err!("No impl block found for function"))?;
291 if fn_def.self_param(sema.db).is_some() { 311 if fn_def.self_param(sema.db).is_some() {
292 return Err(RenameError("Method already has a self parameter".to_string())); 312 bail!("Method already has a self parameter");
293 } 313 }
294 314
295 let params = fn_def.assoc_fn_params(sema.db); 315 let params = fn_def.assoc_fn_params(sema.db);
296 let first_param = 316 let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?;
297 params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
298 let first_param_ty = first_param.ty(); 317 let first_param_ty = first_param.ty();
299 let impl_ty = impl_block.target_ty(sema.db); 318 let impl_ty = impl_block.target_ty(sema.db);
300 let (ty, self_param) = if impl_ty.remove_ref().is_some() { 319 let (ty, self_param) = if impl_ty.remove_ref().is_some() {
@@ -307,18 +326,17 @@ fn rename_to_self(
307 }; 326 };
308 327
309 if ty != impl_ty { 328 if ty != impl_ty {
310 return Err(RenameError("Parameter type differs from impl block type".to_string())); 329 bail!("Parameter type differs from impl block type");
311 } 330 }
312 331
313 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 332 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
314 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
315 333
316 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 334 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
317 .into_iter() 335 .into_iter()
318 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 336 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
319 337
320 if param_ref.is_empty() { 338 if param_ref.is_empty() {
321 return Err(RenameError("Parameter to rename not found".to_string())); 339 bail!("Parameter to rename not found");
322 } 340 }
323 341
324 let mut edits = usages 342 let mut edits = usages
@@ -367,12 +385,22 @@ fn rename_self_to_param(
367 self_token: SyntaxToken, 385 self_token: SyntaxToken,
368 new_name: &str, 386 new_name: &str,
369) -> Result<RangeInfo<SourceChange>, RenameError> { 387) -> Result<RangeInfo<SourceChange>, RenameError> {
388 let ident_kind = check_identifier(new_name)?;
389 match ident_kind {
390 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name),
391 IdentifierKind::ToSelf => {
392 // no-op
393 mark::hit!(rename_self_to_self);
394 return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default()));
395 }
396 _ => (),
397 }
370 let source_file = sema.parse(position.file_id); 398 let source_file = sema.parse(position.file_id);
371 let syn = source_file.syntax(); 399 let syn = source_file.syntax();
372 400
373 let text = sema.db.file_text(position.file_id); 401 let text = sema.db.file_text(position.file_id);
374 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) 402 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
375 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 403 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
376 let search_range = fn_def.syntax().text_range(); 404 let search_range = fn_def.syntax().text_range();
377 405
378 let mut edits: Vec<SourceFileEdit> = vec![]; 406 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -382,12 +410,10 @@ fn rename_self_to_param(
382 if !search_range.contains_inclusive(offset) { 410 if !search_range.contains_inclusive(offset) {
383 continue; 411 continue;
384 } 412 }
385 if let Some(ref usage) = 413 if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) {
386 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
387 {
388 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 414 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
389 text_edit_from_self_param(syn, self_param, new_name) 415 text_edit_from_self_param(syn, self_param, new_name)
390 .ok_or_else(|| RenameError("No target type found".to_string()))? 416 .ok_or_else(|| format_err!("No target type found"))?
391 } else { 417 } else {
392 TextEdit::replace(usage.text_range(), String::from(new_name)) 418 TextEdit::replace(usage.text_range(), String::from(new_name))
393 }; 419 };
@@ -395,6 +421,10 @@ fn rename_self_to_param(
395 } 421 }
396 } 422 }
397 423
424 if edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
425 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
426 }
427
398 let range = ast::SelfParam::cast(self_token.parent()) 428 let range = ast::SelfParam::cast(self_token.parent())
399 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 429 .map_or(self_token.text_range(), |p| p.syntax().text_range());
400 430
@@ -405,24 +435,34 @@ fn rename_reference(
405 sema: &Semantics<RootDatabase>, 435 sema: &Semantics<RootDatabase>,
406 position: FilePosition, 436 position: FilePosition,
407 new_name: &str, 437 new_name: &str,
408 is_lifetime_name: bool,
409) -> Result<RangeInfo<SourceChange>, RenameError> { 438) -> Result<RangeInfo<SourceChange>, RenameError> {
410 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { 439 let ident_kind = check_identifier(new_name)?;
411 Some(range_info) => range_info, 440 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
412 None => return Err(RenameError("No references found at position".to_string())), 441
413 }; 442 match (ident_kind, &refs.declaration.kind) {
414 443 (IdentifierKind::ToSelf, ReferenceKind::Lifetime)
415 match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { 444 | (IdentifierKind::Underscore, ReferenceKind::Lifetime)
416 (true, false) => { 445 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => {
417 return Err(RenameError(format!( 446 mark::hit!(rename_not_a_lifetime_ident_ref);
418 "Invalid name `{}`: not a lifetime identifier", 447 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
419 new_name
420 )))
421 } 448 }
422 (false, true) => { 449 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime),
423 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) 450 (IdentifierKind::Lifetime, _) => {
451 mark::hit!(rename_not_an_ident_ref);
452 bail!("Invalid name `{}`: not an identifier", new_name)
424 } 453 }
425 _ => (), 454 (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => {
455 unreachable!("rename_self_to_param should've been called instead")
456 }
457 (IdentifierKind::ToSelf, _) => {
458 mark::hit!(rename_to_self);
459 return rename_to_self(sema, position);
460 }
461 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
462 mark::hit!(rename_underscore_multiple);
463 bail!("Cannot rename reference to `_` as it is being referenced multiple times")
464 }
465 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
426 } 466 }
427 467
428 let edit = refs 468 let edit = refs
@@ -430,10 +470,6 @@ fn rename_reference(
430 .map(|reference| source_edit_from_reference(sema, reference, new_name)) 470 .map(|reference| source_edit_from_reference(sema, reference, new_name))
431 .collect::<Vec<_>>(); 471 .collect::<Vec<_>>();
432 472
433 if edit.is_empty() {
434 return Err(RenameError("No references found at position".to_string()));
435 }
436
437 Ok(RangeInfo::new(range, SourceChange::from(edit))) 473 Ok(RangeInfo::new(range, SourceChange::from(edit)))
438} 474}
439 475
@@ -462,9 +498,11 @@ mod tests {
462 text_edit_builder.replace(indel.delete, indel.insert); 498 text_edit_builder.replace(indel.delete, indel.insert);
463 } 499 }
464 } 500 }
465 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 501 if let Some(file_id) = file_id {
466 text_edit_builder.finish().apply(&mut result); 502 let mut result = analysis.file_text(file_id).unwrap().to_string();
467 assert_eq_text!(ra_fixture_after, &*result); 503 text_edit_builder.finish().apply(&mut result);
504 assert_eq_text!(ra_fixture_after, &*result);
505 }
468 } 506 }
469 Err(err) => { 507 Err(err) => {
470 if ra_fixture_after.starts_with("error:") { 508 if ra_fixture_after.starts_with("error:") {
@@ -530,6 +568,7 @@ mod tests {
530 568
531 #[test] 569 #[test]
532 fn test_rename_to_invalid_identifier_lifetime() { 570 fn test_rename_to_invalid_identifier_lifetime() {
571 mark::check!(rename_not_an_ident_ref);
533 check( 572 check(
534 "'foo", 573 "'foo",
535 r#"fn main() { let i$0 = 1; }"#, 574 r#"fn main() { let i$0 = 1; }"#,
@@ -539,6 +578,7 @@ mod tests {
539 578
540 #[test] 579 #[test]
541 fn test_rename_to_invalid_identifier_lifetime2() { 580 fn test_rename_to_invalid_identifier_lifetime2() {
581 mark::check!(rename_not_a_lifetime_ident_ref);
542 check( 582 check(
543 "foo", 583 "foo",
544 r#"fn main<'a>(_: &'a$0 ()) {}"#, 584 r#"fn main<'a>(_: &'a$0 ()) {}"#,
@@ -547,7 +587,27 @@ mod tests {
547 } 587 }
548 588
549 #[test] 589 #[test]
590 fn test_rename_to_underscore_invalid() {
591 mark::check!(rename_underscore_multiple);
592 check(
593 "_",
594 r#"fn main(foo$0: ()) {foo;}"#,
595 "error: Cannot rename reference to `_` as it is being referenced multiple times",
596 );
597 }
598
599 #[test]
600 fn test_rename_mod_invalid() {
601 check(
602 "'foo",
603 r#"mod foo$0 {}"#,
604 "error: Invalid name `'foo`: cannot rename module to 'foo",
605 );
606 }
607
608 #[test]
550 fn test_rename_for_local() { 609 fn test_rename_for_local() {
610 mark::check!(rename_ident);
551 check( 611 check(
552 "k", 612 "k",
553 r#" 613 r#"
@@ -1178,6 +1238,7 @@ fn foo(f: foo::Foo) {
1178 1238
1179 #[test] 1239 #[test]
1180 fn test_parameter_to_self() { 1240 fn test_parameter_to_self() {
1241 mark::check!(rename_to_self);
1181 check( 1242 check(
1182 "self", 1243 "self",
1183 r#" 1244 r#"
@@ -1481,6 +1542,7 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1481 1542
1482 #[test] 1543 #[test]
1483 fn test_rename_lifetimes() { 1544 fn test_rename_lifetimes() {
1545 mark::check!(rename_lifetime);
1484 check( 1546 check(
1485 "'yeeee", 1547 "'yeeee",
1486 r#" 1548 r#"
@@ -1565,4 +1627,24 @@ fn foo<'a>() -> &'a () {
1565"#, 1627"#,
1566 ) 1628 )
1567 } 1629 }
1630
1631 #[test]
1632 fn test_self_to_self() {
1633 mark::check!(rename_self_to_self);
1634 check(
1635 "self",
1636 r#"
1637struct Foo;
1638impl Foo {
1639 fn foo(self$0) {}
1640}
1641"#,
1642 r#"
1643struct Foo;
1644impl Foo {
1645 fn foo(self) {}
1646}
1647"#,
1648 )
1649 }
1568} 1650}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 3a1e204db..f5ee7de86 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -3,7 +3,7 @@ use std::fmt;
3use assists::utils::test_related_attribute; 3use assists::utils::test_related_attribute;
4use cfg::CfgExpr; 4use cfg::CfgExpr;
5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; 5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
6use ide_db::RootDatabase; 6use ide_db::{defs::Definition, RootDatabase};
7use itertools::Itertools; 7use itertools::Itertools;
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, 9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner},
@@ -110,7 +110,10 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
110 _ => None, 110 _ => None,
111 } 111 }
112 }; 112 };
113 runnable.or_else(|| runnable_doctest(&sema, item)) 113 runnable.or_else(|| match doc_owner_to_def(&sema, item)? {
114 Definition::ModuleDef(def) => module_def_doctest(&sema, def),
115 _ => None,
116 })
114 }) 117 })
115 .collect() 118 .collect()
116} 119}
@@ -170,20 +173,26 @@ pub(crate) fn runnable_mod(
170 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) 173 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
171} 174}
172 175
173fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 176// FIXME: figure out a proper API here.
174 match_ast! { 177pub(crate) fn doc_owner_to_def(
178 sema: &Semantics<RootDatabase>,
179 item: SyntaxNode,
180) -> Option<Definition> {
181 let res: hir::ModuleDef = match_ast! {
175 match item { 182 match item {
176 ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 183 ast::SourceFile(it) => sema.scope(&item).module()?.into(),
177 ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 184 ast::Fn(it) => sema.to_def(&it)?.into(),
178 ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 185 ast::Struct(it) => sema.to_def(&it)?.into(),
179 ast::Union(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 186 ast::Enum(it) => sema.to_def(&it)?.into(),
180 ast::Trait(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 187 ast::Union(it) => sema.to_def(&it)?.into(),
181 ast::Const(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 188 ast::Trait(it) => sema.to_def(&it)?.into(),
182 ast::Static(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 189 ast::Const(it) => sema.to_def(&it)?.into(),
183 ast::TypeAlias(it) => module_def_doctest(sema, sema.to_def(&it)?.into()), 190 ast::Static(it) => sema.to_def(&it)?.into(),
184 _ => None, 191 ast::TypeAlias(it) => sema.to_def(&it)?.into(),
192 _ => return None,
185 } 193 }
186 } 194 };
195 Some(Definition::ModuleDef(res))
187} 196}
188 197
189fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> { 198fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {