aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/display/navigation_target.rs65
-rw-r--r--crates/ide/src/doc_links.rs7
-rw-r--r--crates/ide/src/extend_selection.rs2
-rw-r--r--crates/ide/src/goto_definition.rs57
-rw-r--r--crates/ide/src/goto_implementation.rs8
-rw-r--r--crates/ide/src/hover.rs4
-rw-r--r--crates/ide/src/lib.rs7
-rw-r--r--crates/ide/src/references.rs91
-rw-r--r--crates/ide/src/references/rename.rs104
-rw-r--r--crates/ide/src/runnables.rs203
-rw-r--r--crates/ide/src/syntax_highlighting.rs1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html5
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs3
13 files changed, 374 insertions, 183 deletions
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 73fc73619..48acb8c93 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -1,15 +1,13 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use either::Either; 3use either::Either;
4use hir::{ 4use hir::{AssocItem, Documentation, FieldSource, HasAttrs, HasSource, InFile, ModuleSource};
5 AssocItem, Documentation, FieldSource, HasAttrs, HasSource, HirFileId, InFile, ModuleSource,
6};
7use ide_db::base_db::{FileId, SourceDatabase}; 5use ide_db::base_db::{FileId, SourceDatabase};
8use ide_db::{defs::Definition, RootDatabase}; 6use ide_db::{defs::Definition, RootDatabase};
9use syntax::{ 7use syntax::{
10 ast::{self, NameOwner}, 8 ast::{self, NameOwner},
11 match_ast, AstNode, SmolStr, 9 match_ast, AstNode, SmolStr,
12 SyntaxKind::{self, IDENT_PAT, TYPE_PARAM}, 10 SyntaxKind::{self, IDENT_PAT, LIFETIME_PARAM, TYPE_PARAM},
13 TextRange, 11 TextRange,
14}; 12};
15 13
@@ -119,25 +117,6 @@ impl NavigationTarget {
119 ) 117 )
120 } 118 }
121 119
122 /// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner`
123 pub(crate) fn from_doc_commented(
124 db: &RootDatabase,
125 named: InFile<&dyn ast::NameOwner>,
126 node: InFile<&dyn ast::DocCommentsOwner>,
127 ) -> NavigationTarget {
128 let name =
129 named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_"));
130 let frange = node.map(|it| it.syntax()).original_file_range(db);
131
132 NavigationTarget::from_syntax(
133 frange.file_id,
134 name,
135 None,
136 frange.range,
137 node.value.syntax().kind(),
138 )
139 }
140
141 fn from_syntax( 120 fn from_syntax(
142 file_id: FileId, 121 file_id: FileId,
143 name: SmolStr, 122 name: SmolStr,
@@ -168,7 +147,7 @@ impl ToNav for FileSymbol {
168 focus_range: self.name_range, 147 focus_range: self.name_range,
169 container_name: self.container_name.clone(), 148 container_name: self.container_name.clone(),
170 description: description_from_symbol(db, self), 149 description: description_from_symbol(db, self),
171 docs: docs_from_symbol(db, self), 150 docs: None,
172 } 151 }
173 } 152 }
174} 153}
@@ -190,6 +169,7 @@ impl TryToNav for Definition {
190 Definition::SelfType(it) => Some(it.to_nav(db)), 169 Definition::SelfType(it) => Some(it.to_nav(db)),
191 Definition::Local(it) => Some(it.to_nav(db)), 170 Definition::Local(it) => Some(it.to_nav(db)),
192 Definition::TypeParam(it) => Some(it.to_nav(db)), 171 Definition::TypeParam(it) => Some(it.to_nav(db)),
172 Definition::LifetimeParam(it) => Some(it.to_nav(db)),
193 } 173 }
194 } 174 }
195} 175}
@@ -252,7 +232,7 @@ impl ToNav for hir::Module {
252 } 232 }
253} 233}
254 234
255impl ToNav for hir::ImplDef { 235impl ToNav for hir::Impl {
256 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { 236 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
257 let src = self.source(db); 237 let src = self.source(db);
258 let derive_attr = self.is_builtin_derive(db); 238 let derive_attr = self.is_builtin_derive(db);
@@ -384,28 +364,21 @@ impl ToNav for hir::TypeParam {
384 } 364 }
385} 365}
386 366
387pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option<Documentation> { 367impl ToNav for hir::LifetimeParam {
388 let parse = db.parse(symbol.file_id); 368 fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
389 let node = symbol.ptr.to_node(parse.tree().syntax()); 369 let src = self.source(db);
390 let file_id = HirFileId::from(symbol.file_id); 370 let full_range = src.value.syntax().text_range();
391 371 NavigationTarget {
392 let it = match_ast! { 372 file_id: src.file_id.original_file(db),
393 match node { 373 name: self.name(db).to_string().into(),
394 ast::Fn(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)), 374 kind: LIFETIME_PARAM,
395 ast::Struct(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)), 375 full_range,
396 ast::Enum(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)), 376 focus_range: Some(full_range),
397 ast::Trait(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)), 377 container_name: None,
398 ast::Module(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)), 378 description: None,
399 ast::TypeAlias(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)), 379 docs: None,
400 ast::Const(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)),
401 ast::Static(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)),
402 ast::RecordField(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)),
403 ast::Variant(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)),
404 ast::MacroCall(it) => hir::Attrs::from_attrs_owner(db, InFile::new(file_id, &it)),
405 _ => return None,
406 } 380 }
407 }; 381 }
408 it.docs()
409} 382}
410 383
411/// Get a description of a symbol. 384/// Get a description of a symbol.
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index 10263537a..79c081cac 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -112,7 +112,7 @@ fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
112 .as_assoc_item(db) 112 .as_assoc_item(db)
113 .and_then(|assoc| match assoc.container(db) { 113 .and_then(|assoc| match assoc.container(db) {
114 AssocItemContainer::Trait(t) => Some(t.into()), 114 AssocItemContainer::Trait(t) => Some(t.into()),
115 AssocItemContainer::ImplDef(impld) => { 115 AssocItemContainer::Impl(impld) => {
116 impld.target_ty(db).as_adt().map(|adt| adt.into()) 116 impld.target_ty(db).as_adt().map(|adt| adt.into())
117 } 117 }
118 }) 118 })
@@ -190,7 +190,10 @@ fn rewrite_intra_doc_link(
190 }, 190 },
191 Definition::Macro(it) => it.resolve_doc_path(db, link, ns), 191 Definition::Macro(it) => it.resolve_doc_path(db, link, ns),
192 Definition::Field(it) => it.resolve_doc_path(db, link, ns), 192 Definition::Field(it) => it.resolve_doc_path(db, link, ns),
193 Definition::SelfType(_) | Definition::Local(_) | Definition::TypeParam(_) => return None, 193 Definition::SelfType(_)
194 | Definition::Local(_)
195 | Definition::TypeParam(_)
196 | Definition::LifetimeParam(_) => return None,
194 }?; 197 }?;
195 let krate = resolved.module(db)?.krate(); 198 let krate = resolved.module(db)?.krate();
196 let canonical_path = resolved.canonical_path(db)?; 199 let canonical_path = resolved.canonical_path(db)?;
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 0971f7701..6f3022dfd 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -237,7 +237,7 @@ fn pick_best(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
237 fn priority(n: &SyntaxToken) -> usize { 237 fn priority(n: &SyntaxToken) -> usize {
238 match n.kind() { 238 match n.kind() {
239 WHITESPACE => 0, 239 WHITESPACE => 0,
240 IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2, 240 IDENT | T![self] | T![super] | T![crate] | LIFETIME_IDENT => 2,
241 _ => 1, 241 _ => 1,
242 } 242 }
243 } 243 }
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index b9810457f..173509b08 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -1,3 +1,4 @@
1use either::Either;
1use hir::Semantics; 2use hir::Semantics;
2use ide_db::{ 3use ide_db::{
3 base_db::FileId, 4 base_db::FileId,
@@ -33,7 +34,7 @@ pub(crate) fn goto_definition(
33 let nav_targets = match_ast! { 34 let nav_targets = match_ast! {
34 match parent { 35 match parent {
35 ast::NameRef(name_ref) => { 36 ast::NameRef(name_ref) => {
36 reference_definition(&sema, &name_ref).to_vec() 37 reference_definition(&sema, Either::Right(&name_ref)).to_vec()
37 }, 38 },
38 ast::Name(name) => { 39 ast::Name(name) => {
39 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); 40 let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db);
@@ -53,6 +54,13 @@ pub(crate) fn goto_definition(
53 let self_param = func.param_list()?.self_param()?; 54 let self_param = func.param_list()?.self_param()?;
54 vec![self_to_nav_target(self_param, position.file_id)?] 55 vec![self_to_nav_target(self_param, position.file_id)?]
55 }, 56 },
57 ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, &lt) {
58 let def = name_class.referenced_or_defined(sema.db);
59 let nav = def.try_to_nav(sema.db)?;
60 vec![nav]
61 } else {
62 reference_definition(&sema, Either::Left(&lt)).to_vec()
63 },
56 _ => return None, 64 _ => return None,
57 } 65 }
58 }; 66 };
@@ -64,7 +72,7 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
64 return tokens.max_by_key(priority); 72 return tokens.max_by_key(priority);
65 fn priority(n: &SyntaxToken) -> usize { 73 fn priority(n: &SyntaxToken) -> usize {
66 match n.kind() { 74 match n.kind() {
67 IDENT | INT_NUMBER | T![self] => 2, 75 IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] => 2,
68 kind if kind.is_trivia() => 0, 76 kind if kind.is_trivia() => 0,
69 _ => 1, 77 _ => 1,
70 } 78 }
@@ -102,9 +110,12 @@ impl ReferenceResult {
102 110
103pub(crate) fn reference_definition( 111pub(crate) fn reference_definition(
104 sema: &Semantics<RootDatabase>, 112 sema: &Semantics<RootDatabase>,
105 name_ref: &ast::NameRef, 113 name_ref: Either<&ast::Lifetime, &ast::NameRef>,
106) -> ReferenceResult { 114) -> ReferenceResult {
107 let name_kind = NameRefClass::classify(sema, name_ref); 115 let name_kind = name_ref.either(
116 |lifetime| NameRefClass::classify_lifetime(sema, lifetime),
117 |name_ref| NameRefClass::classify(sema, name_ref),
118 );
108 if let Some(def) = name_kind { 119 if let Some(def) = name_kind {
109 let def = def.referenced(sema.db); 120 let def = def.referenced(sema.db);
110 return match def.try_to_nav(sema.db) { 121 return match def.try_to_nav(sema.db) {
@@ -114,10 +125,9 @@ pub(crate) fn reference_definition(
114 } 125 }
115 126
116 // Fallback index based approach: 127 // Fallback index based approach:
117 let navs = symbol_index::index_resolve(sema.db, name_ref) 128 let name = name_ref.either(ast::Lifetime::text, ast::NameRef::text);
118 .into_iter() 129 let navs =
119 .map(|s| s.to_nav(sema.db)) 130 symbol_index::index_resolve(sema.db, name).into_iter().map(|s| s.to_nav(sema.db)).collect();
120 .collect();
121 ReferenceResult::Approximate(navs) 131 ReferenceResult::Approximate(navs)
122} 132}
123 133
@@ -1036,4 +1046,35 @@ impl Foo {
1036}"#, 1046}"#,
1037 ) 1047 )
1038 } 1048 }
1049
1050 #[test]
1051 fn goto_lifetime_param_on_decl() {
1052 check(
1053 r#"
1054fn foo<'foobar<|>>(_: &'foobar ()) {
1055 //^^^^^^^
1056}"#,
1057 )
1058 }
1059
1060 #[test]
1061 fn goto_lifetime_param_decl() {
1062 check(
1063 r#"
1064fn foo<'foobar>(_: &'foobar<|> ()) {
1065 //^^^^^^^
1066}"#,
1067 )
1068 }
1069
1070 #[test]
1071 fn goto_lifetime_param_decl_nested() {
1072 check(
1073 r#"
1074fn foo<'foobar>(_: &'foobar ()) {
1075 fn foo<'foobar>(_: &'foobar<|> ()) {}
1076 //^^^^^^^
1077}"#,
1078 )
1079 }
1039} 1080}
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 529004878..6eac39639 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
1use hir::{Crate, ImplDef, Semantics}; 1use hir::{Crate, Impl, Semantics};
2use ide_db::RootDatabase; 2use ide_db::RootDatabase;
3use syntax::{algo::find_node_at_offset, ast, AstNode}; 3use syntax::{algo::find_node_at_offset, ast, AstNode};
4 4
@@ -49,7 +49,7 @@ fn impls_for_def(
49 ast::AdtDef::Union(def) => sema.to_def(def)?.ty(sema.db), 49 ast::AdtDef::Union(def) => sema.to_def(def)?.ty(sema.db),
50 }; 50 };
51 51
52 let impls = ImplDef::all_in_crate(sema.db, krate); 52 let impls = Impl::all_in_crate(sema.db, krate);
53 53
54 Some( 54 Some(
55 impls 55 impls
@@ -67,7 +67,7 @@ fn impls_for_trait(
67) -> Option<Vec<NavigationTarget>> { 67) -> Option<Vec<NavigationTarget>> {
68 let tr = sema.to_def(node)?; 68 let tr = sema.to_def(node)?;
69 69
70 let impls = ImplDef::for_trait(sema.db, krate, tr); 70 let impls = Impl::for_trait(sema.db, krate, tr);
71 71
72 Some(impls.into_iter().map(|imp| imp.to_nav(sema.db)).collect()) 72 Some(impls.into_iter().map(|imp| imp.to_nav(sema.db)).collect())
73} 73}
@@ -221,6 +221,8 @@ struct Foo<|>;
221mod marker { 221mod marker {
222 trait Copy {} 222 trait Copy {}
223} 223}
224#[rustc_builtin_macro]
225macro Copy {}
224"#, 226"#,
225 ); 227 );
226 } 228 }
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index c03dd74e4..da6bb726a 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -295,7 +295,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
295 Definition::ModuleDef(md) => match md { 295 Definition::ModuleDef(md) => match md {
296 ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) { 296 ModuleDef::Function(f) => match f.as_assoc_item(db)?.container(db) {
297 AssocItemContainer::Trait(t) => Some(t.name(db)), 297 AssocItemContainer::Trait(t) => Some(t.name(db)),
298 AssocItemContainer::ImplDef(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)), 298 AssocItemContainer::Impl(i) => i.target_ty(db).as_adt().map(|adt| adt.name(db)),
299 }, 299 },
300 ModuleDef::EnumVariant(e) => Some(e.parent_enum(db).name(db)), 300 ModuleDef::EnumVariant(e) => Some(e.parent_enum(db).name(db)),
301 _ => None, 301 _ => None,
@@ -370,7 +370,7 @@ fn hover_for_definition(db: &RootDatabase, def: Definition) -> Option<Markup> {
370 Adt::Enum(it) => from_def_source(db, it, mod_path), 370 Adt::Enum(it) => from_def_source(db, it, mod_path),
371 }) 371 })
372 } 372 }
373 Definition::TypeParam(_) => { 373 Definition::TypeParam(_) | Definition::LifetimeParam(_) => {
374 // FIXME: Hover for generic param 374 // FIXME: Hover for generic param
375 None 375 None
376 } 376 }
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 71068cac2..c5c652cda 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -528,6 +528,13 @@ impl Analysis {
528 self.with_db(|db| references::rename::rename(db, position, new_name)) 528 self.with_db(|db| references::rename::rename(db, position, new_name))
529 } 529 }
530 530
531 pub fn prepare_rename(
532 &self,
533 position: FilePosition,
534 ) -> Cancelable<Result<RangeInfo<()>, RenameError>> {
535 self.with_db(|db| references::rename::prepare_rename(db, position))
536 }
537
531 pub fn structural_search_replace( 538 pub fn structural_search_replace(
532 &self, 539 &self,
533 query: &str, 540 query: &str,
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 675957fff..98190a86b 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -130,6 +130,8 @@ pub(crate) fn find_all_refs(
130 kind = ReferenceKind::FieldShorthandForLocal; 130 kind = ReferenceKind::FieldShorthandForLocal;
131 } 131 }
132 } 132 }
133 } else if let Definition::LifetimeParam(_) = def {
134 kind = ReferenceKind::Lifetime;
133 }; 135 };
134 136
135 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) }; 137 let declaration = Declaration { nav, kind, access: decl_access(&def, &syntax, decl_range) };
@@ -148,11 +150,29 @@ fn find_name(
148 let range = name.syntax().text_range(); 150 let range = name.syntax().text_range();
149 return Some(RangeInfo::new(range, def)); 151 return Some(RangeInfo::new(range, def));
150 } 152 }
151 let name_ref = 153
152 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; 154 let (text_range, def) = if let Some(lifetime) =
153 let def = NameRefClass::classify(sema, &name_ref)?.referenced(sema.db); 155 sema.find_node_at_offset_with_descend::<ast::Lifetime>(&syntax, position.offset)
154 let range = name_ref.syntax().text_range(); 156 {
155 Some(RangeInfo::new(range, def)) 157 if let Some(def) = NameRefClass::classify_lifetime(sema, &lifetime)
158 .map(|class| NameRefClass::referenced(class, sema.db))
159 {
160 (lifetime.syntax().text_range(), def)
161 } else {
162 (
163 lifetime.syntax().text_range(),
164 NameClass::classify_lifetime(sema, &lifetime)?.referenced_or_defined(sema.db),
165 )
166 }
167 } else {
168 let name_ref =
169 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
170 (
171 name_ref.syntax().text_range(),
172 NameRefClass::classify(sema, &name_ref)?.referenced(sema.db),
173 )
174 };
175 Some(RangeInfo::new(text_range, def))
156} 176}
157 177
158fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> { 178fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
@@ -1005,4 +1025,65 @@ impl Foo {
1005 } 1025 }
1006 expect.assert_eq(&actual) 1026 expect.assert_eq(&actual)
1007 } 1027 }
1028
1029 #[test]
1030 fn test_find_lifetimes_function() {
1031 check(
1032 r#"
1033trait Foo<'a> {}
1034impl<'a> Foo<'a> for &'a () {}
1035fn foo<'a, 'b: 'a>(x: &'a<|> ()) -> &'a () where &'a (): Foo<'a> {
1036 fn bar<'a>(_: &'a ()) {}
1037 x
1038}
1039"#,
1040 expect![[r#"
1041 'a LIFETIME_PARAM FileId(0) 55..57 55..57 Lifetime
1042
1043 FileId(0) 63..65 Lifetime
1044 FileId(0) 71..73 Lifetime
1045 FileId(0) 82..84 Lifetime
1046 FileId(0) 95..97 Lifetime
1047 FileId(0) 106..108 Lifetime
1048 "#]],
1049 );
1050 }
1051
1052 #[test]
1053 fn test_find_lifetimes_type_alias() {
1054 check(
1055 r#"
1056type Foo<'a, T> where T: 'a<|> = &'a T;
1057"#,
1058 expect![[r#"
1059 'a LIFETIME_PARAM FileId(0) 9..11 9..11 Lifetime
1060
1061 FileId(0) 25..27 Lifetime
1062 FileId(0) 31..33 Lifetime
1063 "#]],
1064 );
1065 }
1066
1067 #[test]
1068 fn test_find_lifetimes_trait_impl() {
1069 check(
1070 r#"
1071trait Foo<'a> {
1072 fn foo() -> &'a ();
1073}
1074impl<'a> Foo<'a> for &'a () {
1075 fn foo() -> &'a<|> () {
1076 unimplemented!()
1077 }
1078}
1079"#,
1080 expect![[r#"
1081 'a LIFETIME_PARAM FileId(0) 47..49 47..49 Lifetime
1082
1083 FileId(0) 55..57 Lifetime
1084 FileId(0) 64..66 Lifetime
1085 FileId(0) 89..91 Lifetime
1086 "#]],
1087 );
1088 }
1008} 1089}
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 44081f210..56e923841 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -35,6 +35,29 @@ impl fmt::Display for RenameError {
35 35
36impl Error for RenameError {} 36impl Error for RenameError {}
37 37
38pub(crate) fn prepare_rename(
39 db: &RootDatabase,
40 position: FilePosition,
41) -> Result<RangeInfo<()>, RenameError> {
42 let sema = Semantics::new(db);
43 let source_file = sema.parse(position.file_id);
44 let syntax = source_file.syntax();
45 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
46 rename_mod(&sema, position, module, "dummy")
47 } else if let Some(self_token) =
48 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
49 {
50 rename_self_to_param(&sema, position, self_token, "dummy")
51 } else {
52 let range = match find_all_refs(&sema, position, None) {
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![])))
57 }
58 .map(|info| RangeInfo::new(info.range, ()))
59}
60
38pub(crate) fn rename( 61pub(crate) fn rename(
39 db: &RootDatabase, 62 db: &RootDatabase,
40 position: FilePosition, 63 position: FilePosition,
@@ -49,11 +72,18 @@ pub(crate) fn rename_with_semantics(
49 position: FilePosition, 72 position: FilePosition,
50 new_name: &str, 73 new_name: &str,
51) -> Result<RangeInfo<SourceChange>, RenameError> { 74) -> Result<RangeInfo<SourceChange>, RenameError> {
52 match lex_single_syntax_kind(new_name) { 75 let is_lifetime_name = match lex_single_syntax_kind(new_name) {
53 Some(res) => match res { 76 Some(res) => match res {
54 (SyntaxKind::IDENT, _) => (), 77 (SyntaxKind::IDENT, _) => false,
55 (SyntaxKind::UNDERSCORE, _) => (), 78 (SyntaxKind::UNDERSCORE, _) => false,
56 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position), 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 }
57 (_, Some(syntax_error)) => { 87 (_, Some(syntax_error)) => {
58 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error))) 88 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
59 } 89 }
@@ -62,18 +92,21 @@ pub(crate) fn rename_with_semantics(
62 } 92 }
63 }, 93 },
64 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))), 94 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
65 } 95 };
66 96
67 let source_file = sema.parse(position.file_id); 97 let source_file = sema.parse(position.file_id);
68 let syntax = source_file.syntax(); 98 let syntax = source_file.syntax();
69 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 99 // this is here to prevent lifetime renames from happening on modules and self
100 if is_lifetime_name {
101 rename_reference(&sema, position, new_name, is_lifetime_name)
102 } else if let Some(module) = find_module_at_offset(&sema, position, syntax) {
70 rename_mod(&sema, position, module, new_name) 103 rename_mod(&sema, position, module, new_name)
71 } else if let Some(self_token) = 104 } else if let Some(self_token) =
72 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 105 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
73 { 106 {
74 rename_self_to_param(&sema, position, self_token, new_name) 107 rename_self_to_param(&sema, position, self_token, new_name)
75 } else { 108 } else {
76 rename_reference(&sema, position, new_name) 109 rename_reference(&sema, position, new_name, is_lifetime_name)
77 } 110 }
78} 111}
79 112
@@ -355,12 +388,26 @@ fn rename_reference(
355 sema: &Semantics<RootDatabase>, 388 sema: &Semantics<RootDatabase>,
356 position: FilePosition, 389 position: FilePosition,
357 new_name: &str, 390 new_name: &str,
391 is_lifetime_name: bool,
358) -> Result<RangeInfo<SourceChange>, RenameError> { 392) -> Result<RangeInfo<SourceChange>, RenameError> {
359 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { 393 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
360 Some(range_info) => range_info, 394 Some(range_info) => range_info,
361 None => return Err(RenameError("No references found at position".to_string())), 395 None => return Err(RenameError("No references found at position".to_string())),
362 }; 396 };
363 397
398 match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) {
399 (true, false) => {
400 return Err(RenameError(format!(
401 "Invalid name `{}`: not a lifetime identifier",
402 new_name
403 )))
404 }
405 (false, true) => {
406 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
407 }
408 _ => (),
409 }
410
364 let edit = refs 411 let edit = refs
365 .into_iter() 412 .into_iter()
366 .map(|reference| source_edit_from_reference(sema, reference, new_name)) 413 .map(|reference| source_edit_from_reference(sema, reference, new_name))
@@ -465,6 +512,24 @@ mod tests {
465 } 512 }
466 513
467 #[test] 514 #[test]
515 fn test_rename_to_invalid_identifier_lifetime() {
516 check(
517 "'foo",
518 r#"fn main() { let i<|> = 1; }"#,
519 "error: Invalid name `'foo`: not an identifier",
520 );
521 }
522
523 #[test]
524 fn test_rename_to_invalid_identifier_lifetime2() {
525 check(
526 "foo",
527 r#"fn main<'a>(_: &'a<|> ()) {}"#,
528 "error: Invalid name `foo`: not a lifetime identifier",
529 );
530 }
531
532 #[test]
468 fn test_rename_for_local() { 533 fn test_rename_for_local() {
469 check( 534 check(
470 "k", 535 "k",
@@ -1396,4 +1461,31 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1396"#, 1461"#,
1397 ) 1462 )
1398 } 1463 }
1464
1465 #[test]
1466 fn test_rename_lifetimes() {
1467 check(
1468 "'yeeee",
1469 r#"
1470trait Foo<'a> {
1471 fn foo() -> &'a ();
1472}
1473impl<'a> Foo<'a> for &'a () {
1474 fn foo() -> &'a<|> () {
1475 unimplemented!()
1476 }
1477}
1478"#,
1479 r#"
1480trait Foo<'a> {
1481 fn foo() -> &'a ();
1482}
1483impl<'yeeee> Foo<'yeeee> for &'yeeee () {
1484 fn foo() -> &'yeeee () {
1485 unimplemented!()
1486 }
1487}
1488"#,
1489 )
1490 }
1399} 1491}
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 646f63704..2f465c195 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -2,7 +2,7 @@ use std::fmt;
2 2
3use assists::utils::test_related_attribute; 3use assists::utils::test_related_attribute;
4use cfg::CfgExpr; 4use cfg::CfgExpr;
5use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; 5use hir::{AsAssocItem, HasAttrs, InFile, Semantics};
6use ide_db::RootDatabase; 6use ide_db::RootDatabase;
7use itertools::Itertools; 7use itertools::Itertools;
8use syntax::{ 8use syntax::{
@@ -10,7 +10,10 @@ use syntax::{
10 match_ast, SyntaxNode, 10 match_ast, SyntaxNode,
11}; 11};
12 12
13use crate::{display::ToNav, FileId, NavigationTarget}; 13use crate::{
14 display::{ToNav, TryToNav},
15 FileId, NavigationTarget,
16};
14 17
15#[derive(Debug, Clone)] 18#[derive(Debug, Clone)]
16pub struct Runnable { 19pub struct Runnable {
@@ -101,124 +104,109 @@ pub(crate) fn runnable(
101 item: SyntaxNode, 104 item: SyntaxNode,
102 file_id: FileId, 105 file_id: FileId,
103) -> Option<Runnable> { 106) -> Option<Runnable> {
104 match_ast! { 107 let runnable_item = match_ast! {
105 match item { 108 match (item.clone()) {
106 ast::Struct(it) => runnable_struct(sema, it, file_id),
107 ast::Fn(it) => runnable_fn(sema, it, file_id), 109 ast::Fn(it) => runnable_fn(sema, it, file_id),
108 ast::Module(it) => runnable_mod(sema, it, file_id), 110 ast::Module(it) => runnable_mod(sema, it),
109 _ => None, 111 _ => None,
110 } 112 }
111 } 113 };
114 runnable_item.or_else(|| runnable_doctest(sema, item))
112} 115}
113 116
114fn runnable_fn( 117fn runnable_fn(sema: &Semantics<RootDatabase>, func: ast::Fn, file_id: FileId) -> Option<Runnable> {
115 sema: &Semantics<RootDatabase>, 118 let def = sema.to_def(&func)?;
116 fn_def: ast::Fn, 119 let name_string = func.name()?.text().to_string();
117 file_id: FileId,
118) -> Option<Runnable> {
119 let name_string = fn_def.name()?.text().to_string();
120 120
121 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
122 let kind = if name_string == "main" { 121 let kind = if name_string == "main" {
123 RunnableKind::Bin 122 RunnableKind::Bin
124 } else { 123 } else {
125 let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) { 124 let canonical_path = sema.to_def(&func).and_then(|def| {
126 Some(module) => { 125 let def: hir::ModuleDef = def.into();
127 let def = sema.to_def(&fn_def)?; 126 def.canonical_path(sema.db)
128 let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| { 127 });
129 match assoc_item.container(sema.db) { 128 let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
130 hir::AssocItemContainer::Trait(trait_item) => { 129
131 Some(trait_item.name(sema.db).to_string()) 130 if test_related_attribute(&func).is_some() {
132 } 131 let attr = TestAttr::from_fn(&func);
133 hir::AssocItemContainer::ImplDef(impl_def) => impl_def
134 .target_ty(sema.db)
135 .as_adt()
136 .map(|adt| adt.name(sema.db).to_string()),
137 }
138 });
139
140 let path_iter = module
141 .path_to_root(sema.db)
142 .into_iter()
143 .rev()
144 .filter_map(|it| it.name(sema.db))
145 .map(|name| name.to_string());
146
147 let path = if let Some(impl_trait_name) = impl_trait_name {
148 path_iter
149 .chain(std::iter::once(impl_trait_name))
150 .chain(std::iter::once(name_string))
151 .join("::")
152 } else {
153 path_iter.chain(std::iter::once(name_string)).join("::")
154 };
155
156 TestId::Path(path)
157 }
158 None => TestId::Name(name_string),
159 };
160
161 if test_related_attribute(&fn_def).is_some() {
162 let attr = TestAttr::from_fn(&fn_def);
163 RunnableKind::Test { test_id, attr } 132 RunnableKind::Test { test_id, attr }
164 } else if fn_def.has_atom_attr("bench") { 133 } else if func.has_atom_attr("bench") {
165 RunnableKind::Bench { test_id } 134 RunnableKind::Bench { test_id }
166 } else if has_runnable_doc_test(&attrs) {
167 RunnableKind::DocTest { test_id }
168 } else { 135 } else {
169 return None; 136 return None;
170 } 137 }
171 }; 138 };
172 139
173 let cfg = attrs.cfg(); 140 let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &func));
174 141 let cfg = def.attrs(sema.db).cfg();
175 let nav = if let RunnableKind::DocTest { .. } = kind {
176 NavigationTarget::from_doc_commented(
177 sema.db,
178 InFile::new(file_id.into(), &fn_def),
179 InFile::new(file_id.into(), &fn_def),
180 )
181 } else {
182 NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def))
183 };
184 Some(Runnable { nav, kind, cfg }) 142 Some(Runnable { nav, kind, cfg })
185} 143}
186 144
187fn runnable_struct( 145fn runnable_doctest(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> {
188 sema: &Semantics<RootDatabase>, 146 match_ast! {
189 struct_def: ast::Struct, 147 match item {
190 file_id: FileId, 148 ast::Fn(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
191) -> Option<Runnable> { 149 ast::Struct(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
192 let name_string = struct_def.name()?.text().to_string(); 150 ast::Enum(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
151 ast::Union(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
152 ast::Trait(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
153 ast::Const(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
154 ast::Static(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
155 ast::TypeAlias(it) => module_def_doctest(sema, sema.to_def(&it)?.into()),
156 _ => None,
157 }
158 }
159}
193 160
194 let attrs = 161fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
195 Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &struct_def)); 162 let attrs = match def {
163 hir::ModuleDef::Module(it) => it.attrs(sema.db),
164 hir::ModuleDef::Function(it) => it.attrs(sema.db),
165 hir::ModuleDef::Adt(it) => it.attrs(sema.db),
166 hir::ModuleDef::EnumVariant(it) => it.attrs(sema.db),
167 hir::ModuleDef::Const(it) => it.attrs(sema.db),
168 hir::ModuleDef::Static(it) => it.attrs(sema.db),
169 hir::ModuleDef::Trait(it) => it.attrs(sema.db),
170 hir::ModuleDef::TypeAlias(it) => it.attrs(sema.db),
171 hir::ModuleDef::BuiltinType(_) => return None,
172 };
196 if !has_runnable_doc_test(&attrs) { 173 if !has_runnable_doc_test(&attrs) {
197 return None; 174 return None;
198 } 175 }
199 let cfg = attrs.cfg(); 176 let def_name = def.name(sema.db).map(|it| it.to_string());
200 177 let test_id = def
201 let test_id = match sema.to_def(&struct_def).map(|def| def.module(sema.db)) { 178 .canonical_path(sema.db)
202 Some(module) => { 179 // This probably belongs to canonical path?
203 let path_iter = module 180 .map(|path| {
204 .path_to_root(sema.db) 181 let assoc_def = match def {
205 .into_iter() 182 hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
206 .rev() 183 hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
207 .filter_map(|it| it.name(sema.db)) 184 hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
208 .map(|name| name.to_string()); 185 _ => None,
209 let path = path_iter.chain(std::iter::once(name_string)).join("::"); 186 };
210 187 // FIXME: this also looks very wrong
211 TestId::Path(path) 188 if let Some(assoc_def) = assoc_def {
212 } 189 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
213 None => TestId::Name(name_string), 190 if let Some(adt) = imp.target_ty(sema.db).as_adt() {
214 }; 191 let name = adt.name(sema.db).to_string();
215 192 let idx = path.rfind(':').unwrap_or(0);
216 let nav = NavigationTarget::from_doc_commented( 193 let (prefix, suffix) = path.split_at(idx);
217 sema.db, 194 return format!("{}{}::{}", prefix, name, suffix);
218 InFile::new(file_id.into(), &struct_def), 195 }
219 InFile::new(file_id.into(), &struct_def), 196 }
220 ); 197 }
221 Some(Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg }) 198 path
199 })
200 .map(TestId::Path)
201 .or_else(|| def_name.clone().map(TestId::Name))?;
202
203 let mut nav = def.try_to_nav(sema.db)?;
204 nav.focus_range = None;
205 nav.description = None;
206 nav.docs = None;
207 nav.kind = syntax::SyntaxKind::COMMENT;
208 let res = Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() };
209 Some(res)
222} 210}
223 211
224#[derive(Debug, Copy, Clone)] 212#[derive(Debug, Copy, Clone)]
@@ -262,11 +250,7 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
262 }) 250 })
263} 251}
264 252
265fn runnable_mod( 253fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
266 sema: &Semantics<RootDatabase>,
267 module: ast::Module,
268 file_id: FileId,
269) -> Option<Runnable> {
270 if !has_test_function_or_multiple_test_submodules(&module) { 254 if !has_test_function_or_multiple_test_submodules(&module) {
271 return None; 255 return None;
272 } 256 }
@@ -279,7 +263,8 @@ fn runnable_mod(
279 .filter_map(|it| it.name(sema.db)) 263 .filter_map(|it| it.name(sema.db))
280 .join("::"); 264 .join("::");
281 265
282 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module)); 266 let def = sema.to_def(&module)?;
267 let attrs = def.attrs(sema.db);
283 let cfg = attrs.cfg(); 268 let cfg = attrs.cfg();
284 let nav = module_def.to_nav(sema.db); 269 let nav = module_def.to_nav(sema.db);
285 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) 270 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
@@ -319,7 +304,7 @@ mod tests {
319 304
320 use crate::fixture; 305 use crate::fixture;
321 306
322 use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST}; 307 use super::*;
323 308
324 fn check( 309 fn check(
325 ra_fixture: &str, 310 ra_fixture: &str,
@@ -548,7 +533,7 @@ struct StructWithRunnable(String);
548 full_range: 15..74, 533 full_range: 15..74,
549 focus_range: None, 534 focus_range: None,
550 name: "should_have_runnable", 535 name: "should_have_runnable",
551 kind: FN, 536 kind: COMMENT,
552 container_name: None, 537 container_name: None,
553 description: None, 538 description: None,
554 docs: None, 539 docs: None,
@@ -568,7 +553,7 @@ struct StructWithRunnable(String);
568 full_range: 76..148, 553 full_range: 76..148,
569 focus_range: None, 554 focus_range: None,
570 name: "should_have_runnable_1", 555 name: "should_have_runnable_1",
571 kind: FN, 556 kind: COMMENT,
572 container_name: None, 557 container_name: None,
573 description: None, 558 description: None,
574 docs: None, 559 docs: None,
@@ -588,7 +573,7 @@ struct StructWithRunnable(String);
588 full_range: 150..254, 573 full_range: 150..254,
589 focus_range: None, 574 focus_range: None,
590 name: "should_have_runnable_2", 575 name: "should_have_runnable_2",
591 kind: FN, 576 kind: COMMENT,
592 container_name: None, 577 container_name: None,
593 description: None, 578 description: None,
594 docs: None, 579 docs: None,
@@ -608,7 +593,7 @@ struct StructWithRunnable(String);
608 full_range: 756..821, 593 full_range: 756..821,
609 focus_range: None, 594 focus_range: None,
610 name: "StructWithRunnable", 595 name: "StructWithRunnable",
611 kind: STRUCT, 596 kind: COMMENT,
612 container_name: None, 597 container_name: None,
613 description: None, 598 description: None,
614 docs: None, 599 docs: None,
@@ -670,7 +655,7 @@ impl Data {
670 full_range: 44..98, 655 full_range: 44..98,
671 focus_range: None, 656 focus_range: None,
672 name: "foo", 657 name: "foo",
673 kind: FN, 658 kind: COMMENT,
674 container_name: None, 659 container_name: None,
675 description: None, 660 description: None,
676 docs: None, 661 docs: None,
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 990b0f7d9..488969f1a 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -806,6 +806,7 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
806 } 806 }
807 return h; 807 return h;
808 } 808 }
809 Definition::LifetimeParam(_) => HighlightTag::Lifetime,
809 } 810 }
810 .into() 811 .into()
811} 812}
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 0569cf1e5..3530a5fdb 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -38,6 +38,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
38<pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="punctuation">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="punctuation">}</span><span class="punctuation">;</span> 38<pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="punctuation">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="punctuation">}</span><span class="punctuation">;</span>
39<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="punctuation">{</span><span class="punctuation">}</span> 39<span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="punctuation">{</span><span class="punctuation">}</span>
40 40
41<span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">rustc_builtin_macro</span><span class="attribute attribute">]</span>
42<span class="keyword">macro</span> <span class="unresolved_reference declaration">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span>
43
41<span class="comment">// Needed for function consuming vs normal</span> 44<span class="comment">// Needed for function consuming vs normal</span>
42<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">marker</span> <span class="punctuation">{</span> 45<span class="keyword">pub</span> <span class="keyword">mod</span> <span class="module declaration">marker</span> <span class="punctuation">{</span>
43 <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"copy"</span><span class="attribute attribute">]</span> 46 <span class="attribute attribute">#</span><span class="attribute attribute">[</span><span class="function attribute">lang</span><span class="attribute attribute"> </span><span class="operator attribute">=</span><span class="attribute attribute"> </span><span class="string_literal attribute">"copy"</span><span class="attribute attribute">]</span>
@@ -119,7 +122,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
119 <span class="value_param callable">f</span><span class="punctuation">(</span><span class="punctuation">)</span> 122 <span class="value_param callable">f</span><span class="punctuation">(</span><span class="punctuation">)</span>
120<span class="punctuation">}</span> 123<span class="punctuation">}</span>
121 124
122<span class="keyword">fn</span> <span class="function declaration">foobar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="keyword">impl</span> <span class="unresolved_reference">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span> 125<span class="keyword">fn</span> <span class="function declaration">foobar</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="keyword">impl</span> <span class="macro">Copy</span> <span class="punctuation">{</span><span class="punctuation">}</span>
123 126
124<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 127<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
125 <span class="keyword">let</span> <span class="variable declaration">bar</span> <span class="operator">=</span> <span class="function">foobar</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> 128 <span class="keyword">let</span> <span class="variable declaration">bar</span> <span class="operator">=</span> <span class="function">foobar</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 1dc018a16..f53d2c3ba 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -12,6 +12,9 @@ fn test_highlighting() {
12use inner::{self as inner_mod}; 12use inner::{self as inner_mod};
13mod inner {} 13mod inner {}
14 14
15#[rustc_builtin_macro]
16macro Copy {}
17
15// Needed for function consuming vs normal 18// Needed for function consuming vs normal
16pub mod marker { 19pub mod marker {
17 #[lang = "copy"] 20 #[lang = "copy"]