Try to make go to definition work in format!

SourceAnalyzer didn't work properly within expression macro expansions because
it didn't find the enclosing function. Fix this by going up the expansion chain
to find ancestors. This makes the test work, but apparently in real usage it's
still not working.
This commit is contained in:
Florian Diebold 2019-12-06 19:30:15 +01:00
parent eae425b10f
commit a565072dde
3 changed files with 63 additions and 5 deletions

View File

@ -76,18 +76,30 @@ fn def_with_body_from_child_node(
db: &impl HirDatabase, db: &impl HirDatabase,
child: InFile<&SyntaxNode>, child: InFile<&SyntaxNode>,
) -> Option<DefWithBody> { ) -> Option<DefWithBody> {
child.value.ancestors().find_map(|node| { ancestors_with_macros(db, child).find_map(|node| {
let n = &node.value;
match_ast! { match_ast! {
match node { match n {
ast::FnDef(def) => { return Function::from_source(db, child.with_value(def)).map(DefWithBody::from); }, ast::FnDef(def) => { return Function::from_source(db, node.with_value(def)).map(DefWithBody::from); },
ast::ConstDef(def) => { return Const::from_source(db, child.with_value(def)).map(DefWithBody::from); }, ast::ConstDef(def) => { return Const::from_source(db, node.with_value(def)).map(DefWithBody::from); },
ast::StaticDef(def) => { return Static::from_source(db, child.with_value(def)).map(DefWithBody::from); }, ast::StaticDef(def) => { return Static::from_source(db, node.with_value(def)).map(DefWithBody::from); },
_ => { None }, _ => { None },
} }
} }
}) })
} }
fn ancestors_with_macros<'a>(
db: &'a (impl HirDatabase),
node: InFile<&SyntaxNode>,
) -> impl Iterator<Item = InFile<SyntaxNode>> + 'a {
let file = node.with_value(()); // keep just the file id for borrow checker purposes
let parent_node = node.file_id.call_node(db);
let parent_ancestors: Box<dyn Iterator<Item = InFile<SyntaxNode>>> =
Box::new(parent_node.into_iter().flat_map(move |n| ancestors_with_macros(db, n.as_ref())));
node.value.ancestors().map(move |n| file.with_value(n)).chain(parent_ancestors)
}
/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
/// original source files. It should not be used inside the HIR itself. /// original source files. It should not be used inside the HIR itself.
#[derive(Debug)] #[derive(Debug)]
@ -135,6 +147,7 @@ pub struct ReferenceDescriptor {
pub name: String, pub name: String,
} }
#[derive(Debug)]
pub struct Expansion { pub struct Expansion {
macro_file_kind: MacroFileKind, macro_file_kind: MacroFileKind,
macro_call_id: MacroCallId, macro_call_id: MacroCallId,

View File

@ -76,6 +76,17 @@ pub fn original_file(self, db: &dyn db::AstDatabase) -> FileId {
} }
} }
/// If this is a macro call, returns the syntax node of the call.
pub fn call_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> {
match self.0 {
HirFileIdRepr::FileId(_) => None,
HirFileIdRepr::MacroFile(macro_file) => {
let loc = db.lookup_intern_macro(macro_file.macro_call_id);
Some(loc.kind.node(db))
}
}
}
/// Return expansion information if it is a macro-expansion file /// Return expansion information if it is a macro-expansion file
pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> { pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> {
match self.0 { match self.0 {
@ -176,6 +187,13 @@ pub fn file_id(&self) -> HirFileId {
} }
} }
pub fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
match self {
MacroCallKind::FnLike(ast_id) => ast_id.with_value(ast_id.to_node(db).syntax().clone()),
MacroCallKind::Attr(ast_id) => ast_id.with_value(ast_id.to_node(db).syntax().clone()),
}
}
pub fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> { pub fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> {
match self { match self {
MacroCallKind::FnLike(ast_id) => { MacroCallKind::FnLike(ast_id) => {

View File

@ -693,4 +693,31 @@ fn bar() {
"foo FN_DEF FileId(1) [52; 63) [55; 58)", "foo FN_DEF FileId(1) [52; 63) [55; 58)",
); );
} }
#[test]
fn goto_through_format() {
check_goto(
"
//- /lib.rs
#[macro_export]
macro_rules! format {
($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*)))
}
#[rustc_builtin_macro]
#[macro_export]
macro_rules! format_args {
($fmt:expr) => ({ /* compiler built-in */ });
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
}
pub mod __export {
pub use crate::format_args;
}
fn foo() -> i8 {}
fn test() {
format!(\"{}\", fo<|>o())
}
",
"foo FN_DEF FileId(1) [359; 376) [362; 365)",
);
}
} }