2169: MBE: Mapping spans for goto definition r=matklad a=edwin0cheng

Currently, go to definition gives the wrong span in MBE.  This PR implement a mapping mechanism to fix it and it could be used for future MBE hygiene implementation.

The basic idea of the mapping is:
1. When expanding the macro, generated 2 `TokenMap` which maps the macro args and macro defs between tokens and input text-ranges.
2. Before converting generated `TokenTree` to `SyntaxNode`, generated a `ExpandedRangeMap` which is a mapping between token and output text-ranges.
3. Using these 3 mappings to construct an `ExpansionInfo`  which can map between input text ranges and output text ranges.


Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
This commit is contained in:
bors[bot] 2019-11-09 09:13:14 +00:00 committed by GitHub
commit 561bb979ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 305 additions and 101 deletions

View file

@ -29,6 +29,21 @@ pub struct NavigationTarget {
docs: Option<String>,
}
fn find_range_from_node(
db: &RootDatabase,
src: hir::HirFileId,
node: &SyntaxNode,
) -> (FileId, TextRange) {
let text_range = node.text_range();
let (file_id, text_range) = src
.expansion_info(db)
.and_then(|expansion_info| expansion_info.find_range(text_range))
.unwrap_or((src, text_range));
// FIXME: handle recursive macro generated macro
(file_id.original_file(db), text_range)
}
impl NavigationTarget {
/// When `focus_range` is specified, returns it. otherwise
/// returns `full_range`
@ -72,8 +87,12 @@ impl NavigationTarget {
self.focus_range
}
pub(crate) fn from_bind_pat(file_id: FileId, pat: &ast::BindPat) -> NavigationTarget {
NavigationTarget::from_named(file_id, pat, None, None)
pub(crate) fn from_bind_pat(
db: &RootDatabase,
file_id: FileId,
pat: &ast::BindPat,
) -> NavigationTarget {
NavigationTarget::from_named(db, file_id.into(), pat, None, None)
}
pub(crate) fn from_symbol(db: &RootDatabase, symbol: FileSymbol) -> NavigationTarget {
@ -96,7 +115,7 @@ impl NavigationTarget {
) -> NavigationTarget {
let parse = db.parse(file_id);
let pat = pat.to_node(parse.tree().syntax());
NavigationTarget::from_bind_pat(file_id, &pat)
NavigationTarget::from_bind_pat(db, file_id, &pat)
}
pub(crate) fn from_self_param(
@ -119,31 +138,46 @@ impl NavigationTarget {
pub(crate) fn from_module(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
let src = module.definition_source(db);
let file_id = src.file_id.original_file(db);
let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
match src.ast {
ModuleSource::SourceFile(node) => {
NavigationTarget::from_syntax(file_id, name, None, node.syntax(), None, None)
let (file_id, text_range) = find_range_from_node(db, src.file_id, node.syntax());
NavigationTarget::from_syntax(
file_id,
name,
None,
text_range,
node.syntax(),
None,
None,
)
}
ModuleSource::Module(node) => {
let (file_id, text_range) = find_range_from_node(db, src.file_id, node.syntax());
NavigationTarget::from_syntax(
file_id,
name,
None,
text_range,
node.syntax(),
node.doc_comment_text(),
node.short_label(),
)
}
ModuleSource::Module(node) => NavigationTarget::from_syntax(
file_id,
name,
None,
node.syntax(),
node.doc_comment_text(),
node.short_label(),
),
}
}
pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget {
let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default();
if let Some(src) = module.declaration_source(db) {
let file_id = src.file_id.original_file(db);
let (file_id, text_range) = find_range_from_node(db, src.file_id, src.ast.syntax());
return NavigationTarget::from_syntax(
file_id,
name,
None,
text_range,
src.ast.syntax(),
src.ast.doc_comment_text(),
src.ast.short_label(),
@ -154,13 +188,25 @@ impl NavigationTarget {
pub(crate) fn from_field(db: &RootDatabase, field: hir::StructField) -> NavigationTarget {
let src = field.source(db);
let file_id = src.file_id.original_file(db);
match src.ast {
FieldSource::Named(it) => {
NavigationTarget::from_named(file_id, &it, it.doc_comment_text(), it.short_label())
}
FieldSource::Named(it) => NavigationTarget::from_named(
db,
src.file_id,
&it,
it.doc_comment_text(),
it.short_label(),
),
FieldSource::Pos(it) => {
NavigationTarget::from_syntax(file_id, "".into(), None, it.syntax(), None, None)
let (file_id, text_range) = find_range_from_node(db, src.file_id, it.syntax());
NavigationTarget::from_syntax(
file_id,
"".into(),
None,
text_range,
it.syntax(),
None,
None,
)
}
}
}
@ -172,7 +218,8 @@ impl NavigationTarget {
{
let src = def.source(db);
NavigationTarget::from_named(
src.file_id.original_file(db),
db,
src.file_id,
&src.ast,
src.ast.doc_comment_text(),
src.ast.short_label(),
@ -212,10 +259,13 @@ impl NavigationTarget {
impl_block: hir::ImplBlock,
) -> NavigationTarget {
let src = impl_block.source(db);
let (file_id, text_range) = find_range_from_node(db, src.file_id, src.ast.syntax());
NavigationTarget::from_syntax(
src.file_id.original_file(db),
file_id,
"impl".into(),
None,
text_range,
src.ast.syntax(),
None,
None,
@ -236,12 +286,7 @@ impl NavigationTarget {
pub(crate) fn from_macro_def(db: &RootDatabase, macro_call: hir::MacroDef) -> NavigationTarget {
let src = macro_call.source(db);
log::debug!("nav target {:#?}", src.ast.syntax());
NavigationTarget::from_named(
src.file_id.original_file(db),
&src.ast,
src.ast.doc_comment_text(),
None,
)
NavigationTarget::from_named(db, src.file_id, &src.ast, src.ast.doc_comment_text(), None)
}
#[cfg(test)]
@ -270,21 +315,33 @@ impl NavigationTarget {
/// Allows `NavigationTarget` to be created from a `NameOwner`
pub(crate) fn from_named(
file_id: FileId,
db: &RootDatabase,
file_id: hir::HirFileId,
node: &impl ast::NameOwner,
docs: Option<String>,
description: Option<String>,
) -> NavigationTarget {
//FIXME: use `_` instead of empty string
let name = node.name().map(|it| it.text().clone()).unwrap_or_default();
let focus_range = node.name().map(|it| it.syntax().text_range());
NavigationTarget::from_syntax(file_id, name, focus_range, node.syntax(), docs, description)
let focus_range = node.name().map(|it| find_range_from_node(db, file_id, it.syntax()).1);
let (file_id, full_range) = find_range_from_node(db, file_id, node.syntax());
NavigationTarget::from_syntax(
file_id,
name,
focus_range,
full_range,
node.syntax(),
docs,
description,
)
}
fn from_syntax(
file_id: FileId,
name: SmolStr,
focus_range: Option<TextRange>,
full_range: TextRange,
node: &SyntaxNode,
docs: Option<String>,
description: Option<String>,
@ -293,9 +350,8 @@ impl NavigationTarget {
file_id,
name,
kind: node.kind(),
full_range: node.text_range(),
full_range,
focus_range,
// ptr: Some(LocalSyntaxPtr::new(node)),
container_name: None,
description,
docs,

View file

@ -101,19 +101,20 @@ pub(crate) fn name_definition(
}
}
if let Some(nav) = named_target(file_id, &parent) {
if let Some(nav) = named_target(db, file_id, &parent) {
return Some(vec![nav]);
}
None
}
fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget> {
fn named_target(db: &RootDatabase, file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget> {
match_ast! {
match node {
ast::StructDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -121,7 +122,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::EnumDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -129,7 +131,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::EnumVariant(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -137,7 +140,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::FnDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -145,7 +149,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::TypeAliasDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -153,7 +158,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::ConstDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -161,7 +167,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::StaticDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -169,7 +176,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::TraitDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -177,7 +185,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::RecordFieldDef(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -185,7 +194,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::Module(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
it.short_label(),
@ -193,7 +203,8 @@ fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget>
},
ast::MacroCall(it) => {
Some(NavigationTarget::from_named(
file_id,
db,
file_id.into(),
&it,
it.doc_comment_text(),
None,
@ -334,6 +345,46 @@ mod tests {
);
}
#[test]
fn goto_definition_works_for_macro_defined_fn_with_arg() {
check_goto(
"
//- /lib.rs
macro_rules! define_fn {
($name:ident) => (fn $name() {})
}
define_fn!(
foo
)
fn bar() {
<|>foo();
}
",
"foo FN_DEF FileId(1) [80; 83) [80; 83)",
);
}
#[test]
fn goto_definition_works_for_macro_defined_fn_no_arg() {
check_goto(
"
//- /lib.rs
macro_rules! define_fn {
() => (fn foo() {})
}
define_fn!();
fn bar() {
<|>foo();
}
",
"foo FN_DEF FileId(1) [39; 42) [39; 42)",
);
}
#[test]
fn goto_definition_works_for_methods() {
covers!(goto_definition_works_for_methods);

View file

@ -94,10 +94,10 @@ impl FromIterator<TableEntry<FileId, Parse<ast::SourceFile>>> for SyntaxTreeStat
}
}
impl FromIterator<TableEntry<MacroFile, Option<Parse<SyntaxNode>>>> for SyntaxTreeStats {
impl<M> FromIterator<TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>> for SyntaxTreeStats {
fn from_iter<T>(iter: T) -> SyntaxTreeStats
where
T: IntoIterator<Item = TableEntry<MacroFile, Option<Parse<SyntaxNode>>>>,
T: IntoIterator<Item = TableEntry<MacroFile, Option<(Parse<SyntaxNode>, M)>>>,
{
let mut res = SyntaxTreeStats::default();
for entry in iter {