Make more precise range macro upmapping
This commit is contained in:
parent
5fd3688018
commit
45ff51ba22
@ -219,14 +219,20 @@ pub fn hir_file_for(&self, syntax_node: &SyntaxNode) -> HirFileId {
|
||||
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<FileRange> {
|
||||
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<N: AstNode>(&self, node: N) -> Option<N> {
|
||||
self.imp.original_ast_node(node)
|
||||
}
|
||||
@ -445,7 +451,7 @@ fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> {
|
||||
}
|
||||
|
||||
fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> {
|
||||
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 @@ fn speculative_expand_attr(
|
||||
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 @@ fn original_range_opt(&self, node: &SyntaxNode) -> Option<FileRange> {
|
||||
}
|
||||
|
||||
fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> {
|
||||
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<SyntaxNodePtr>) -> FileRange {
|
||||
@ -792,8 +796,7 @@ fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimePar
|
||||
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 @@ fn resolve_label(&self, lifetime: &ast::Lifetime) -> Option<Label> {
|
||||
.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 @@ fn resolve_macro_call(&self, macro_call: &ast::MacroCall) -> Option<MacroDef> {
|
||||
}
|
||||
|
||||
fn resolve_attr_macro_call(&self, item: &ast::Item) -> Option<MacroDef> {
|
||||
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 @@ fn lookup(&self, root_node: &SyntaxNode) -> Option<HirFileId> {
|
||||
cache.get(root_node).copied()
|
||||
}
|
||||
|
||||
fn wrap_node_infile<N: AstNode>(&self, node: N) -> InFile<N> {
|
||||
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(|| {
|
||||
|
@ -39,24 +39,24 @@ pub fn syntax<DB: HirDatabase>(&self, sema: &Semantics<DB>) -> Option<SyntaxNode
|
||||
}
|
||||
|
||||
pub fn original_range(&self, db: &dyn HirDatabase) -> Option<FileRange> {
|
||||
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<FileRange> {
|
||||
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<FileRange> {
|
||||
) -> Option<InFile<SyntaxNode>> {
|
||||
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)]
|
||||
|
@ -710,21 +710,9 @@ fn ascend_node_border_tokens(
|
||||
|
||||
let first = first_token(node)?;
|
||||
let last = last_token(node)?;
|
||||
let is_single_token = first == last;
|
||||
|
||||
node.descendants().find_map(|it| {
|
||||
let first = first_token(&it)?;
|
||||
let first = ascend_call_token(db, &expansion, InFile::new(file_id, first))?;
|
||||
|
||||
let last = last_token(&it)?;
|
||||
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 = 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(
|
||||
@ -760,20 +748,28 @@ pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> {
|
||||
}
|
||||
|
||||
pub fn original_ast_node(self, db: &dyn db::AstDatabase) -> Option<InFile<N>> {
|
||||
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> {
|
||||
|
@ -405,7 +405,7 @@ fn caller() {
|
||||
call!(call$0ee);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"callee Function FileId(0) 152..158 152..158"#]],
|
||||
expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
|
||||
expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
|
||||
expect![[]],
|
||||
);
|
||||
@ -426,7 +426,7 @@ fn caller() {
|
||||
call!(callee);
|
||||
}
|
||||
"#,
|
||||
expect![[r#"callee Function FileId(0) 152..158 152..158"#]],
|
||||
expect![[r#"callee Function FileId(0) 144..159 152..158"#]],
|
||||
expect![[r#"caller Function FileId(0) 160..194 163..169 : [184..190]"#]],
|
||||
expect![[]],
|
||||
);
|
||||
|
@ -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<RootDatabase>,
|
||||
config: &HoverConfig,
|
||||
token: &SyntaxToken,
|
||||
original_token: &SyntaxToken,
|
||||
) -> Option<RangeInfo<HoverResult>> {
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
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 @@ pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> Nav
|
||||
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 @@ pub(crate) fn debug_render(&self) -> String {
|
||||
/// 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 @@ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
|
||||
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 @@ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
|
||||
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 @@ fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
|
||||
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 @@ fn try_to_nav(&self, db: &RootDatabase) -> Option<NavigationTarget> {
|
||||
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<TextRange> {
|
||||
InFile::new(file_id, syntax).original_file_range_opt(db).map(|it| it.range)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use expect_test::expect;
|
||||
|
@ -1484,12 +1484,12 @@ fn f() {
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
func Function FileId(0) 140..144 140..144
|
||||
func Function FileId(0) 137..146 140..144
|
||||
|
||||
FileId(0) 161..165
|
||||
|
||||
|
||||
func Function FileId(0) 140..144 140..144
|
||||
func Function FileId(0) 137..146 140..144
|
||||
|
||||
FileId(0) 181..185
|
||||
"#]],
|
||||
|
@ -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 }
|
||||
}};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user