diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index a210574a06..e3ff05b49a 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -219,14 +219,20 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.find_file(syntax_node).file_id } + /// Attempts to map the node out of macro expanded files returning the original file range. + /// If upmapping is not possible, this will fall back to the range of the macro call of the + /// macro file the node resides in. pub fn original_range(&self, node: &SyntaxNode) -> FileRange { self.imp.original_range(node) } + /// Attempts to map the node out of macro expanded files returning the original file range. pub fn original_range_opt(&self, node: &SyntaxNode) -> Option { self.imp.original_range_opt(node) } + /// Attempts to map the node out of macro expanded files. + /// This only work for attribute expansions, as other ones do not have nodes as input. pub fn original_ast_node(&self, node: N) -> Option { self.imp.original_ast_node(node) } @@ -445,7 +451,7 @@ impl<'db> SemanticsImpl<'db> { } fn expand_attr_macro(&self, item: &ast::Item) -> Option { - let src = self.find_file(item.syntax()).with_value(item.clone()); + let src = self.wrap_node_infile(item.clone()); let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?; let file_id = macro_call_id.as_file(); let node = self.parse_or_expand(file_id)?; @@ -519,8 +525,7 @@ impl<'db> SemanticsImpl<'db> { speculative_args: &ast::Item, token_to_map: SyntaxToken, ) -> Option<(SyntaxNode, SyntaxToken)> { - let file_id = self.find_file(actual_macro_call.syntax()).file_id; - let macro_call = InFile::new(file_id, actual_macro_call.clone()); + let macro_call = self.wrap_node_infile(actual_macro_call.clone()); let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(macro_call))?; hir_expand::db::expand_speculative( self.db.upcast(), @@ -740,8 +745,7 @@ impl<'db> SemanticsImpl<'db> { } fn original_ast_node(&self, node: N) -> Option { - let InFile { file_id, .. } = self.find_file(node.syntax()); - InFile::new(file_id, node).original_ast_node(self.db.upcast()).map(|it| it.value) + self.wrap_node_infile(node).original_ast_node(self.db.upcast()).map(|it| it.value) } fn diagnostics_display_range(&self, src: InFile) -> FileRange { @@ -792,8 +796,7 @@ impl<'db> SemanticsImpl<'db> { gpl.lifetime_params() .find(|tp| tp.lifetime().as_ref().map(|lt| lt.text()).as_ref() == Some(&text)) })?; - let file_id = self.find_file(lifetime_param.syntax()).file_id; - let src = InFile::new(file_id, lifetime_param); + let src = self.wrap_node_infile(lifetime_param); ToDef::to_def(self, src) } @@ -815,8 +818,7 @@ impl<'db> SemanticsImpl<'db> { .map_or(false, |lt| lt.text() == text) }) })?; - let file_id = self.find_file(label.syntax()).file_id; - let src = InFile::new(file_id, label); + let src = self.wrap_node_infile(label); ToDef::to_def(self, src) } @@ -880,7 +882,7 @@ impl<'db> SemanticsImpl<'db> { } fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option { - let item_in_file = self.find_file(item.syntax()).with_value(item.clone()); + let item_in_file = self.wrap_node_infile(item.clone()); let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(item_in_file))?; Some(MacroDef { id: self.db.lookup_intern_macro_call(macro_call_id).def }) } @@ -1080,6 +1082,11 @@ impl<'db> SemanticsImpl<'db> { cache.get(root_node).copied() } + fn wrap_node_infile(&self, node: N) -> InFile { + let InFile { file_id, .. } = self.find_file(node.syntax()); + InFile::new(file_id, node) + } + fn find_file<'node>(&self, node: &'node SyntaxNode) -> InFile<&'node SyntaxNode> { let root_node = find_root(node); let file_id = self.lookup(&root_node).unwrap_or_else(|| { diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index f1669d5288..857c9e0ed9 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -39,24 +39,24 @@ impl DeclarationLocation { } pub fn original_range(&self, db: &dyn HirDatabase) -> Option { - find_original_file_range(db, self.hir_file_id, &self.ptr) + let node = resolve_node(db, self.hir_file_id, &self.ptr)?; + Some(node.as_ref().original_file_range(db.upcast())) } pub fn original_name_range(&self, db: &dyn HirDatabase) -> Option { - find_original_file_range(db, self.hir_file_id, &self.name_ptr) + let node = resolve_node(db, self.hir_file_id, &self.name_ptr)?; + node.as_ref().original_file_range_opt(db.upcast()) } } -fn find_original_file_range( +fn resolve_node( db: &dyn HirDatabase, file_id: HirFileId, ptr: &SyntaxNodePtr, -) -> Option { +) -> Option> { let root = db.parse_or_expand(file_id)?; let node = ptr.to_node(&root); - let node = InFile::new(file_id, &node); - - Some(node.original_file_range(db.upcast())) + Some(InFile::new(file_id, node)) } #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 51899ca2f6..580a1225cb 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -705,24 +705,14 @@ fn ascend_node_border_tokens( ) -> Option> { let expansion = file_id.expansion_info(db)?; - // the input node has only one token ? - let first = skip_trivia_token(node.first_token()?, Direction::Next)?; - let last = skip_trivia_token(node.last_token()?, Direction::Prev)?; - let is_single_token = first == last; + let first_token = |node: &SyntaxNode| skip_trivia_token(node.first_token()?, Direction::Next); + let last_token = |node: &SyntaxNode| skip_trivia_token(node.last_token()?, Direction::Prev); - node.descendants().find_map(|it| { - let first = skip_trivia_token(it.first_token()?, Direction::Next)?; - let first = ascend_call_token(db, &expansion, InFile::new(file_id, first))?; - - let last = skip_trivia_token(it.last_token()?, Direction::Prev)?; - let last = ascend_call_token(db, &expansion, InFile::new(file_id, last))?; - - if (!is_single_token && first == last) || (first.file_id != last.file_id) { - return None; - } - - Some(InFile::new(first.file_id, (first.value, last.value))) - }) + let first = first_token(node)?; + let last = last_token(node)?; + let first = ascend_call_token(db, &expansion, InFile::new(file_id, first))?; + let last = ascend_call_token(db, &expansion, InFile::new(file_id, last))?; + (first.file_id == last.file_id).then(|| InFile::new(first.file_id, (first.value, last.value))) } fn ascend_call_token( @@ -758,20 +748,28 @@ impl InFile { } pub fn original_ast_node(self, db: &dyn db::AstDatabase) -> Option> { - match ascend_node_border_tokens(db, self.syntax()) { - Some(InFile { file_id, value: (first, last) }) => { - let original_file = file_id.original_file(db); - if file_id != original_file.into() { - let range = first.text_range().cover(last.text_range()); - tracing::error!("Failed mapping up more for {:?}", range); - return None; - } - let anc = algo::least_common_ancestor(&first.parent()?, &last.parent()?)?; - Some(InFile::new(file_id, anc.ancestors().find_map(N::cast)?)) - } - _ if !self.file_id.is_macro() => Some(self), - _ => None, + // This kind of upmapping can only be achieved in attribute expanded files, + // as we don't have node inputs otherwise and therefor can't find an `N` node in the input + if !self.file_id.is_macro() { + return Some(self); + } else if !self.file_id.is_attr_macro(db) { + return None; } + + if let Some(InFile { file_id, value: (first, last) }) = + ascend_node_border_tokens(db, self.syntax()) + { + if file_id.is_macro() { + let range = first.text_range().cover(last.text_range()); + tracing::error!("Failed mapping out of macro file for {:?}", range); + return None; + } + // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes + let anc = algo::least_common_ancestor(&first.parent()?, &last.parent()?)?; + let value = anc.ancestors().find_map(N::cast)?; + return Some(InFile::new(file_id, value)); + } + None } pub fn syntax(&self) -> InFile<&SyntaxNode> { diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 21bea25a5f..21ff480df4 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -146,9 +146,10 @@ pub(crate) fn hover( if let Some(res) = render::keyword(sema, config, &original_token) { return Some(RangeInfo::new(original_token.text_range(), res)); } - if let res @ Some(_) = - descended.iter().find_map(|token| hover_type_fallback(sema, config, token)) - { + let res = descended + .iter() + .find_map(|token| hover_type_fallback(sema, config, token, &original_token)); + if let res @ Some(_) = res { return res; } } @@ -230,6 +231,7 @@ fn hover_type_fallback( sema: &Semantics, config: &HoverConfig, token: &SyntaxToken, + original_token: &SyntaxToken, ) -> Option> { let node = token .ancestors() @@ -248,7 +250,10 @@ fn hover_type_fallback( }; let res = render::type_info(sema, config, &expr_or_pat)?; - let range = sema.original_range(&node).range; + let range = sema + .original_range_opt(&node) + .map(|frange| frange.range) + .unwrap_or_else(|| original_token.text_range()); Some(RangeInfo::new(range, res)) } diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 1c3ff06247..dd4c17df29 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -14,7 +14,7 @@ use ide_db::{ use ide_db::{defs::Definition, RootDatabase}; use syntax::{ ast::{self, HasName}, - match_ast, AstNode, SmolStr, TextRange, + match_ast, AstNode, SmolStr, SyntaxNode, TextRange, }; /// `NavigationTarget` represents an element in the editor's UI which you can @@ -90,10 +90,8 @@ impl NavigationTarget { let name = module.name(db).map(|it| it.to_smol_str()).unwrap_or_default(); if let Some(src @ InFile { value, .. }) = &module.declaration_source(db) { let FileRange { file_id, range: full_range } = src.syntax().original_file_range(db); - let focus_range = value - .name() - .and_then(|name| src.with_value(name.syntax()).original_file_range_opt(db)) - .map(|it| it.range); + let focus_range = + value.name().and_then(|it| orig_focus_range(db, src.file_id, it.syntax())); let mut res = NavigationTarget::from_syntax( file_id, name, @@ -129,15 +127,11 @@ impl NavigationTarget { /// Allows `NavigationTarget` to be created from a `NameOwner` pub(crate) fn from_named( db: &RootDatabase, - node: InFile<&dyn ast::HasName>, + node @ InFile { file_id, value }: InFile<&dyn ast::HasName>, kind: SymbolKind, ) -> NavigationTarget { - let name = node.value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into()); - let focus_range = node - .value - .name() - .and_then(|it| node.with_value(it.syntax()).original_file_range_opt(db)) - .map(|it| it.range); + let name = value.name().map(|it| it.text().into()).unwrap_or_else(|| "_".into()); + let focus_range = value.name().and_then(|it| orig_focus_range(db, file_id, it.syntax())); let FileRange { file_id, range } = node.map(|it| it.syntax()).original_file_range(db); NavigationTarget::from_syntax(file_id, name, focus_range, range, kind) @@ -279,9 +273,7 @@ impl ToNav for hir::Module { ModuleSource::SourceFile(node) => (node.syntax(), None), ModuleSource::Module(node) => ( node.syntax(), - node.name() - .and_then(|it| InFile::new(file_id, it.syntax()).original_file_range_opt(db)) - .map(|it| it.range), + node.name().and_then(|it| orig_focus_range(db, file_id, it.syntax())), ), ModuleSource::BlockExpr(node) => (node.syntax(), None), }; @@ -299,10 +291,7 @@ impl TryToNav for hir::Impl { let focus_range = if derive_attr.is_some() { None } else { - value - .self_ty() - .and_then(|ty| InFile::new(file_id, ty.syntax()).original_file_range_opt(db)) - .map(|it| it.range) + value.self_ty().and_then(|ty| orig_focus_range(db, file_id, ty.syntax())) }; let FileRange { file_id, range: full_range } = match &derive_attr { @@ -397,9 +386,7 @@ impl ToNav for hir::Local { Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()), Either::Right(it) => (it.syntax(), it.name()), }; - let focus_range = name - .and_then(|it| InFile::new(file_id, it.syntax()).original_file_range_opt(db)) - .map(|it| it.range); + let focus_range = name.and_then(|it| orig_focus_range(db, file_id, it.syntax())); let FileRange { file_id, range: full_range } = InFile::new(file_id, node).original_file_range(db); @@ -505,10 +492,7 @@ impl TryToNav for hir::ConstParam { let InFile { file_id, value } = self.source(db)?; let name = self.name(db).to_smol_str(); - let focus_range = value - .name() - .and_then(|it| InFile::new(file_id, it.syntax()).original_file_range_opt(db)) - .map(|it| it.range); + let focus_range = value.name().and_then(|it| orig_focus_range(db, file_id, it.syntax())); let FileRange { file_id, range: full_range } = InFile::new(file_id, value.syntax()).original_file_range(db); Some(NavigationTarget { @@ -549,6 +533,14 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> } } +fn orig_focus_range( + db: &RootDatabase, + file_id: hir::HirFileId, + syntax: &SyntaxNode, +) -> Option { + InFile::new(file_id, syntax).original_file_range_opt(db).map(|it| it.range) +} + #[cfg(test)] mod tests { use expect_test::expect; diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index d6b1cce45f..0f7855a053 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs @@ -187,10 +187,10 @@ macro_rules! match_ast { (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; (match ($node:expr) { - $( ast::$ast:ident($it:pat) => $res:expr, )* + $( $( $path:ident )::+ ($it:pat) => $res:expr, )* _ => $catch_all:expr $(,)? }) => {{ - $( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )* + $( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )* { $catch_all } }}; }