aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/call_hierarchy.rs2
-rw-r--r--crates/ide/src/diagnostics/fixes.rs2
-rw-r--r--crates/ide/src/display/navigation_target.rs18
-rw-r--r--crates/ide/src/display/short_label.rs10
-rw-r--r--crates/ide/src/doc_links.rs31
-rw-r--r--crates/ide/src/extend_selection.rs4
-rw-r--r--crates/ide/src/goto_definition.rs95
-rw-r--r--crates/ide/src/hover.rs42
-rw-r--r--crates/ide/src/inlay_hints.rs2
-rw-r--r--crates/ide/src/join_lines.rs2
-rw-r--r--crates/ide/src/lib.rs21
-rw-r--r--crates/ide/src/references.rs72
-rw-r--r--crates/ide/src/references/rename.rs676
-rw-r--r--crates/ide/src/runnables.rs230
-rw-r--r--crates/ide/src/status.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs22
-rw-r--r--crates/ide/src/syntax_highlighting/inject.rs2
-rw-r--r--crates/ide/src/syntax_tree.rs28
19 files changed, 690 insertions, 573 deletions
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index e8999a7f3..b10a0a78b 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -47,7 +47,7 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio
47 47
48 let mut calls = CallLocations::default(); 48 let mut calls = CallLocations::default();
49 49
50 for (&file_id, references) in refs.info.references().iter() { 50 for (&file_id, references) in refs.references().iter() {
51 let file = sema.parse(file_id); 51 let file = sema.parse(file_id);
52 let file = file.syntax(); 52 let file = file.syntax();
53 for reference in references { 53 for reference in references {
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index e4335119b..579d5a308 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -140,7 +140,7 @@ impl DiagnosticWithFix for IncorrectCase {
140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; 140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
141 141
142 let label = format!("Rename to {}", self.suggested_text); 142 let label = format!("Rename to {}", self.suggested_text);
143 Some(Fix::new(&label, rename_changes.info, rename_changes.range)) 143 Some(Fix::new(&label, rename_changes, frange.range))
144 } 144 }
145} 145}
146 146
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 685052e7f..9c568c90c 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -153,8 +153,7 @@ impl NavigationTarget {
153 node: InFile<&dyn ast::NameOwner>, 153 node: InFile<&dyn ast::NameOwner>,
154 kind: SymbolKind, 154 kind: SymbolKind,
155 ) -> NavigationTarget { 155 ) -> NavigationTarget {
156 let name = 156 let name = node.value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into());
157 node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
158 let focus_range = 157 let focus_range =
159 node.value.name().map(|it| node.with_value(it.syntax()).original_file_range(db).range); 158 node.value.name().map(|it| node.with_value(it.syntax()).original_file_range(db).range);
160 let frange = node.map(|it| it.syntax()).original_file_range(db); 159 let frange = node.map(|it| it.syntax()).original_file_range(db);
@@ -295,6 +294,7 @@ impl ToNav for hir::Module {
295 ModuleSource::Module(node) => { 294 ModuleSource::Module(node) => {
296 (node.syntax(), node.name().map(|it| it.syntax().text_range())) 295 (node.syntax(), node.name().map(|it| it.syntax().text_range()))
297 } 296 }
297 ModuleSource::BlockExpr(node) => (node.syntax(), None),
298 }; 298 };
299 let frange = src.with_value(syntax).original_file_range(db); 299 let frange = src.with_value(syntax).original_file_range(db);
300 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, SymbolKind::Module) 300 NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, SymbolKind::Module)
@@ -400,15 +400,13 @@ impl TryToNav for hir::GenericParam {
400impl ToNav for hir::Local { 400impl ToNav for hir::Local {
401 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 401 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
402 let src = self.source(db); 402 let src = self.source(db);
403 let (node, focus_range) = match &src.value { 403 let (node, name) = match &src.value {
404 Either::Left(bind_pat) => ( 404 Either::Left(bind_pat) => (bind_pat.syntax().clone(), bind_pat.name()),
405 bind_pat.syntax().clone(), 405 Either::Right(it) => (it.syntax().clone(), it.name()),
406 bind_pat
407 .name()
408 .map(|it| src.with_value(&it.syntax().clone()).original_file_range(db).range),
409 ),
410 Either::Right(it) => (it.syntax().clone(), it.self_token().map(|it| it.text_range())),
411 }; 406 };
407 let focus_range =
408 name.map(|it| src.with_value(&it.syntax().clone()).original_file_range(db).range);
409
412 let full_range = src.with_value(&node).original_file_range(db); 410 let full_range = src.with_value(&node).original_file_range(db);
413 let name = match self.name(db) { 411 let name = match self.name(db) {
414 Some(it) => it.to_string().into(), 412 Some(it) => it.to_string().into(),
diff --git a/crates/ide/src/display/short_label.rs b/crates/ide/src/display/short_label.rs
index 990f740b8..7ac050473 100644
--- a/crates/ide/src/display/short_label.rs
+++ b/crates/ide/src/display/short_label.rs
@@ -53,6 +53,12 @@ impl ShortLabel for ast::SourceFile {
53 } 53 }
54} 54}
55 55
56impl ShortLabel for ast::BlockExpr {
57 fn short_label(&self) -> Option<String> {
58 None
59 }
60}
61
56impl ShortLabel for ast::TypeAlias { 62impl ShortLabel for ast::TypeAlias {
57 fn short_label(&self) -> Option<String> { 63 fn short_label(&self) -> Option<String> {
58 short_label_from_node(self, "type ") 64 short_label_from_node(self, "type ")
@@ -90,7 +96,7 @@ impl ShortLabel for ast::Variant {
90impl ShortLabel for ast::ConstParam { 96impl ShortLabel for ast::ConstParam {
91 fn short_label(&self) -> Option<String> { 97 fn short_label(&self) -> Option<String> {
92 let mut buf = "const ".to_owned(); 98 let mut buf = "const ".to_owned();
93 buf.push_str(self.name()?.text().as_str()); 99 buf.push_str(self.name()?.text());
94 if let Some(type_ref) = self.ty() { 100 if let Some(type_ref) = self.ty() {
95 format_to!(buf, ": {}", type_ref.syntax()); 101 format_to!(buf, ": {}", type_ref.syntax());
96 } 102 }
@@ -117,6 +123,6 @@ where
117{ 123{
118 let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default(); 124 let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default();
119 buf.push_str(label); 125 buf.push_str(label);
120 buf.push_str(node.name()?.text().as_str()); 126 buf.push_str(node.name()?.text());
121 Some(buf) 127 Some(buf)
122} 128}
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index de10406bc..730e0dd0a 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -221,14 +221,31 @@ fn rewrite_intra_doc_link(
221 }?; 221 }?;
222 let krate = resolved.module(db)?.krate(); 222 let krate = resolved.module(db)?.krate();
223 let canonical_path = resolved.canonical_path(db)?; 223 let canonical_path = resolved.canonical_path(db)?;
224 let new_target = get_doc_url(db, &krate)? 224 let mut new_url = get_doc_url(db, &krate)?
225 .join(&format!("{}/", krate.display_name(db)?)) 225 .join(&format!("{}/", krate.display_name(db)?))
226 .ok()? 226 .ok()?
227 .join(&canonical_path.replace("::", "/")) 227 .join(&canonical_path.replace("::", "/"))
228 .ok()? 228 .ok()?
229 .join(&get_symbol_filename(db, &resolved)?) 229 .join(&get_symbol_filename(db, &resolved)?)
230 .ok()? 230 .ok()?;
231 .into_string(); 231
232 if let ModuleDef::Trait(t) = resolved {
233 let items = t.items(db);
234 if let Some(field_or_assoc_item) = items.iter().find_map(|assoc_item| {
235 if let Some(name) = assoc_item.name(db) {
236 if link.to_string() == format!("{}::{}", canonical_path, name) {
237 return Some(FieldOrAssocItem::AssocItem(*assoc_item));
238 }
239 }
240 None
241 }) {
242 if let Some(fragment) = get_symbol_fragment(db, &field_or_assoc_item) {
243 new_url = new_url.join(&fragment).ok()?;
244 }
245 };
246 }
247
248 let new_target = new_url.into_string();
232 let new_title = strip_prefixes_suffixes(title); 249 let new_title = strip_prefixes_suffixes(title);
233 Some((new_target, new_title.to_string())) 250 Some((new_target, new_title.to_string()))
234} 251}
@@ -438,10 +455,10 @@ fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem)
438 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), 455 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
439 FieldOrAssocItem::AssocItem(assoc) => match assoc { 456 FieldOrAssocItem::AssocItem(assoc) => match assoc {
440 AssocItem::Function(function) => { 457 AssocItem::Function(function) => {
441 let is_trait_method = matches!( 458 let is_trait_method = function
442 function.as_assoc_item(db).map(|assoc| assoc.container(db)), 459 .as_assoc_item(db)
443 Some(AssocItemContainer::Trait(..)) 460 .and_then(|assoc| assoc.containing_trait(db))
444 ); 461 .is_some();
445 // This distinction may get more complicated when specialization is available. 462 // This distinction may get more complicated when specialization is available.
446 // Rustdoc makes this decision based on whether a method 'has defaultness'. 463 // Rustdoc makes this decision based on whether a method 'has defaultness'.
447 // Currently this is only the case for provided trait methods. 464 // Currently this is only the case for provided trait methods.
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 17a540972..2d722dee0 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -213,8 +213,8 @@ fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextSize) -> TextRange
213 let ws_text = ws.text(); 213 let ws_text = ws.text();
214 let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start(); 214 let suffix = TextRange::new(offset, ws.text_range().end()) - ws.text_range().start();
215 let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start(); 215 let prefix = TextRange::new(ws.text_range().start(), offset) - ws.text_range().start();
216 let ws_suffix = &ws_text.as_str()[suffix]; 216 let ws_suffix = &ws_text[suffix];
217 let ws_prefix = &ws_text.as_str()[prefix]; 217 let ws_prefix = &ws_text[prefix];
218 if ws_text.contains('\n') && !ws_suffix.contains('\n') { 218 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
219 if let Some(node) = ws.next_sibling_or_token() { 219 if let Some(node) = ws.next_sibling_or_token() {
220 let start = match ws_prefix.rfind('\n') { 220 let start = match ws_prefix.rfind('\n') {
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 988a5668f..1a997fa40 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -2,16 +2,14 @@ use either::Either;
2use hir::{HasAttrs, ModuleDef, Semantics}; 2use hir::{HasAttrs, ModuleDef, Semantics};
3use ide_db::{ 3use ide_db::{
4 defs::{Definition, NameClass, NameRefClass}, 4 defs::{Definition, NameClass, NameRefClass},
5 symbol_index, RootDatabase, 5 RootDatabase,
6}; 6};
7use syntax::{ 7use syntax::{
8 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T, 8 ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextSize, TokenAtOffset, T,
9}; 9};
10 10
11use crate::{ 11use crate::{
12 display::{ToNav, TryToNav}, 12 display::TryToNav, doc_links::extract_definitions_from_markdown, runnables::doc_owner_to_def,
13 doc_links::extract_definitions_from_markdown,
14 runnables::doc_owner_to_def,
15 FilePosition, NavigationTarget, RangeInfo, 13 FilePosition, NavigationTarget, RangeInfo,
16}; 14};
17 15
@@ -38,33 +36,26 @@ pub(crate) fn goto_definition(
38 return Some(RangeInfo::new(original_token.text_range(), vec![nav])); 36 return Some(RangeInfo::new(original_token.text_range(), vec![nav]));
39 } 37 }
40 38
41 let nav_targets = match_ast! { 39 let nav = match_ast! {
42 match parent { 40 match parent {
43 ast::NameRef(name_ref) => { 41 ast::NameRef(name_ref) => {
44 reference_definition(&sema, Either::Right(&name_ref)).to_vec() 42 reference_definition(&sema, Either::Right(&name_ref))
45 }, 43 },
46 ast::Name(name) => { 44 ast::Name(name) => {
47 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); 45 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db);
48 let nav = def.try_to_nav(sema.db)?; 46 def.try_to_nav(sema.db)
49 vec![nav]
50 }, 47 },
51 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) { 48 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) {
52 let def = name_class.referenced_or_defined(sema.db); 49 let def = name_class.referenced_or_defined(sema.db);
53 let nav = def.try_to_nav(sema.db)?; 50 def.try_to_nav(sema.db)
54 vec![nav]
55 } else { 51 } else {
56 reference_definition(&sema, Either::Left(&lt)).to_vec() 52 reference_definition(&sema, Either::Left(&lt))
57 },
58 ast::SelfParam(self_param) => {
59 let def = NameClass::classify_self_param(&sema, &self_param)?.referenced_or_defined(sema.db);
60 let nav = def.try_to_nav(sema.db)?;
61 vec![nav]
62 }, 53 },
63 _ => return None, 54 _ => return None,
64 } 55 }
65 }; 56 };
66 57
67 Some(RangeInfo::new(original_token.text_range(), nav_targets)) 58 Some(RangeInfo::new(original_token.text_range(), nav.into_iter().collect()))
68} 59}
69 60
70fn def_for_doc_comment( 61fn def_for_doc_comment(
@@ -125,42 +116,16 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
125 } 116 }
126} 117}
127 118
128#[derive(Debug)]
129pub(crate) enum ReferenceResult {
130 Exact(NavigationTarget),
131 Approximate(Vec<NavigationTarget>),
132}
133
134impl ReferenceResult {
135 fn to_vec(self) -> Vec<NavigationTarget> {
136 match self {
137 ReferenceResult::Exact(target) => vec![target],
138 ReferenceResult::Approximate(vec) => vec,
139 }
140 }
141}
142
143pub(crate) fn reference_definition( 119pub(crate) fn reference_definition(
144 sema: &Semantics<RootDatabase>, 120 sema: &Semantics<RootDatabase>,
145 name_ref: Either<&ast::Lifetime, &ast::NameRef>, 121 name_ref: Either<&ast::Lifetime, &ast::NameRef>,
146) -> ReferenceResult { 122) -> Option<NavigationTarget> {
147 let name_kind = name_ref.either( 123 let name_kind = name_ref.either(
148 |lifetime| NameRefClass::classify_lifetime(sema, lifetime), 124 |lifetime| NameRefClass::classify_lifetime(sema, lifetime),
149 |name_ref| NameRefClass::classify(sema, name_ref), 125 |name_ref| NameRefClass::classify(sema, name_ref),
150 ); 126 )?;
151 if let Some(def) = name_kind { 127 let def = name_kind.referenced(sema.db);
152 let def = def.referenced(sema.db); 128 def.try_to_nav(sema.db)
153 return match def.try_to_nav(sema.db) {
154 Some(nav) => ReferenceResult::Exact(nav),
155 None => ReferenceResult::Approximate(Vec::new()),
156 };
157 }
158
159 // Fallback index based approach:
160 let name = name_ref.either(ast::Lifetime::text, ast::NameRef::text);
161 let navs =
162 symbol_index::index_resolve(sema.db, name).into_iter().map(|s| s.to_nav(sema.db)).collect();
163 ReferenceResult::Approximate(navs)
164} 129}
165 130
166#[cfg(test)] 131#[cfg(test)]
@@ -197,12 +162,12 @@ mod tests {
197 fn goto_def_for_extern_crate() { 162 fn goto_def_for_extern_crate() {
198 check( 163 check(
199 r#" 164 r#"
200 //- /main.rs crate:main deps:std 165//- /main.rs crate:main deps:std
201 extern crate std$0; 166extern crate std$0;
202 //- /std/lib.rs crate:std 167//- /std/lib.rs crate:std
203 // empty 168// empty
204 //^ file 169//^ file
205 "#, 170"#,
206 ) 171 )
207 } 172 }
208 173
@@ -210,12 +175,12 @@ mod tests {
210 fn goto_def_for_renamed_extern_crate() { 175 fn goto_def_for_renamed_extern_crate() {
211 check( 176 check(
212 r#" 177 r#"
213 //- /main.rs crate:main deps:std 178//- /main.rs crate:main deps:std
214 extern crate std as abc$0; 179extern crate std as abc$0;
215 //- /std/lib.rs crate:std 180//- /std/lib.rs crate:std
216 // empty 181// empty
217 //^ file 182//^ file
218 "#, 183"#,
219 ) 184 )
220 } 185 }
221 186
@@ -302,13 +267,13 @@ fn bar() {
302 fn goto_def_for_macros_from_other_crates() { 267 fn goto_def_for_macros_from_other_crates() {
303 check( 268 check(
304 r#" 269 r#"
305//- /lib.rs 270//- /lib.rs crate:main deps:foo
306use foo::foo; 271use foo::foo;
307fn bar() { 272fn bar() {
308 $0foo!(); 273 $0foo!();
309} 274}
310 275
311//- /foo/lib.rs 276//- /foo/lib.rs crate:foo
312#[macro_export] 277#[macro_export]
313macro_rules! foo { () => { () } } 278macro_rules! foo { () => { () } }
314 //^^^ 279 //^^^
@@ -320,10 +285,10 @@ macro_rules! foo { () => { () } }
320 fn goto_def_for_macros_in_use_tree() { 285 fn goto_def_for_macros_in_use_tree() {
321 check( 286 check(
322 r#" 287 r#"
323//- /lib.rs 288//- /lib.rs crate:main deps:foo
324use foo::foo$0; 289use foo::foo$0;
325 290
326//- /foo/lib.rs 291//- /foo/lib.rs crate:foo
327#[macro_export] 292#[macro_export]
328macro_rules! foo { () => { () } } 293macro_rules! foo { () => { () } }
329 //^^^ 294 //^^^
@@ -981,10 +946,10 @@ type Alias<T> = T$0;
981 fn goto_def_for_macro_container() { 946 fn goto_def_for_macro_container() {
982 check( 947 check(
983 r#" 948 r#"
984//- /lib.rs 949//- /lib.rs crate:main deps:foo
985foo::module$0::mac!(); 950foo::module$0::mac!();
986 951
987//- /foo/lib.rs 952//- /foo/lib.rs crate:foo
988pub mod module { 953pub mod module {
989 //^^^^^^ 954 //^^^^^^
990 #[macro_export] 955 #[macro_export]
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 6022bd275..d47a4cb0f 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -98,7 +98,6 @@ pub(crate) fn hover(
98 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), 98 ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)),
99 ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime) 99 ast::Lifetime(lifetime) => NameClass::classify_lifetime(&sema, &lifetime)
100 .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)), 100 .map_or_else(|| NameRefClass::classify_lifetime(&sema, &lifetime).map(|d| d.referenced(sema.db)), |d| d.defined(sema.db)),
101 ast::SelfParam(self_param) => NameClass::classify_self_param(&sema, &self_param).and_then(|d| d.defined(sema.db)),
102 _ => None, 101 _ => None,
103 } 102 }
104 }; 103 };
@@ -183,12 +182,7 @@ fn runnable_action(
183) -> Option<HoverAction> { 182) -> Option<HoverAction> {
184 match def { 183 match def {
185 Definition::ModuleDef(it) => match it { 184 Definition::ModuleDef(it) => match it {
186 ModuleDef::Module(it) => match it.definition_source(sema.db).value { 185 ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)),
187 ModuleSource::Module(it) => {
188 runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it))
189 }
190 _ => None,
191 },
192 ModuleDef::Function(func) => { 186 ModuleDef::Function(func) => {
193 let src = func.source(sema.db)?; 187 let src = func.source(sema.db)?;
194 if src.file_id != file_id.into() { 188 if src.file_id != file_id.into() {
@@ -327,6 +321,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
327 match it.definition_source(db).value { 321 match it.definition_source(db).value {
328 ModuleSource::Module(it) => it.short_label(), 322 ModuleSource::Module(it) => it.short_label(),
329 ModuleSource::SourceFile(it) => it.short_label(), 323 ModuleSource::SourceFile(it) => it.short_label(),
324 ModuleSource::BlockExpr(it) => it.short_label(),
330 }, 325 },
331 mod_path, 326 mod_path,
332 ), 327 ),
@@ -1831,6 +1826,35 @@ pub struct B$0ar
1831 "#]], 1826 "#]],
1832 ); 1827 );
1833 } 1828 }
1829 #[test]
1830 fn test_hover_intra_link_reference_to_trait_method() {
1831 check(
1832 r#"
1833pub trait Foo {
1834 fn buzz() -> usize;
1835}
1836/// [Foo][buzz]
1837///
1838/// [buzz]: Foo::buzz
1839pub struct B$0ar
1840"#,
1841 expect![[r#"
1842 *Bar*
1843
1844 ```rust
1845 test
1846 ```
1847
1848 ```rust
1849 pub struct Bar
1850 ```
1851
1852 ---
1853
1854 [Foo](https://docs.rs/test/*/test/trait.Foo.html#tymethod.buzz)
1855 "#]],
1856 );
1857 }
1834 1858
1835 #[test] 1859 #[test]
1836 fn test_hover_external_url() { 1860 fn test_hover_external_url() {
@@ -3223,7 +3247,7 @@ impl Foo {
3223} 3247}
3224"#, 3248"#,
3225 expect![[r#" 3249 expect![[r#"
3226 *&self* 3250 *self*
3227 3251
3228 ```rust 3252 ```rust
3229 &Foo 3253 &Foo
@@ -3243,7 +3267,7 @@ impl Foo {
3243} 3267}
3244"#, 3268"#,
3245 expect![[r#" 3269 expect![[r#"
3246 *self: Arc<Foo>* 3270 *self*
3247 3271
3248 ```rust 3272 ```rust
3249 Arc<Foo> 3273 Arc<Foo>
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index a2039fcc7..54485fd30 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -411,7 +411,7 @@ fn get_string_representation(expr: &ast::Expr) -> Option<String> {
411 match expr { 411 match expr {
412 ast::Expr::MethodCallExpr(method_call_expr) => { 412 ast::Expr::MethodCallExpr(method_call_expr) => {
413 let name_ref = method_call_expr.name_ref()?; 413 let name_ref = method_call_expr.name_ref()?;
414 match name_ref.text().as_str() { 414 match name_ref.text() {
415 "clone" => method_call_expr.receiver().map(|rec| rec.to_string()), 415 "clone" => method_call_expr.receiver().map(|rec| rec.to_string()),
416 name_ref => Some(name_ref.to_owned()), 416 name_ref => Some(name_ref.to_owned()),
417 } 417 }
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 981467c8d..631bde0f1 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -59,7 +59,7 @@ fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextS
59 // The node is either the first or the last in the file 59 // The node is either the first or the last in the file
60 let suff = &token.text()[TextRange::new( 60 let suff = &token.text()[TextRange::new(
61 offset - token.text_range().start() + TextSize::of('\n'), 61 offset - token.text_range().start() + TextSize::of('\n'),
62 TextSize::of(token.text().as_str()), 62 TextSize::of(token.text()),
63 )]; 63 )];
64 let spaces = suff.bytes().take_while(|&b| b == b' ').count(); 64 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
65 65
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index f8d69382e..567b8117e 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -86,18 +86,15 @@ pub use completion::{
86 InsertTextFormat, 86 InsertTextFormat,
87}; 87};
88pub use hir::{Documentation, Semantics}; 88pub use hir::{Documentation, Semantics};
89pub use ide_db::base_db::{
90 Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
91 SourceRootId,
92};
93pub use ide_db::{ 89pub use ide_db::{
90 base_db::{
91 Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange,
92 SourceRoot, SourceRootId,
93 },
94 call_info::CallInfo, 94 call_info::CallInfo,
95 search::{FileReference, ReferenceAccess, ReferenceKind},
96};
97pub use ide_db::{
98 label::Label, 95 label::Label,
99 line_index::{LineCol, LineIndex}, 96 line_index::{LineCol, LineIndex},
100 search::SearchScope, 97 search::{FileReference, ReferenceAccess, ReferenceKind, SearchScope},
101 source_change::{FileSystemEdit, SourceChange}, 98 source_change::{FileSystemEdit, SourceChange},
102 symbol_index::Query, 99 symbol_index::Query,
103 RootDatabase, 100 RootDatabase,
@@ -372,9 +369,7 @@ impl Analysis {
372 position: FilePosition, 369 position: FilePosition,
373 search_scope: Option<SearchScope>, 370 search_scope: Option<SearchScope>,
374 ) -> Cancelable<Option<ReferenceSearchResult>> { 371 ) -> Cancelable<Option<ReferenceSearchResult>> {
375 self.with_db(|db| { 372 self.with_db(|db| references::find_all_refs(&Semantics::new(db), position, search_scope))
376 references::find_all_refs(&Semantics::new(db), position, search_scope).map(|it| it.info)
377 })
378 } 373 }
379 374
380 /// Finds all methods and free functions for the file. Does not return tests! 375 /// Finds all methods and free functions for the file. Does not return tests!
@@ -481,6 +476,7 @@ impl Analysis {
481 position: FilePosition, 476 position: FilePosition,
482 full_import_path: &str, 477 full_import_path: &str,
483 imported_name: String, 478 imported_name: String,
479 import_for_trait_assoc_item: bool,
484 ) -> Cancelable<Vec<TextEdit>> { 480 ) -> Cancelable<Vec<TextEdit>> {
485 Ok(self 481 Ok(self
486 .with_db(|db| { 482 .with_db(|db| {
@@ -490,6 +486,7 @@ impl Analysis {
490 position, 486 position,
491 full_import_path, 487 full_import_path,
492 imported_name, 488 imported_name,
489 import_for_trait_assoc_item,
493 ) 490 )
494 })? 491 })?
495 .unwrap_or_default()) 492 .unwrap_or_default())
@@ -523,7 +520,7 @@ impl Analysis {
523 &self, 520 &self,
524 position: FilePosition, 521 position: FilePosition,
525 new_name: &str, 522 new_name: &str,
526 ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> { 523 ) -> Cancelable<Result<SourceChange, RenameError>> {
527 self.with_db(|db| references::rename::rename(db, position, new_name)) 524 self.with_db(|db| references::rename::rename(db, position, new_name))
528 } 525 }
529 526
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 51a2f4327..3a4f4d80b 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -25,7 +25,7 @@ use syntax::{
25 AstNode, SyntaxNode, TextRange, TokenAtOffset, T, 25 AstNode, SyntaxNode, TextRange, TokenAtOffset, T,
26}; 26};
27 27
28use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 28use crate::{display::TryToNav, FilePosition, NavigationTarget};
29 29
30#[derive(Debug, Clone)] 30#[derive(Debug, Clone)]
31pub struct ReferenceSearchResult { 31pub struct ReferenceSearchResult {
@@ -41,14 +41,6 @@ pub struct Declaration {
41} 41}
42 42
43impl ReferenceSearchResult { 43impl ReferenceSearchResult {
44 pub fn declaration(&self) -> &Declaration {
45 &self.declaration
46 }
47
48 pub fn decl_target(&self) -> &NavigationTarget {
49 &self.declaration.nav
50 }
51
52 pub fn references(&self) -> &UsageSearchResult { 44 pub fn references(&self) -> &UsageSearchResult {
53 &self.references 45 &self.references
54 } 46 }
@@ -87,7 +79,7 @@ pub(crate) fn find_all_refs(
87 sema: &Semantics<RootDatabase>, 79 sema: &Semantics<RootDatabase>,
88 position: FilePosition, 80 position: FilePosition,
89 search_scope: Option<SearchScope>, 81 search_scope: Option<SearchScope>,
90) -> Option<RangeInfo<ReferenceSearchResult>> { 82) -> Option<ReferenceSearchResult> {
91 let _p = profile::span("find_all_refs"); 83 let _p = profile::span("find_all_refs");
92 let syntax = sema.parse(position.file_id).syntax().clone(); 84 let syntax = sema.parse(position.file_id).syntax().clone();
93 85
@@ -105,7 +97,7 @@ pub(crate) fn find_all_refs(
105 ) 97 )
106 }; 98 };
107 99
108 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?; 100 let def = find_name(&sema, &syntax, position, opt_name)?;
109 101
110 let mut usages = def.usages(sema).set_scope(search_scope).all(); 102 let mut usages = def.usages(sema).set_scope(search_scope).all();
111 usages 103 usages
@@ -139,7 +131,7 @@ pub(crate) fn find_all_refs(
139 131
140 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; 132 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };
141 133
142 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references: usages })) 134 Some(ReferenceSearchResult { declaration, references: usages })
143} 135}
144 136
145fn find_name( 137fn find_name(
@@ -147,35 +139,27 @@ fn find_name(
147 syntax: &SyntaxNode, 139 syntax: &SyntaxNode,
148 position: FilePosition, 140 position: FilePosition,
149 opt_name: Option<ast::Name>, 141 opt_name: Option<ast::Name>,
150) -> Option<RangeInfo<Definition>> { 142) -> Option<Definition> {
151 if let Some(name) = opt_name { 143 let def = if let Some(name) = opt_name {
152 let def = NameClass::classify(sema, &name)?.referenced_or_defined(sema.db); 144 NameClass::classify(sema, &name)?.referenced_or_defined(sema.db)
153 let FileRange { range, .. } = sema.original_range(name.syntax()); 145 } else if let Some(lifetime) =
154 return Some(RangeInfo::new(range, def));
155 }
156
157 let (FileRange { range, .. }, def) = if let Some(lifetime) =
158 sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset) 146 sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset)
159 { 147 {
160 if let Some(def) = NameRefClass::classify_lifetime(sema, &lifetime) 148 if let Some(def) =
161 .map(|class| NameRefClass::referenced(class, sema.db)) 149 NameRefClass::classify_lifetime(sema, &lifetime).map(|class| class.referenced(sema.db))
162 { 150 {
163 (sema.original_range(lifetime.syntax()), def) 151 def
164 } else { 152 } else {
165 ( 153 NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db)
166 sema.original_range(lifetime.syntax()),
167 NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db),
168 )
169 } 154 }
155 } else if let Some(name_ref) =
156 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)
157 {
158 NameRefClass::classify(sema, &name_ref)?.referenced(sema.db)
170 } else { 159 } else {
171 let name_ref = 160 return None;
172 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
173 (
174 sema.original_range(name_ref.syntax()),
175 NameRefClass::classify(sema, &name_ref)?.referenced(sema.db),
176 )
177 }; 161 };
178 Some(RangeInfo::new(range, def)) 162 Some(def)
179} 163}
180 164
181fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { 165fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
@@ -930,6 +914,26 @@ impl Foo {
930 ); 914 );
931 } 915 }
932 916
917 #[test]
918 fn test_find_self_refs_decl() {
919 check(
920 r#"
921struct Foo { bar: i32 }
922
923impl Foo {
924 fn foo(self$0) {
925 self;
926 }
927}
928"#,
929 expect![[r#"
930 self SelfParam FileId(0) 47..51 47..51 SelfParam
931
932 FileId(0) 63..67 Other Read
933 "#]],
934 );
935 }
936
933 fn check(ra_fixture: &str, expect: Expect) { 937 fn check(ra_fixture: &str, expect: Expect) {
934 check_with_scope(ra_fixture, None, expect) 938 check_with_scope(ra_fixture, None, expect)
935 } 939 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 9ac4af026..c25bcce50 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,27 +1,25 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::{ 2use std::fmt::{self, Display};
3 convert::TryInto,
4 fmt::{self, Display},
5};
6 3
7use hir::{Module, ModuleDef, ModuleSource, Semantics}; 4use either::Either;
5use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics};
8use ide_db::{ 6use ide_db::{
9 base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}, 7 base_db::{AnchoredPathBuf, FileId, FileRange},
10 defs::{Definition, NameClass, NameRefClass}, 8 defs::{Definition, NameClass, NameRefClass},
11 search::FileReference, 9 search::FileReference,
12 RootDatabase, 10 RootDatabase,
13}; 11};
12use stdx::assert_never;
14use syntax::{ 13use syntax::{
15 algo::find_node_at_offset,
16 ast::{self, NameOwner}, 14 ast::{self, NameOwner},
17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T, 15 lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T,
18}; 16};
19use test_utils::mark; 17use test_utils::mark;
20use text_edit::TextEdit; 18use text_edit::TextEdit;
21 19
22use crate::{ 20use crate::{
23 FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, 21 display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, SourceChange,
24 TextRange, TextSize, 22 TextRange,
25}; 23};
26 24
27type RenameResult<T> = Result<T, RenameError>; 25type RenameResult<T> = Result<T, RenameError>;
@@ -50,24 +48,22 @@ pub(crate) fn prepare_rename(
50 let sema = Semantics::new(db); 48 let sema = Semantics::new(db);
51 let source_file = sema.parse(position.file_id); 49 let source_file = sema.parse(position.file_id);
52 let syntax = source_file.syntax(); 50 let syntax = source_file.syntax();
53 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 51 let range = match &find_name_like(&sema, &syntax, position)
54 rename_mod(&sema, position, module, "dummy") 52 .ok_or_else(|| format_err!("No references found at position"))?
55 } else if let Some(self_token) =
56 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
57 { 53 {
58 rename_self_to_param(&sema, position, self_token, "dummy") 54 NameLike::Name(it) => it.syntax(),
59 } else { 55 NameLike::NameRef(it) => it.syntax(),
60 let RangeInfo { range, .. } = find_all_refs(&sema, position)?; 56 NameLike::Lifetime(it) => it.syntax(),
61 Ok(RangeInfo::new(range, SourceChange::default()))
62 } 57 }
63 .map(|info| RangeInfo::new(info.range, ())) 58 .text_range();
59 Ok(RangeInfo::new(range, ()))
64} 60}
65 61
66pub(crate) fn rename( 62pub(crate) fn rename(
67 db: &RootDatabase, 63 db: &RootDatabase,
68 position: FilePosition, 64 position: FilePosition,
69 new_name: &str, 65 new_name: &str,
70) -> RenameResult<RangeInfo<SourceChange>> { 66) -> RenameResult<SourceChange> {
71 let sema = Semantics::new(db); 67 let sema = Semantics::new(db);
72 rename_with_semantics(&sema, position, new_name) 68 rename_with_semantics(&sema, position, new_name)
73} 69}
@@ -76,18 +72,15 @@ pub(crate) fn rename_with_semantics(
76 sema: &Semantics<RootDatabase>, 72 sema: &Semantics<RootDatabase>,
77 position: FilePosition, 73 position: FilePosition,
78 new_name: &str, 74 new_name: &str,
79) -> RenameResult<RangeInfo<SourceChange>> { 75) -> RenameResult<SourceChange> {
80 let source_file = sema.parse(position.file_id); 76 let source_file = sema.parse(position.file_id);
81 let syntax = source_file.syntax(); 77 let syntax = source_file.syntax();
82 78
83 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 79 let def = find_definition(sema, syntax, position)
84 rename_mod(&sema, position, module, new_name) 80 .ok_or_else(|| format_err!("No references found at position"))?;
85 } else if let Some(self_token) = 81 match def {
86 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self]) 82 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
87 { 83 def => rename_reference(sema, def, new_name),
88 rename_self_to_param(&sema, position, self_token, new_name)
89 } else {
90 rename_reference(&sema, position, new_name)
91 } 84 }
92} 85}
93 86
@@ -98,17 +91,12 @@ pub(crate) fn will_rename_file(
98) -> Option<SourceChange> { 91) -> Option<SourceChange> {
99 let sema = Semantics::new(db); 92 let sema = Semantics::new(db);
100 let module = sema.to_module_def(file_id)?; 93 let module = sema.to_module_def(file_id)?;
101 94 let mut change = rename_mod(&sema, module, new_name_stem).ok()?;
102 let decl = module.declaration_source(db)?;
103 let range = decl.value.name()?.syntax().text_range();
104
105 let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() };
106 let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info;
107 change.file_system_edits.clear(); 95 change.file_system_edits.clear();
108 Some(change) 96 Some(change)
109} 97}
110 98
111#[derive(Debug, PartialEq)] 99#[derive(Copy, Clone, Debug, PartialEq)]
112enum IdentifierKind { 100enum IdentifierKind {
113 Ident, 101 Ident,
114 Lifetime, 102 Lifetime,
@@ -135,40 +123,51 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
135 } 123 }
136} 124}
137 125
138fn find_module_at_offset( 126enum NameLike {
127 Name(ast::Name),
128 NameRef(ast::NameRef),
129 Lifetime(ast::Lifetime),
130}
131
132fn find_name_like(
139 sema: &Semantics<RootDatabase>, 133 sema: &Semantics<RootDatabase>,
140 position: FilePosition,
141 syntax: &SyntaxNode, 134 syntax: &SyntaxNode,
142) -> Option<Module> { 135 position: FilePosition,
143 let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; 136) -> Option<NameLike> {
144 137 let namelike = if let Some(name_ref) =
145 let module = match_ast! { 138 sema.find_node_at_offset_with_descend::<ast::NameRef>(syntax, position.offset)
146 match (ident.parent()) { 139 {
147 ast::NameRef(name_ref) => { 140 NameLike::NameRef(name_ref)
148 match NameRefClass::classify(sema, &name_ref)? { 141 } else if let Some(name) =
149 NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, 142 sema.find_node_at_offset_with_descend::<ast::Name>(syntax, position.offset)
150 _ => return None, 143 {
151 } 144 NameLike::Name(name)
152 }, 145 } else if let Some(lifetime) =
153 ast::Name(name) => { 146 sema.find_node_at_offset_with_descend::<ast::Lifetime>(syntax, position.offset)
154 match NameClass::classify(&sema, &name)? { 147 {
155 NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, 148 NameLike::Lifetime(lifetime)
156 _ => return None, 149 } else {
157 } 150 return None;
158 },
159 _ => return None,
160 }
161 }; 151 };
162 152 Some(namelike)
163 Some(module)
164} 153}
165 154
166fn find_all_refs( 155fn find_definition(
167 sema: &Semantics<RootDatabase>, 156 sema: &Semantics<RootDatabase>,
157 syntax: &SyntaxNode,
168 position: FilePosition, 158 position: FilePosition,
169) -> RenameResult<RangeInfo<ReferenceSearchResult>> { 159) -> Option<Definition> {
170 crate::references::find_all_refs(sema, position, None) 160 let def = match find_name_like(sema, syntax, position)? {
171 .ok_or_else(|| format_err!("No references found at position")) 161 NameLike::Name(name) => NameClass::classify(sema, &name)?.referenced_or_defined(sema.db),
162 NameLike::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref)?.referenced(sema.db),
163 NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime)
164 .map(|class| NameRefClass::referenced(class, sema.db))
165 .or_else(|| {
166 NameClass::classify_lifetime(sema, &lifetime)
167 .map(|it| it.referenced_or_defined(sema.db))
168 })?,
169 };
170 Some(def)
172} 171}
173 172
174fn source_edit_from_references( 173fn source_edit_from_references(
@@ -242,72 +241,84 @@ fn edit_text_range_for_record_field_expr_or_pat(
242 241
243fn rename_mod( 242fn rename_mod(
244 sema: &Semantics<RootDatabase>, 243 sema: &Semantics<RootDatabase>,
245 position: FilePosition,
246 module: Module, 244 module: Module,
247 new_name: &str, 245 new_name: &str,
248) -> RenameResult<RangeInfo<SourceChange>> { 246) -> RenameResult<SourceChange> {
249 if IdentifierKind::Ident != check_identifier(new_name)? { 247 if IdentifierKind::Ident != check_identifier(new_name)? {
250 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); 248 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
251 } 249 }
252 250
253 let mut source_change = SourceChange::default(); 251 let mut source_change = SourceChange::default();
254 252
255 let src = module.definition_source(sema.db); 253 let InFile { file_id, value: def_source } = module.definition_source(sema.db);
256 let file_id = src.file_id.original_file(sema.db); 254 let file_id = file_id.original_file(sema.db);
257 match src.value { 255 if let ModuleSource::SourceFile(..) = def_source {
258 ModuleSource::SourceFile(..) => { 256 // mod is defined in path/to/dir/mod.rs
259 // mod is defined in path/to/dir/mod.rs 257 let path = if module.is_mod_rs(sema.db) {
260 let path = if module.is_mod_rs(sema.db) { 258 format!("../{}/mod.rs", new_name)
261 format!("../{}/mod.rs", new_name) 259 } else {
262 } else { 260 format!("{}.rs", new_name)
263 format!("{}.rs", new_name) 261 };
264 }; 262 let dst = AnchoredPathBuf { anchor: file_id, path };
265 let dst = AnchoredPathBuf { anchor: file_id, path }; 263 let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
266 let move_file = FileSystemEdit::MoveFile { src: file_id, dst }; 264 source_change.push_file_system_edit(move_file);
267 source_change.push_file_system_edit(move_file); 265 }
268 } 266
269 ModuleSource::Module(..) => {} 267 if let Some(InFile { file_id, value: decl_source }) = module.declaration_source(sema.db) {
270 } 268 let file_id = file_id.original_file(sema.db);
271 269 match decl_source.name() {
272 if let Some(src) = module.declaration_source(sema.db) { 270 Some(name) => source_change.insert_source_edit(
273 let file_id = src.file_id.original_file(sema.db); 271 file_id,
274 let name = src.value.name().unwrap(); 272 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
275 source_change.insert_source_edit( 273 ),
276 file_id, 274 _ => unreachable!(),
277 TextEdit::replace(name.syntax().text_range(), new_name.into()), 275 };
278 );
279 } 276 }
280 277 let def = Definition::ModuleDef(ModuleDef::Module(module));
281 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 278 let usages = def.usages(sema).all();
282 let ref_edits = refs.references().iter().map(|(&file_id, references)| { 279 let ref_edits = usages.iter().map(|(&file_id, references)| {
283 source_edit_from_references(sema, file_id, references, new_name) 280 source_edit_from_references(sema, file_id, references, new_name)
284 }); 281 });
285 source_change.extend(ref_edits); 282 source_change.extend(ref_edits);
286 283
287 Ok(RangeInfo::new(range, source_change)) 284 Ok(source_change)
288} 285}
289 286
290fn rename_to_self( 287fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
291 sema: &Semantics<RootDatabase>, 288 if assert_never!(local.is_self(sema.db)) {
292 position: FilePosition, 289 bail!("rename_to_self invoked on self");
293) -> Result<RangeInfo<SourceChange>, RenameError> { 290 }
294 let source_file = sema.parse(position.file_id); 291
295 let syn = source_file.syntax(); 292 let fn_def = match local.parent(sema.db) {
293 hir::DefWithBody::Function(func) => func,
294 _ => bail!("Cannot rename non-param local to self"),
295 };
296
297 // FIXME: reimplement this on the hir instead
298 // as of the time of this writing params in hir don't keep their names
299 let fn_ast =
300 fn_def.source(sema.db).ok_or(format_err!("Cannot rename non-param local to self"))?.value;
296 301
297 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 302 let first_param_range = fn_ast
298 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
299 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
300 let param_range = fn_ast
301 .param_list() 303 .param_list()
302 .and_then(|p| p.params().next()) 304 .and_then(|p| p.params().next())
303 .ok_or_else(|| format_err!("Method has no parameters"))? 305 .ok_or_else(|| format_err!("Method has no parameters"))?
304 .syntax() 306 .syntax()
305 .text_range(); 307 .text_range();
306 if !param_range.contains(position.offset) { 308 let InFile { file_id, value: local_source } = local.source(sema.db);
307 bail!("Only the first parameter can be self"); 309 match local_source {
310 either::Either::Left(pat)
311 if !first_param_range.contains_range(pat.syntax().text_range()) =>
312 {
313 bail!("Only the first parameter can be self");
314 }
315 _ => (),
308 } 316 }
309 317
310 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 318 let impl_block = fn_ast
319 .syntax()
320 .ancestors()
321 .find_map(|node| ast::Impl::cast(node))
311 .and_then(|def| sema.to_def(&def)) 322 .and_then(|def| sema.to_def(&def))
312 .ok_or_else(|| format_err!("No impl block found for function"))?; 323 .ok_or_else(|| format_err!("No impl block found for function"))?;
313 if fn_def.self_param(sema.db).is_some() { 324 if fn_def.self_param(sema.db).is_some() {
@@ -331,25 +342,21 @@ fn rename_to_self(
331 bail!("Parameter type differs from impl block type"); 342 bail!("Parameter type differs from impl block type");
332 } 343 }
333 344
334 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; 345 let def = Definition::Local(local);
335 346 let usages = def.usages(sema).all();
336 let mut source_change = SourceChange::default(); 347 let mut source_change = SourceChange::default();
337 source_change.extend(refs.references().iter().map(|(&file_id, references)| { 348 source_change.extend(usages.iter().map(|(&file_id, references)| {
338 source_edit_from_references(sema, file_id, references, "self") 349 source_edit_from_references(sema, file_id, references, "self")
339 })); 350 }));
340 source_change.insert_source_edit( 351 source_change.insert_source_edit(
341 position.file_id, 352 file_id.original_file(sema.db),
342 TextEdit::replace(param_range, String::from(self_param)), 353 TextEdit::replace(first_param_range, String::from(self_param)),
343 ); 354 );
344 355
345 Ok(RangeInfo::new(range, source_change)) 356 Ok(source_change)
346} 357}
347 358
348fn text_edit_from_self_param( 359fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
349 syn: &SyntaxNode,
350 self_param: &ast::SelfParam,
351 new_name: &str,
352) -> Option<TextEdit> {
353 fn target_type_name(impl_def: &ast::Impl) -> Option<String> { 360 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
354 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { 361 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
355 return Some(p.path()?.segment()?.name_ref()?.text().to_string()); 362 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
@@ -357,7 +364,7 @@ fn text_edit_from_self_param(
357 None 364 None
358 } 365 }
359 366
360 let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; 367 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
361 let type_name = target_type_name(&impl_def)?; 368 let type_name = target_type_name(&impl_def)?;
362 369
363 let mut replacement_text = String::from(new_name); 370 let mut replacement_text = String::from(new_name);
@@ -374,96 +381,119 @@ fn text_edit_from_self_param(
374 381
375fn rename_self_to_param( 382fn rename_self_to_param(
376 sema: &Semantics<RootDatabase>, 383 sema: &Semantics<RootDatabase>,
377 position: FilePosition, 384 local: hir::Local,
378 self_token: SyntaxToken,
379 new_name: &str, 385 new_name: &str,
380) -> Result<RangeInfo<SourceChange>, RenameError> { 386 identifier_kind: IdentifierKind,
381 let ident_kind = check_identifier(new_name)?; 387) -> RenameResult<SourceChange> {
382 match ident_kind { 388 let (file_id, self_param) = match local.source(sema.db) {
383 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), 389 InFile { file_id, value: Either::Right(self_param) } => (file_id, self_param),
384 IdentifierKind::ToSelf => { 390 _ => {
385 // no-op 391 assert_never!(true, "rename_self_to_param invoked on a non-self local");
386 mark::hit!(rename_self_to_self); 392 bail!("rename_self_to_param invoked on a non-self local");
387 return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default()));
388 }
389 _ => (),
390 }
391 let source_file = sema.parse(position.file_id);
392 let syn = source_file.syntax();
393
394 let text = sema.db.file_text(position.file_id);
395 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
396 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
397 let search_range = fn_def.syntax().text_range();
398
399 let mut source_change = SourceChange::default();
400
401 for (idx, _) in text.match_indices("self") {
402 let offset: TextSize = idx.try_into().unwrap();
403 if !search_range.contains_inclusive(offset) {
404 continue;
405 } 393 }
406 if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) { 394 };
407 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
408 text_edit_from_self_param(syn, self_param, new_name)
409 .ok_or_else(|| format_err!("No target type found"))?
410 } else {
411 TextEdit::replace(usage.text_range(), String::from(new_name))
412 };
413 source_change.insert_source_edit(position.file_id, edit);
414 }
415 }
416 395
417 if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore { 396 let def = Definition::Local(local);
397 let usages = def.usages(sema).all();
398 let edit = text_edit_from_self_param(&self_param, new_name)
399 .ok_or_else(|| format_err!("No target type found"))?;
400 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {
418 bail!("Cannot rename reference to `_` as it is being referenced multiple times"); 401 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
419 } 402 }
420 403 let mut source_change = SourceChange::default();
421 let range = ast::SelfParam::cast(self_token.parent()) 404 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
422 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 405 source_change.extend(usages.iter().map(|(&file_id, references)| {
423 406 source_edit_from_references(sema, file_id, &references, new_name)
424 Ok(RangeInfo::new(range, source_change)) 407 }));
408 Ok(source_change)
425} 409}
426 410
427fn rename_reference( 411fn rename_reference(
428 sema: &Semantics<RootDatabase>, 412 sema: &Semantics<RootDatabase>,
429 position: FilePosition, 413 def: Definition,
430 new_name: &str, 414 new_name: &str,
431) -> Result<RangeInfo<SourceChange>, RenameError> { 415) -> RenameResult<SourceChange> {
432 let ident_kind = check_identifier(new_name)?; 416 let ident_kind = check_identifier(new_name)?;
433 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
434 417
435 match (ident_kind, &refs.declaration.kind) { 418 let def_is_lbl_or_lt = matches!(def,
436 (IdentifierKind::ToSelf, ReferenceKind::Lifetime) 419 Definition::GenericParam(hir::GenericParam::LifetimeParam(_))
437 | (IdentifierKind::Underscore, ReferenceKind::Lifetime) 420 | Definition::Label(_)
438 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { 421 );
422 match (ident_kind, def) {
423 (IdentifierKind::ToSelf, _)
424 | (IdentifierKind::Underscore, _)
425 | (IdentifierKind::Ident, _)
426 if def_is_lbl_or_lt =>
427 {
439 mark::hit!(rename_not_a_lifetime_ident_ref); 428 mark::hit!(rename_not_a_lifetime_ident_ref);
440 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 429 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
441 } 430 }
442 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), 431 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
443 (IdentifierKind::Lifetime, _) => { 432 (IdentifierKind::Lifetime, _) => {
444 mark::hit!(rename_not_an_ident_ref); 433 mark::hit!(rename_not_an_ident_ref);
445 bail!("Invalid name `{}`: not an identifier", new_name) 434 bail!("Invalid name `{}`: not an identifier", new_name)
446 } 435 }
447 (IdentifierKind::ToSelf, ReferenceKind::SelfParam) => { 436 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
448 unreachable!("rename_self_to_param should've been called instead") 437 // no-op
438 mark::hit!(rename_self_to_self);
439 return Ok(SourceChange::default());
449 } 440 }
450 (IdentifierKind::ToSelf, _) => { 441 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
451 mark::hit!(rename_to_self); 442 mark::hit!(rename_self_to_param);
452 return rename_to_self(sema, position); 443 return rename_self_to_param(sema, local, new_name, ident_kind);
453 } 444 }
454 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => { 445 (IdentifierKind::ToSelf, Definition::Local(local)) => {
455 mark::hit!(rename_underscore_multiple); 446 mark::hit!(rename_to_self);
456 bail!("Cannot rename reference to `_` as it is being referenced multiple times") 447 return rename_to_self(sema, local);
457 } 448 }
449 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
458 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 450 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
459 } 451 }
460 452
453 let usages = def.usages(sema).all();
454 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
455 mark::hit!(rename_underscore_multiple);
456 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
457 }
461 let mut source_change = SourceChange::default(); 458 let mut source_change = SourceChange::default();
462 source_change.extend(refs.into_iter().map(|(file_id, references)| { 459 source_change.extend(usages.iter().map(|(&file_id, references)| {
463 source_edit_from_references(sema, file_id, &references, new_name) 460 source_edit_from_references(sema, file_id, &references, new_name)
464 })); 461 }));
465 462
466 Ok(RangeInfo::new(range, source_change)) 463 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
464 source_change.insert_source_edit(file_id, edit);
465 Ok(source_change)
466}
467
468fn source_edit_from_def(
469 sema: &Semantics<RootDatabase>,
470 def: Definition,
471 new_name: &str,
472) -> RenameResult<(FileId, TextEdit)> {
473 let nav = def.try_to_nav(sema.db).unwrap();
474
475 let mut replacement_text = String::new();
476 let mut repl_range = nav.focus_or_full_range();
477 if let Definition::Local(local) = def {
478 if let Either::Left(pat) = local.source(sema.db).value {
479 if matches!(
480 pat.syntax().parent().and_then(ast::RecordPatField::cast),
481 Some(pat_field) if pat_field.name_ref().is_none()
482 ) {
483 replacement_text.push_str(": ");
484 replacement_text.push_str(new_name);
485 repl_range = TextRange::new(
486 pat.syntax().text_range().end(),
487 pat.syntax().text_range().end(),
488 );
489 }
490 }
491 }
492 if replacement_text.is_empty() {
493 replacement_text.push_str(new_name);
494 }
495 let edit = TextEdit::replace(repl_range, replacement_text);
496 Ok((nav.file_id, edit))
467} 497}
468 498
469#[cfg(test)] 499#[cfg(test)]
@@ -485,7 +515,7 @@ mod tests {
485 Ok(source_change) => { 515 Ok(source_change) => {
486 let mut text_edit_builder = TextEdit::builder(); 516 let mut text_edit_builder = TextEdit::builder();
487 let mut file_id: Option<FileId> = None; 517 let mut file_id: Option<FileId> = None;
488 for edit in source_change.info.source_file_edits { 518 for edit in source_change.source_file_edits {
489 file_id = Some(edit.0); 519 file_id = Some(edit.0);
490 for indel in edit.1.into_iter() { 520 for indel in edit.1.into_iter() {
491 text_edit_builder.replace(indel.delete, indel.insert); 521 text_edit_builder.replace(indel.delete, indel.insert);
@@ -884,36 +914,33 @@ mod foo$0;
884// empty 914// empty
885"#, 915"#,
886 expect![[r#" 916 expect![[r#"
887 RangeInfo { 917 SourceChange {
888 range: 4..7, 918 source_file_edits: {
889 info: SourceChange { 919 FileId(
890 source_file_edits: { 920 1,
891 FileId( 921 ): TextEdit {
892 1, 922 indels: [
893 ): TextEdit { 923 Indel {
894 indels: [ 924 insert: "foo2",
895 Indel { 925 delete: 4..7,
896 insert: "foo2", 926 },
897 delete: 4..7, 927 ],
898 },
899 ],
900 },
901 }, 928 },
902 file_system_edits: [ 929 },
903 MoveFile { 930 file_system_edits: [
904 src: FileId( 931 MoveFile {
932 src: FileId(
933 2,
934 ),
935 dst: AnchoredPathBuf {
936 anchor: FileId(
905 2, 937 2,
906 ), 938 ),
907 dst: AnchoredPathBuf { 939 path: "foo2.rs",
908 anchor: FileId(
909 2,
910 ),
911 path: "foo2.rs",
912 },
913 }, 940 },
914 ], 941 },
915 is_snippet: false, 942 ],
916 }, 943 is_snippet: false,
917 } 944 }
918 "#]], 945 "#]],
919 ); 946 );
@@ -936,46 +963,43 @@ pub struct FooContent;
936use crate::foo$0::FooContent; 963use crate::foo$0::FooContent;
937"#, 964"#,
938 expect![[r#" 965 expect![[r#"
939 RangeInfo { 966 SourceChange {
940 range: 11..14, 967 source_file_edits: {
941 info: SourceChange { 968 FileId(
942 source_file_edits: { 969 0,
943 FileId( 970 ): TextEdit {
944 0, 971 indels: [
945 ): TextEdit { 972 Indel {
946 indels: [ 973 insert: "quux",
947 Indel { 974 delete: 8..11,
948 insert: "quux", 975 },
949 delete: 8..11, 976 ],
950 }, 977 },
951 ], 978 FileId(
952 }, 979 2,
953 FileId( 980 ): TextEdit {
954 2, 981 indels: [
955 ): TextEdit { 982 Indel {
956 indels: [ 983 insert: "quux",
957 Indel { 984 delete: 11..14,
958 insert: "quux", 985 },
959 delete: 11..14, 986 ],
960 },
961 ],
962 },
963 }, 987 },
964 file_system_edits: [ 988 },
965 MoveFile { 989 file_system_edits: [
966 src: FileId( 990 MoveFile {
991 src: FileId(
992 1,
993 ),
994 dst: AnchoredPathBuf {
995 anchor: FileId(
967 1, 996 1,
968 ), 997 ),
969 dst: AnchoredPathBuf { 998 path: "quux.rs",
970 anchor: FileId(
971 1,
972 ),
973 path: "quux.rs",
974 },
975 }, 999 },
976 ], 1000 },
977 is_snippet: false, 1001 ],
978 }, 1002 is_snippet: false,
979 } 1003 }
980 "#]], 1004 "#]],
981 ); 1005 );
@@ -992,36 +1016,33 @@ mod fo$0o;
992// empty 1016// empty
993"#, 1017"#,
994 expect![[r#" 1018 expect![[r#"
995 RangeInfo { 1019 SourceChange {
996 range: 4..7, 1020 source_file_edits: {
997 info: SourceChange { 1021 FileId(
998 source_file_edits: { 1022 0,
999 FileId( 1023 ): TextEdit {
1000 0, 1024 indels: [
1001 ): TextEdit { 1025 Indel {
1002 indels: [ 1026 insert: "foo2",
1003 Indel { 1027 delete: 4..7,
1004 insert: "foo2", 1028 },
1005 delete: 4..7, 1029 ],
1006 },
1007 ],
1008 },
1009 }, 1030 },
1010 file_system_edits: [ 1031 },
1011 MoveFile { 1032 file_system_edits: [
1012 src: FileId( 1033 MoveFile {
1034 src: FileId(
1035 1,
1036 ),
1037 dst: AnchoredPathBuf {
1038 anchor: FileId(
1013 1, 1039 1,
1014 ), 1040 ),
1015 dst: AnchoredPathBuf { 1041 path: "../foo2/mod.rs",
1016 anchor: FileId(
1017 1,
1018 ),
1019 path: "../foo2/mod.rs",
1020 },
1021 }, 1042 },
1022 ], 1043 },
1023 is_snippet: false, 1044 ],
1024 }, 1045 is_snippet: false,
1025 } 1046 }
1026 "#]], 1047 "#]],
1027 ); 1048 );
@@ -1039,36 +1060,33 @@ mod outer { mod fo$0o; }
1039// empty 1060// empty
1040"#, 1061"#,
1041 expect![[r#" 1062 expect![[r#"
1042 RangeInfo { 1063 SourceChange {
1043 range: 16..19, 1064 source_file_edits: {
1044 info: SourceChange { 1065 FileId(
1045 source_file_edits: { 1066 0,
1046 FileId( 1067 ): TextEdit {
1047 0, 1068 indels: [
1048 ): TextEdit { 1069 Indel {
1049 indels: [ 1070 insert: "bar",
1050 Indel { 1071 delete: 16..19,
1051 insert: "bar", 1072 },
1052 delete: 16..19, 1073 ],
1053 },
1054 ],
1055 },
1056 }, 1074 },
1057 file_system_edits: [ 1075 },
1058 MoveFile { 1076 file_system_edits: [
1059 src: FileId( 1077 MoveFile {
1078 src: FileId(
1079 1,
1080 ),
1081 dst: AnchoredPathBuf {
1082 anchor: FileId(
1060 1, 1083 1,
1061 ), 1084 ),
1062 dst: AnchoredPathBuf { 1085 path: "bar.rs",
1063 anchor: FileId(
1064 1,
1065 ),
1066 path: "bar.rs",
1067 },
1068 }, 1086 },
1069 ], 1087 },
1070 is_snippet: false, 1088 ],
1071 }, 1089 is_snippet: false,
1072 } 1090 }
1073 "#]], 1091 "#]],
1074 ); 1092 );
@@ -1109,46 +1127,43 @@ pub mod foo$0;
1109// pub fn fun() {} 1127// pub fn fun() {}
1110"#, 1128"#,
1111 expect![[r#" 1129 expect![[r#"
1112 RangeInfo { 1130 SourceChange {
1113 range: 8..11, 1131 source_file_edits: {
1114 info: SourceChange { 1132 FileId(
1115 source_file_edits: { 1133 0,
1116 FileId( 1134 ): TextEdit {
1117 0, 1135 indels: [
1118 ): TextEdit { 1136 Indel {
1119 indels: [ 1137 insert: "foo2",
1120 Indel { 1138 delete: 27..30,
1121 insert: "foo2", 1139 },
1122 delete: 27..30, 1140 ],
1123 },
1124 ],
1125 },
1126 FileId(
1127 1,
1128 ): TextEdit {
1129 indels: [
1130 Indel {
1131 insert: "foo2",
1132 delete: 8..11,
1133 },
1134 ],
1135 },
1136 }, 1141 },
1137 file_system_edits: [ 1142 FileId(
1138 MoveFile { 1143 1,
1139 src: FileId( 1144 ): TextEdit {
1145 indels: [
1146 Indel {
1147 insert: "foo2",
1148 delete: 8..11,
1149 },
1150 ],
1151 },
1152 },
1153 file_system_edits: [
1154 MoveFile {
1155 src: FileId(
1156 2,
1157 ),
1158 dst: AnchoredPathBuf {
1159 anchor: FileId(
1140 2, 1160 2,
1141 ), 1161 ),
1142 dst: AnchoredPathBuf { 1162 path: "foo2.rs",
1143 anchor: FileId(
1144 2,
1145 ),
1146 path: "foo2.rs",
1147 },
1148 }, 1163 },
1149 ], 1164 },
1150 is_snippet: false, 1165 ],
1151 }, 1166 is_snippet: false,
1152 } 1167 }
1153 "#]], 1168 "#]],
1154 ); 1169 );
@@ -1350,6 +1365,7 @@ impl Foo {
1350 1365
1351 #[test] 1366 #[test]
1352 fn test_owned_self_to_parameter() { 1367 fn test_owned_self_to_parameter() {
1368 mark::check!(rename_self_to_param);
1353 check( 1369 check(
1354 "foo", 1370 "foo",
1355 r#" 1371 r#"
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index f5ee7de86..975abf47f 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -6,9 +6,10 @@ use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
6use ide_db::{defs::Definition, 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},
10 match_ast, SyntaxNode, 10 match_ast, SyntaxNode,
11}; 11};
12use test_utils::mark;
12 13
13use crate::{ 14use crate::{
14 display::{ToNav, TryToNav}, 15 display::{ToNav, TryToNav},
@@ -95,27 +96,45 @@ impl Runnable {
95// |=== 96// |===
96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 97pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
97 let sema = Semantics::new(db); 98 let sema = Semantics::new(db);
98 let source_file = sema.parse(file_id); 99 let module = match sema.to_module_def(file_id) {
99 source_file 100 None => return Vec::new(),
100 .syntax() 101 Some(it) => it,
101 .descendants() 102 };
102 .filter_map(|item| { 103
103 let runnable = match_ast! { 104 let mut res = Vec::new();
104 match item { 105 runnables_mod(&sema, &mut res, module);
105 ast::Fn(func) => { 106 res
106 let def = sema.to_def(&func)?; 107}
107 runnable_fn(&sema, def) 108
108 }, 109fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) {
109 ast::Module(it) => runnable_mod(&sema, it), 110 acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| {
110 _ => None, 111 let runnable = match def {
111 } 112 hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
112 }; 113 hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
113 runnable.or_else(|| match doc_owner_to_def(&sema, item)? { 114 _ => None,
114 Definition::ModuleDef(def) => module_def_doctest(&sema, def), 115 };
115 _ => None, 116 runnable.or_else(|| module_def_doctest(&sema, def))
116 }) 117 }));
117 }) 118
118 .collect() 119 acc.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map(
120 |def| match def {
121 hir::AssocItem::Function(it) => {
122 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
123 }
124 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
125 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
126 },
127 ));
128
129 for def in module.declarations(sema.db) {
130 if let hir::ModuleDef::Module(submodule) = def {
131 match submodule.definition_source(sema.db).value {
132 hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule),
133 hir::ModuleSource::SourceFile(_) => mark::hit!(dont_recurse_in_outline_submodules),
134 hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable
135 }
136 }
137 }
119} 138}
120 139
121pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { 140pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
@@ -150,26 +169,16 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
150 Some(Runnable { nav, kind, cfg }) 169 Some(Runnable { nav, kind, cfg })
151} 170}
152 171
153pub(crate) fn runnable_mod( 172pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
154 sema: &Semantics<RootDatabase>, 173 if !has_test_function_or_multiple_test_submodules(sema, &def) {
155 module: ast::Module,
156) -> Option<Runnable> {
157 if !has_test_function_or_multiple_test_submodules(&module) {
158 return None; 174 return None;
159 } 175 }
160 let module_def = sema.to_def(&module)?; 176 let path =
177 def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
161 178
162 let path = module_def
163 .path_to_root(sema.db)
164 .into_iter()
165 .rev()
166 .filter_map(|it| it.name(sema.db))
167 .join("::");
168
169 let def = sema.to_def(&module)?;
170 let attrs = def.attrs(sema.db); 179 let attrs = def.attrs(sema.db);
171 let cfg = attrs.cfg(); 180 let cfg = attrs.cfg();
172 let nav = module_def.to_nav(sema.db); 181 let nav = def.to_nav(sema.db);
173 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) 182 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
174} 183}
175 184
@@ -289,35 +298,37 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
289 298
290// We could create runnables for modules with number_of_test_submodules > 0, 299// We could create runnables for modules with number_of_test_submodules > 0,
291// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already 300// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
292fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { 301fn has_test_function_or_multiple_test_submodules(
293 if let Some(item_list) = module.item_list() { 302 sema: &Semantics<RootDatabase>,
294 let mut number_of_test_submodules = 0; 303 module: &hir::Module,
295 304) -> bool {
296 for item in item_list.items() { 305 let mut number_of_test_submodules = 0;
297 match item { 306
298 ast::Item::Fn(f) => { 307 for item in module.declarations(sema.db) {
299 if test_related_attribute(&f).is_some() { 308 match item {
309 hir::ModuleDef::Function(f) => {
310 if let Some(it) = f.source(sema.db) {
311 if test_related_attribute(&it.value).is_some() {
300 return true; 312 return true;
301 } 313 }
302 } 314 }
303 ast::Item::Module(submodule) => { 315 }
304 if has_test_function_or_multiple_test_submodules(&submodule) { 316 hir::ModuleDef::Module(submodule) => {
305 number_of_test_submodules += 1; 317 if has_test_function_or_multiple_test_submodules(sema, &submodule) {
306 } 318 number_of_test_submodules += 1;
307 } 319 }
308 _ => (),
309 } 320 }
321 _ => (),
310 } 322 }
311
312 number_of_test_submodules > 1
313 } else {
314 false
315 } 323 }
324
325 number_of_test_submodules > 1
316} 326}
317 327
318#[cfg(test)] 328#[cfg(test)]
319mod tests { 329mod tests {
320 use expect_test::{expect, Expect}; 330 use expect_test::{expect, Expect};
331 use test_utils::mark;
321 332
322 use crate::fixture; 333 use crate::fixture;
323 334
@@ -753,6 +764,21 @@ mod root_tests {
753 file_id: FileId( 764 file_id: FileId(
754 0, 765 0,
755 ), 766 ),
767 full_range: 202..286,
768 focus_range: 206..220,
769 name: "nested_tests_2",
770 kind: Module,
771 },
772 kind: TestMod {
773 path: "root_tests::nested_tests_0::nested_tests_2",
774 },
775 cfg: None,
776 },
777 Runnable {
778 nav: NavigationTarget {
779 file_id: FileId(
780 0,
781 ),
756 full_range: 84..126, 782 full_range: 84..126,
757 focus_range: 107..121, 783 focus_range: 107..121,
758 name: "nested_test_11", 784 name: "nested_test_11",
@@ -793,21 +819,6 @@ mod root_tests {
793 file_id: FileId( 819 file_id: FileId(
794 0, 820 0,
795 ), 821 ),
796 full_range: 202..286,
797 focus_range: 206..220,
798 name: "nested_tests_2",
799 kind: Module,
800 },
801 kind: TestMod {
802 path: "root_tests::nested_tests_0::nested_tests_2",
803 },
804 cfg: None,
805 },
806 Runnable {
807 nav: NavigationTarget {
808 file_id: FileId(
809 0,
810 ),
811 full_range: 235..276, 822 full_range: 235..276,
812 focus_range: 258..271, 823 focus_range: 258..271,
813 name: "nested_test_2", 824 name: "nested_test_2",
@@ -982,4 +993,85 @@ impl Foo {
982 "#]], 993 "#]],
983 ); 994 );
984 } 995 }
996
997 #[test]
998 fn test_runnables_in_macro() {
999 check(
1000 r#"
1001//- /lib.rs
1002$0
1003macro_rules! gen {
1004 () => {
1005 #[test]
1006 fn foo_test() {
1007 }
1008 }
1009}
1010mod tests {
1011 gen!();
1012}
1013"#,
1014 &[&TEST, &TEST],
1015 expect![[r#"
1016 [
1017 Runnable {
1018 nav: NavigationTarget {
1019 file_id: FileId(
1020 0,
1021 ),
1022 full_range: 90..115,
1023 focus_range: 94..99,
1024 name: "tests",
1025 kind: Module,
1026 },
1027 kind: TestMod {
1028 path: "tests",
1029 },
1030 cfg: None,
1031 },
1032 Runnable {
1033 nav: NavigationTarget {
1034 file_id: FileId(
1035 0,
1036 ),
1037 full_range: 106..113,
1038 focus_range: 106..113,
1039 name: "foo_test",
1040 kind: Function,
1041 },
1042 kind: Test {
1043 test_id: Path(
1044 "tests::foo_test",
1045 ),
1046 attr: TestAttr {
1047 ignore: false,
1048 },
1049 },
1050 cfg: None,
1051 },
1052 ]
1053 "#]],
1054 );
1055 }
1056
1057 #[test]
1058 fn dont_recurse_in_outline_submodules() {
1059 mark::check!(dont_recurse_in_outline_submodules);
1060 check(
1061 r#"
1062//- /lib.rs
1063$0
1064mod m;
1065//- /m.rs
1066mod tests {
1067 #[test]
1068 fn t() {}
1069}
1070"#,
1071 &[],
1072 expect![[r#"
1073 []
1074 "#]],
1075 );
1076 }
985} 1077}
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs
index e10d7c3a4..137c38c0d 100644
--- a/crates/ide/src/status.rs
+++ b/crates/ide/src/status.rs
@@ -38,6 +38,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
38 format_to!(buf, "{}\n", syntax_tree_stats(db)); 38 format_to!(buf, "{}\n", syntax_tree_stats(db));
39 format_to!(buf, "{} (macros)\n", macro_syntax_tree_stats(db)); 39 format_to!(buf, "{} (macros)\n", macro_syntax_tree_stats(db));
40 format_to!(buf, "{} total\n", memory_usage()); 40 format_to!(buf, "{} total\n", memory_usage());
41 format_to!(buf, "\ncounts:\n{}", profile::countme::get_all());
41 42
42 if let Some(file_id) = file_id { 43 if let Some(file_id) = file_id {
43 format_to!(buf, "\nfile info:\n"); 44 format_to!(buf, "\nfile info:\n");
@@ -60,6 +61,7 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
60 None => format_to!(buf, "does not belong to any crate"), 61 None => format_to!(buf, "does not belong to any crate"),
61 } 62 }
62 } 63 }
64
63 buf 65 buf
64} 66}
65 67
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index a74ca844b..8a9b5ca8c 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -30,7 +30,7 @@ fn is_format_string(string: &ast::String) -> Option<()> {
30 let parent = string.syntax().parent(); 30 let parent = string.syntax().parent();
31 31
32 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?; 32 let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?;
33 if !matches!(name.text().as_str(), "format_args" | "format_args_nl") { 33 if !matches!(name.text(), "format_args" | "format_args_nl") {
34 return None; 34 return None;
35 } 35 }
36 36
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 87578e70a..8625ef5df 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -68,7 +68,8 @@ pub(super) fn element(
68 NAME_REF => { 68 NAME_REF => {
69 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 69 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
70 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { 70 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
71 match NameRefClass::classify(sema, &name_ref) { 71 let is_self = name_ref.self_token().is_some();
72 let h = match NameRefClass::classify(sema, &name_ref) {
72 Some(name_kind) => match name_kind { 73 Some(name_kind) => match name_kind {
73 NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), 74 NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(),
74 NameRefClass::Definition(def) => { 75 NameRefClass::Definition(def) => {
@@ -108,6 +109,11 @@ pub(super) fn element(
108 highlight_name_ref_by_syntax(name_ref, sema) 109 highlight_name_ref_by_syntax(name_ref, sema)
109 } 110 }
110 None => HlTag::UnresolvedReference.into(), 111 None => HlTag::UnresolvedReference.into(),
112 };
113 if h.tag == HlTag::Symbol(SymbolKind::Module) && is_self {
114 HlTag::Symbol(SymbolKind::SelfParam).into()
115 } else {
116 h
111 } 117 }
112 }) 118 })
113 } 119 }
@@ -225,18 +231,8 @@ pub(super) fn element(
225 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, 231 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow,
226 T![unsafe] => h | HlMod::Unsafe, 232 T![unsafe] => h | HlMod::Unsafe,
227 T![true] | T![false] => HlTag::BoolLiteral.into(), 233 T![true] | T![false] => HlTag::BoolLiteral.into(),
228 T![self] => { 234 // self is handled as either a Name or NameRef already
229 let self_param = element.parent().and_then(ast::SelfParam::cast); 235 T![self] => return None,
230 if let Some(NameClass::Definition(def)) = self_param
231 .and_then(|self_param| NameClass::classify_self_param(sema, &self_param))
232 {
233 highlight_def(db, def) | HlMod::Definition
234 } else if element.ancestors().any(|it| it.kind() == USE_TREE) {
235 HlTag::Symbol(SymbolKind::SelfParam).into()
236 } else {
237 return None;
238 }
239 }
240 T![ref] => element 236 T![ref] => element
241 .parent() 237 .parent()
242 .and_then(ast::IdentPat::cast) 238 .and_then(ast::IdentPat::cast)
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs
index 281461493..8cdc3688f 100644
--- a/crates/ide/src/syntax_highlighting/inject.rs
+++ b/crates/ide/src/syntax_highlighting/inject.rs
@@ -116,7 +116,7 @@ pub(super) fn doc_comment(hl: &mut Highlights, node: &SyntaxNode) {
116 None => (), 116 None => (),
117 } 117 }
118 118
119 let line: &str = comment.text().as_str(); 119 let line: &str = comment.text();
120 let range = comment.syntax().text_range(); 120 let range = comment.syntax().text_range();
121 121
122 let mut pos = TextSize::of(comment.prefix()); 122 let mut pos = TextSize::of(comment.prefix());
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index 1d4bac7ad..4c63d3023 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -111,7 +111,6 @@ mod tests {
111 let syn = analysis.syntax_tree(file_id, None).unwrap(); 111 let syn = analysis.syntax_tree(file_id, None).unwrap();
112 112
113 assert_eq_text!( 113 assert_eq_text!(
114 syn.trim(),
115 r#" 114 r#"
116[email protected] 115[email protected]
117 [email protected] 116 [email protected]
@@ -127,7 +126,8 @@ [email protected]
127 [email protected] "{" 126 [email protected] "{"
128 [email protected] "}" 127 [email protected] "}"
129"# 128"#
130 .trim() 129 .trim(),
130 syn.trim()
131 ); 131 );
132 132
133 let (analysis, file_id) = fixture::file( 133 let (analysis, file_id) = fixture::file(
@@ -143,7 +143,6 @@ fn test() {
143 let syn = analysis.syntax_tree(file_id, None).unwrap(); 143 let syn = analysis.syntax_tree(file_id, None).unwrap();
144 144
145 assert_eq_text!( 145 assert_eq_text!(
146 syn.trim(),
147 r#" 146 r#"
148[email protected] 147[email protected]
149 [email protected] 148 [email protected]
@@ -176,7 +175,8 @@ [email protected]
176 [email protected] "\n" 175 [email protected] "\n"
177 [email protected] "}" 176 [email protected] "}"
178"# 177"#
179 .trim() 178 .trim(),
179 syn.trim()
180 ); 180 );
181 } 181 }
182 182
@@ -186,7 +186,6 @@ [email protected]
186 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 186 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
187 187
188 assert_eq_text!( 188 assert_eq_text!(
189 syn.trim(),
190 r#" 189 r#"
191[email protected] 190[email protected]
192 [email protected] "fn" 191 [email protected] "fn"
@@ -201,7 +200,8 @@ [email protected]
201 [email protected] "{" 200 [email protected] "{"
202 [email protected] "}" 201 [email protected] "}"
203"# 202"#
204 .trim() 203 .trim(),
204 syn.trim()
205 ); 205 );
206 206
207 let (analysis, range) = fixture::range( 207 let (analysis, range) = fixture::range(
@@ -216,7 +216,6 @@ [email protected]
216 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 216 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
217 217
218 assert_eq_text!( 218 assert_eq_text!(
219 syn.trim(),
220 r#" 219 r#"
221[email protected] 220[email protected]
222 [email protected] 221 [email protected]
@@ -234,7 +233,8 @@ [email protected]
234 [email protected] ")" 233 [email protected] ")"
235 [email protected] ";" 234 [email protected] ";"
236"# 235"#
237 .trim() 236 .trim(),
237 syn.trim()
238 ); 238 );
239 } 239 }
240 240
@@ -253,7 +253,6 @@ fn bar() {
253 ); 253 );
254 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 254 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
255 assert_eq_text!( 255 assert_eq_text!(
256 syn.trim(),
257 r#" 256 r#"
258[email protected] 257[email protected]
259 [email protected] 258 [email protected]
@@ -270,7 +269,8 @@ [email protected]
270 [email protected] "\n" 269 [email protected] "\n"
271 [email protected] "}" 270 [email protected] "}"
272"# 271"#
273 .trim() 272 .trim(),
273 syn.trim()
274 ); 274 );
275 275
276 // With a raw string 276 // With a raw string
@@ -287,7 +287,6 @@ fn bar() {
287 ); 287 );
288 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 288 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
289 assert_eq_text!( 289 assert_eq_text!(
290 syn.trim(),
291 r#" 290 r#"
292[email protected] 291[email protected]
293 [email protected] 292 [email protected]
@@ -304,7 +303,8 @@ [email protected]
304 [email protected] "\n" 303 [email protected] "\n"
305 [email protected] "}" 304 [email protected] "}"
306"# 305"#
307 .trim() 306 .trim(),
307 syn.trim()
308 ); 308 );
309 309
310 // With a raw string 310 // With a raw string
@@ -320,7 +320,6 @@ fn bar() {
320 ); 320 );
321 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 321 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
322 assert_eq_text!( 322 assert_eq_text!(
323 syn.trim(),
324 r#" 323 r#"
325[email protected] 324[email protected]
326 [email protected] 325 [email protected]
@@ -351,7 +350,8 @@ [email protected]
351 [email protected] "\n" 350 [email protected] "\n"
352 [email protected] "}" 351 [email protected] "}"
353"# 352"#
354 .trim() 353 .trim(),
354 syn.trim()
355 ); 355 );
356 } 356 }
357} 357}