diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index ed27f6122b4..1aa0d61fe61 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -16,7 +16,6 @@ use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use smallvec::{smallvec, SmallVec}; use syntax::{ - algo::find_node_at_offset, ast::{self, GenericParamsOwner, LoopBodyOwner}, match_ast, AstNode, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange, TextSize, }; @@ -241,10 +240,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { node: &SyntaxNode, offset: TextSize, ) -> Option { - if let Some(it) = find_node_at_offset(node, offset) { - return Some(it); - } - self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast) } @@ -567,16 +562,25 @@ impl<'db> SemanticsImpl<'db> { // Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop // traversing the inner iterator when it finds a node. + // The outer iterator is over the tokens descendants + // The inner iterator is the ancestors of a descendant fn descend_node_at_offset( &self, node: &SyntaxNode, offset: TextSize, ) -> impl Iterator + '_> + '_ { - // Handle macro token cases node.token_at_offset(offset) .map(move |token| self.descend_into_macros(token)) - .map(|it| it.into_iter().map(move |it| self.token_ancestors_with_macros(it))) - .flatten() + .map(|descendants| { + descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it)) + }) + // re-order the tokens from token_at_offset by returning the ancestors with the smaller first nodes first + // See algo::ancestors_at_offset, which uses the same approach + .kmerge_by(|left, right| { + left.clone() + .map(|node| node.text_range().len()) + .lt(right.clone().map(|node| node.text_range().len())) + }) } fn original_range(&self, node: &SyntaxNode) -> FileRange { @@ -594,11 +598,14 @@ impl<'db> SemanticsImpl<'db> { fn token_ancestors_with_macros( &self, token: SyntaxToken, - ) -> impl Iterator + '_ { + ) -> impl Iterator + Clone + '_ { token.parent().into_iter().flat_map(move |parent| self.ancestors_with_macros(parent)) } - fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator + '_ { + fn ancestors_with_macros( + &self, + node: SyntaxNode, + ) -> impl Iterator + Clone + '_ { let node = self.find_file(node); node.ancestors_with_macros(self.db.upcast()).map(|it| it.value) } diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 1f80fd3404e..723c7a1727b 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -131,8 +131,12 @@ impl SourceToDefCtx<'_, '_> { pub(super) fn module_to_def(&mut self, src: InFile) -> Option { let _p = profile::span("module_to_def"); - let parent_declaration = - src.syntax().cloned().ancestors_with_macros(self.db.upcast()).skip(1).find_map(|it| { + let parent_declaration = src + .syntax() + .cloned() + .ancestors_with_macros_skip_attr_item(self.db.upcast()) + .skip(1) + .find_map(|it| { let m = ast::Module::cast(it.value.clone())?; Some(it.with_value(m)) }); @@ -306,7 +310,8 @@ impl SourceToDefCtx<'_, '_> { } pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option { - for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) { + for container in src.cloned().ancestors_with_macros_skip_attr_item(self.db.upcast()).skip(1) + { if let Some(res) = self.container_to_def(container) { return Some(res); } @@ -370,7 +375,8 @@ impl SourceToDefCtx<'_, '_> { } fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option { - for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) { + for container in src.cloned().ancestors_with_macros_skip_attr_item(self.db.upcast()).skip(1) + { let res: GenericDefId = match_ast! { match (container.value) { ast::Fn(it) => self.fn_to_def(container.with_value(it))?.into(), @@ -388,7 +394,8 @@ impl SourceToDefCtx<'_, '_> { } fn find_pat_or_label_container(&mut self, src: InFile<&SyntaxNode>) -> Option { - for container in src.cloned().ancestors_with_macros(self.db.upcast()).skip(1) { + for container in src.cloned().ancestors_with_macros_skip_attr_item(self.db.upcast()).skip(1) + { let res: DefWithBodyId = match_ast! { match (container.value) { ast::Const(it) => self.const_to_def(container.with_value(it))?.into(), diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 871cccb8300..8bb56e0700d 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -186,6 +186,17 @@ impl HirFileId { } } + /// Return whether this file is an include macro + pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool { + match self.0 { + HirFileIdRepr::MacroFile(macro_file) => { + let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); + matches!(loc.kind, MacroCallKind::Attr { .. }) + } + _ => false, + } + } + pub fn is_macro(self) -> bool { matches!(self.0, HirFileIdRepr::MacroFile(_)) } @@ -525,7 +536,7 @@ impl InFile { pub fn ancestors_with_macros( self, db: &dyn db::AstDatabase, - ) -> impl Iterator> + '_ { + ) -> impl Iterator> + Clone + '_ { iter::successors(Some(self), move |node| match node.value.parent() { Some(parent) => Some(node.with_value(parent)), None => { @@ -534,6 +545,26 @@ impl InFile { } }) } + + /// Skips the attributed item that caused the macro invocation we are climbing up + pub fn ancestors_with_macros_skip_attr_item( + self, + db: &dyn db::AstDatabase, + ) -> impl Iterator> + '_ { + iter::successors(Some(self), move |node| match node.value.parent() { + Some(parent) => Some(node.with_value(parent)), + None => { + let parent_node = node.file_id.call_node(db)?; + if node.file_id.is_attr_macro(db) { + // macro call was an attributed item, skip it + // FIXME: does this fail if this is a direct expansion of another macro? + parent_node.map(|node| node.parent()).transpose() + } else { + Some(parent_node) + } + } + }) + } } impl<'a> InFile<&'a SyntaxNode> { diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index 8ec5e10c4df..079f847030f 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -31,6 +31,7 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< SyntaxKind::IDENT => 1, _ => 0, })?; + let descended = sema.descend_into_macros(tok.clone()); if let Some(attr) = descended.ancestors().find_map(ast::Attr::cast) { if let Some((path, tt)) = attr.as_simple_call() { @@ -45,6 +46,9 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< } } } + + // FIXME: Intermix attribute and bang! expansions + // currently we only recursively expand one of the two types let mut expanded = None; let mut name = None; for node in tok.ancestors() { diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 6994e41c8aa..7cce99c3b0b 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -413,6 +413,22 @@ fn foo() { ); } + #[test] + fn test_hl_local_in_attr() { + check( + r#" +//- proc_macros: identity +#[proc_macros::identity] +fn foo() { + let mut bar = 3; + // ^^^ write + bar$0; + // ^^^ read +} +"#, + ); + } + #[test] fn test_multi_macro_usage() { check( diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 14c0abaaf03..7058f051b82 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -1730,6 +1730,28 @@ id! { ); } + #[test] + fn test_hover_through_attr() { + check( + r#" +//- proc_macros: identity +#[proc_macros::identity] +fn foo$0() {} +"#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + fn foo() + ``` + "#]], + ); + } + #[test] fn test_hover_through_expr_in_macro() { check( diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 8c3ad0fcedc..c39d00b6e7d 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -1507,4 +1507,23 @@ fn f() { "#]], ) } + + #[test] + fn attr_expanded() { + check( + r#" +//- proc_macros: identity + +#[proc_macros::identity] +fn func$0() { + func(); +} +"#, + expect![[r#" + func Function FileId(0) 26..51 29..33 + + FileId(0) 42..46 + "#]], + ) + } } diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index a4297a2fec5..a495e6c5432 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -1880,4 +1880,26 @@ fn main() { f$0() } "error: No identifier available to rename", ) } + + #[test] + fn attributed_item() { + check( + "function", + r#" +//- proc_macros: identity + +#[proc_macros::identity] +fn func$0() { + func(); +} +"#, + r#" + +#[proc_macros::identity] +fn function() { + function(); +} +"#, + ) + } } diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 6b74ec3430d..376384670a2 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -1737,6 +1737,88 @@ fn t1() {} ); } + #[test] + fn attributed_module() { + check( + r#" +//- proc_macros: identity +//- /lib.rs +$0 +#[proc_macros::identity] +mod module { + #[test] + fn t0() {} + #[test] + fn t1() {} +} +"#, + &[TestMod, Test, Test], + expect![[r#" + [ + Runnable { + use_name_in_title: true, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 26..94, + focus_range: 30..36, + name: "module", + kind: Module, + description: "mod module", + }, + kind: TestMod { + path: "module", + }, + cfg: None, + }, + Runnable { + use_name_in_title: true, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 43..65, + focus_range: 58..60, + name: "t0", + kind: Function, + }, + kind: Test { + test_id: Path( + "module::t0", + ), + attr: TestAttr { + ignore: false, + }, + }, + cfg: None, + }, + Runnable { + use_name_in_title: true, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 70..92, + focus_range: 85..87, + name: "t1", + kind: Function, + }, + kind: Test { + test_id: Path( + "module::t1", + ), + attr: TestAttr { + ignore: false, + }, + }, + cfg: None, + }, + ] + "#]], + ); + } + #[test] fn find_no_tests() { check_tests( diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html index e43119aa3b0..448b30aa849 100644 --- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html +++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html @@ -51,14 +51,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd pub trait Copy {} } +#[proc_macros::identity] pub mod ops { - #[lang = "fn_once"] + #[lang = "fn_once"] pub trait FnOnce<Args> {} - #[lang = "fn_mut"] + #[lang = "fn_mut"] pub trait FnMut<Args>: FnOnce<Args> {} - #[lang = "fn"] + #[lang = "fn"] pub trait Fn<Args>: FnMut<Args> {} } diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 740127202ff..6b08e916a95 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -10,6 +10,7 @@ use crate::{fixture, FileRange, HlTag, TextRange}; fn test_highlighting() { check_highlighting( r#" +//- proc_macros: identity //- /main.rs crate:main deps:foo use inner::{self as inner_mod}; mod inner {} @@ -23,6 +24,7 @@ pub mod marker { pub trait Copy {} } +#[proc_macros::identity] pub mod ops { #[lang = "fn_once"] pub trait FnOnce {} diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 8b8c91b2fce..a84e6b3ba40 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs @@ -232,50 +232,36 @@ impl Definition { let file_id = file_id.original_file(db); if let Definition::Local(var) = self { - let range = match var.parent(db) { - DefWithBody::Function(f) => f.source(db).map(|src| src.value.syntax().text_range()), - DefWithBody::Const(c) => c.source(db).map(|src| src.value.syntax().text_range()), - DefWithBody::Static(s) => s.source(db).map(|src| src.value.syntax().text_range()), + let def = match var.parent(db) { + DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()), + DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()), + DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()), }; - return match range { - Some(range) => SearchScope::file_range(FileRange { file_id, range }), + return match def { + Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)), None => SearchScope::single_file(file_id), }; } if let Definition::SelfType(impl_) = self { - return match impl_.source(db).map(|src| src.value.syntax().text_range()) { - Some(range) => SearchScope::file_range(FileRange { file_id, range }), + return match impl_.source(db).map(|src| src.syntax().cloned()) { + Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)), None => SearchScope::single_file(file_id), }; } if let Definition::GenericParam(hir::GenericParam::LifetimeParam(param)) = self { - let range = match param.parent(db) { - hir::GenericDef::Function(it) => { - it.source(db).map(|src| src.value.syntax().text_range()) - } - hir::GenericDef::Adt(it) => { - it.source(db).map(|src| src.value.syntax().text_range()) - } - hir::GenericDef::Trait(it) => { - it.source(db).map(|src| src.value.syntax().text_range()) - } - hir::GenericDef::TypeAlias(it) => { - it.source(db).map(|src| src.value.syntax().text_range()) - } - hir::GenericDef::Impl(it) => { - it.source(db).map(|src| src.value.syntax().text_range()) - } - hir::GenericDef::Variant(it) => { - it.source(db).map(|src| src.value.syntax().text_range()) - } - hir::GenericDef::Const(it) => { - it.source(db).map(|src| src.value.syntax().text_range()) - } + let def = match param.parent(db) { + hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::Variant(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()), }; - return match range { - Some(range) => SearchScope::file_range(FileRange { file_id, range }), + return match def { + Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)), None => SearchScope::single_file(file_id), }; }