aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_expand
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-11-09 09:13:14 +0000
committerGitHub <[email protected]>2019-11-09 09:13:14 +0000
commit561bb979cecd786f5d311ea7bddb1e15d77a3848 (patch)
treea6aaa81c3acbfc5e6add5ebbee9abbc0f8357014 /crates/ra_hir_expand
parent23939cabcc10ecc045a97361df182b9b4db32953 (diff)
parent0a5ec69404a2556dd82e5bb00b295aebaa291f04 (diff)
Merge #2169
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 <[email protected]>
Diffstat (limited to 'crates/ra_hir_expand')
-rw-r--r--crates/ra_hir_expand/src/db.rs43
-rw-r--r--crates/ra_hir_expand/src/lib.rs57
2 files changed, 85 insertions, 15 deletions
diff --git a/crates/ra_hir_expand/src/db.rs b/crates/ra_hir_expand/src/db.rs
index a4ee9a529..b789c6e7b 100644
--- a/crates/ra_hir_expand/src/db.rs
+++ b/crates/ra_hir_expand/src/db.rs
@@ -22,9 +22,12 @@ pub trait AstDatabase: SourceDatabase {
22 22
23 #[salsa::interned] 23 #[salsa::interned]
24 fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId; 24 fn intern_macro(&self, macro_call: MacroCallLoc) -> MacroCallId;
25 fn macro_arg(&self, id: MacroCallId) -> Option<Arc<tt::Subtree>>; 25 fn macro_arg(&self, id: MacroCallId) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>>;
26 fn macro_def(&self, id: MacroDefId) -> Option<Arc<mbe::MacroRules>>; 26 fn macro_def(&self, id: MacroDefId) -> Option<Arc<(mbe::MacroRules, mbe::TokenMap)>>;
27 fn parse_macro(&self, macro_file: MacroFile) -> Option<Parse<SyntaxNode>>; 27 fn parse_macro(
28 &self,
29 macro_file: MacroFile,
30 ) -> Option<(Parse<SyntaxNode>, Arc<mbe::RevTokenMap>)>;
28 fn macro_expand(&self, macro_call: MacroCallId) -> Result<Arc<tt::Subtree>, String>; 31 fn macro_expand(&self, macro_call: MacroCallId) -> Result<Arc<tt::Subtree>, String>;
29} 32}
30 33
@@ -34,10 +37,13 @@ pub(crate) fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdM
34 Arc::new(map) 37 Arc::new(map)
35} 38}
36 39
37pub(crate) fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<MacroRules>> { 40pub(crate) fn macro_def(
41 db: &dyn AstDatabase,
42 id: MacroDefId,
43) -> Option<Arc<(mbe::MacroRules, mbe::TokenMap)>> {
38 let macro_call = id.ast_id.to_node(db); 44 let macro_call = id.ast_id.to_node(db);
39 let arg = macro_call.token_tree()?; 45 let arg = macro_call.token_tree()?;
40 let (tt, _) = mbe::ast_to_token_tree(&arg).or_else(|| { 46 let (tt, tmap) = mbe::ast_to_token_tree(&arg).or_else(|| {
41 log::warn!("fail on macro_def to token tree: {:#?}", arg); 47 log::warn!("fail on macro_def to token tree: {:#?}", arg);
42 None 48 None
43 })?; 49 })?;
@@ -45,15 +51,18 @@ pub(crate) fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Option<Arc<Macr
45 log::warn!("fail on macro_def parse: {:#?}", tt); 51 log::warn!("fail on macro_def parse: {:#?}", tt);
46 None 52 None
47 })?; 53 })?;
48 Some(Arc::new(rules)) 54 Some(Arc::new((rules, tmap)))
49} 55}
50 56
51pub(crate) fn macro_arg(db: &dyn AstDatabase, id: MacroCallId) -> Option<Arc<tt::Subtree>> { 57pub(crate) fn macro_arg(
58 db: &dyn AstDatabase,
59 id: MacroCallId,
60) -> Option<Arc<(tt::Subtree, mbe::TokenMap)>> {
52 let loc = db.lookup_intern_macro(id); 61 let loc = db.lookup_intern_macro(id);
53 let macro_call = loc.ast_id.to_node(db); 62 let macro_call = loc.ast_id.to_node(db);
54 let arg = macro_call.token_tree()?; 63 let arg = macro_call.token_tree()?;
55 let (tt, _) = mbe::ast_to_token_tree(&arg)?; 64 let (tt, tmap) = mbe::ast_to_token_tree(&arg)?;
56 Some(Arc::new(tt)) 65 Some(Arc::new((tt, tmap)))
57} 66}
58 67
59pub(crate) fn macro_expand( 68pub(crate) fn macro_expand(
@@ -64,7 +73,7 @@ pub(crate) fn macro_expand(
64 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?; 73 let macro_arg = db.macro_arg(id).ok_or("Fail to args in to tt::TokenTree")?;
65 74
66 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?; 75 let macro_rules = db.macro_def(loc.def).ok_or("Fail to find macro definition")?;
67 let tt = macro_rules.expand(&macro_arg).map_err(|err| format!("{:?}", err))?; 76 let tt = macro_rules.0.expand(&macro_arg.0).map_err(|err| format!("{:?}", err))?;
68 // Set a hard limit for the expanded tt 77 // Set a hard limit for the expanded tt
69 let count = tt.count(); 78 let count = tt.count();
70 if count > 65536 { 79 if count > 65536 {
@@ -77,7 +86,7 @@ pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Optio
77 match file_id.0 { 86 match file_id.0 {
78 HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), 87 HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()),
79 HirFileIdRepr::MacroFile(macro_file) => { 88 HirFileIdRepr::MacroFile(macro_file) => {
80 db.parse_macro(macro_file).map(|it| it.syntax_node()) 89 db.parse_macro(macro_file).map(|(it, _)| it.syntax_node())
81 } 90 }
82 } 91 }
83} 92}
@@ -85,8 +94,9 @@ pub(crate) fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Optio
85pub(crate) fn parse_macro( 94pub(crate) fn parse_macro(
86 db: &dyn AstDatabase, 95 db: &dyn AstDatabase,
87 macro_file: MacroFile, 96 macro_file: MacroFile,
88) -> Option<Parse<SyntaxNode>> { 97) -> Option<(Parse<SyntaxNode>, Arc<mbe::RevTokenMap>)> {
89 let _p = profile("parse_macro_query"); 98 let _p = profile("parse_macro_query");
99
90 let macro_call_id = macro_file.macro_call_id; 100 let macro_call_id = macro_file.macro_call_id;
91 let tt = db 101 let tt = db
92 .macro_expand(macro_call_id) 102 .macro_expand(macro_call_id)
@@ -97,8 +107,13 @@ pub(crate) fn parse_macro(
97 log::warn!("fail on macro_parse: (reason: {})", err,); 107 log::warn!("fail on macro_parse: (reason: {})", err,);
98 }) 108 })
99 .ok()?; 109 .ok()?;
110
100 match macro_file.macro_file_kind { 111 match macro_file.macro_file_kind {
101 MacroFileKind::Items => mbe::token_tree_to_items(&tt).ok().map(Parse::to_syntax), 112 MacroFileKind::Items => {
102 MacroFileKind::Expr => mbe::token_tree_to_expr(&tt).ok().map(Parse::to_syntax), 113 mbe::token_tree_to_items(&tt).ok().map(|(p, map)| (p.to_syntax(), Arc::new(map)))
114 }
115 MacroFileKind::Expr => {
116 mbe::token_tree_to_expr(&tt).ok().map(|(p, map)| (p.to_syntax(), Arc::new(map)))
117 }
103 } 118 }
104} 119}
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index dd07a16b4..b219b8fbf 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -12,11 +12,12 @@ pub mod hygiene;
12pub mod diagnostics; 12pub mod diagnostics;
13 13
14use std::hash::{Hash, Hasher}; 14use std::hash::{Hash, Hasher};
15use std::sync::Arc;
15 16
16use ra_db::{salsa, CrateId, FileId}; 17use ra_db::{salsa, CrateId, FileId};
17use ra_syntax::{ 18use ra_syntax::{
18 ast::{self, AstNode}, 19 ast::{self, AstNode},
19 SyntaxNode, 20 SyntaxNode, TextRange, TextUnit,
20}; 21};
21 22
22use crate::ast_id_map::FileAstId; 23use crate::ast_id_map::FileAstId;
@@ -66,6 +67,30 @@ impl HirFileId {
66 } 67 }
67 } 68 }
68 } 69 }
70
71 /// Return expansion information if it is a macro-expansion file
72 pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> {
73 match self.0 {
74 HirFileIdRepr::FileId(_) => None,
75 HirFileIdRepr::MacroFile(macro_file) => {
76 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
77
78 let arg_start = loc.ast_id.to_node(db).token_tree()?.syntax().text_range().start();
79 let def_start =
80 loc.def.ast_id.to_node(db).token_tree()?.syntax().text_range().start();
81
82 let macro_def = db.macro_def(loc.def)?;
83 let shift = macro_def.0.shift();
84 let exp_map = db.parse_macro(macro_file)?.1;
85 let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
86
87 let arg_start = (loc.ast_id.file_id, arg_start);
88 let def_start = (loc.def.ast_id.file_id, def_start);
89
90 Some(ExpansionInfo { arg_start, def_start, macro_arg, macro_def, exp_map, shift })
91 }
92 }
93 }
69} 94}
70 95
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -112,6 +137,36 @@ impl MacroCallId {
112 } 137 }
113} 138}
114 139
140#[derive(Debug, Clone, PartialEq, Eq)]
141/// ExpansionInfo mainly describes how to map text range between src and expanded macro
142pub struct ExpansionInfo {
143 pub(crate) arg_start: (HirFileId, TextUnit),
144 pub(crate) def_start: (HirFileId, TextUnit),
145 pub(crate) shift: u32,
146
147 pub(crate) macro_def: Arc<(mbe::MacroRules, mbe::TokenMap)>,
148 pub(crate) macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
149 pub(crate) exp_map: Arc<mbe::RevTokenMap>,
150}
151
152impl ExpansionInfo {
153 pub fn find_range(&self, from: TextRange) -> Option<(HirFileId, TextRange)> {
154 fn look_in_rev_map(exp_map: &mbe::RevTokenMap, from: TextRange) -> Option<tt::TokenId> {
155 exp_map.ranges.iter().find(|&it| it.0.is_subrange(&from)).map(|it| it.1)
156 }
157
158 let token_id = look_in_rev_map(&self.exp_map, from)?;
159 let (token_map, file_offset, token_id) = if token_id.0 >= self.shift {
160 (&self.macro_arg.1, self.arg_start, tt::TokenId(token_id.0 - self.shift).into())
161 } else {
162 (&self.macro_def.1, self.def_start, token_id)
163 };
164
165 let range = token_map.relative_range_of(token_id)?;
166 Some((file_offset.0, TextRange::offset_len(range.start() + file_offset.1, range.len())))
167 }
168}
169
115/// `AstId` points to an AST node in any file. 170/// `AstId` points to an AST node in any file.
116/// 171///
117/// It is stable across reparses, and can be used as salsa key/value. 172/// It is stable across reparses, and can be used as salsa key/value.