diff options
Diffstat (limited to 'crates/hir_expand/src/hygiene.rs')
-rw-r--r-- | crates/hir_expand/src/hygiene.rs | 210 |
1 files changed, 177 insertions, 33 deletions
diff --git a/crates/hir_expand/src/hygiene.rs b/crates/hir_expand/src/hygiene.rs index 7ab0a5e52..8db581b77 100644 --- a/crates/hir_expand/src/hygiene.rs +++ b/crates/hir_expand/src/hygiene.rs | |||
@@ -2,65 +2,209 @@ | |||
2 | //! | 2 | //! |
3 | //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at | 3 | //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at |
4 | //! this moment, this is horribly incomplete and handles only `$crate`. | 4 | //! this moment, this is horribly incomplete and handles only `$crate`. |
5 | use std::sync::Arc; | ||
6 | |||
5 | use base_db::CrateId; | 7 | use base_db::CrateId; |
6 | use either::Either; | 8 | use either::Either; |
7 | use syntax::ast; | 9 | use mbe::Origin; |
10 | use parser::SyntaxKind; | ||
11 | use syntax::{ast, AstNode, SyntaxNode, TextRange, TextSize}; | ||
8 | 12 | ||
9 | use crate::{ | 13 | use crate::{ |
10 | db::AstDatabase, | 14 | db::{self, AstDatabase}, |
11 | name::{AsName, Name}, | 15 | name::{AsName, Name}, |
12 | HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind, | 16 | HirFileId, HirFileIdRepr, InFile, MacroCallId, MacroCallLoc, MacroDefKind, MacroFile, |
13 | }; | 17 | }; |
14 | 18 | ||
15 | #[derive(Clone, Debug)] | 19 | #[derive(Clone, Debug)] |
16 | pub struct Hygiene { | 20 | pub struct Hygiene { |
17 | // This is what `$crate` expands to | 21 | frames: Option<HygieneFrames>, |
18 | def_crate: Option<CrateId>, | ||
19 | |||
20 | // Indicate this is a local inner macro | ||
21 | local_inner: bool, | ||
22 | } | 22 | } |
23 | 23 | ||
24 | impl Hygiene { | 24 | impl Hygiene { |
25 | pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { | 25 | pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { |
26 | let (def_crate, local_inner) = match file_id.0 { | 26 | Hygiene { frames: Some(HygieneFrames::new(db, file_id.clone())) } |
27 | HirFileIdRepr::FileId(_) => (None, false), | ||
28 | HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id { | ||
29 | MacroCallId::LazyMacro(id) => { | ||
30 | let loc = db.lookup_intern_macro(id); | ||
31 | match loc.def.kind { | ||
32 | MacroDefKind::Declarative => (Some(loc.def.krate), loc.def.local_inner), | ||
33 | MacroDefKind::BuiltIn(_) => (Some(loc.def.krate), false), | ||
34 | MacroDefKind::BuiltInDerive(_) => (None, false), | ||
35 | MacroDefKind::BuiltInEager(_) => (None, false), | ||
36 | MacroDefKind::ProcMacro(_) => (None, false), | ||
37 | } | ||
38 | } | ||
39 | MacroCallId::EagerMacro(_id) => (None, false), | ||
40 | }, | ||
41 | }; | ||
42 | Hygiene { def_crate, local_inner } | ||
43 | } | 27 | } |
44 | 28 | ||
45 | pub fn new_unhygienic() -> Hygiene { | 29 | pub fn new_unhygienic() -> Hygiene { |
46 | Hygiene { def_crate: None, local_inner: false } | 30 | Hygiene { frames: None } |
47 | } | 31 | } |
48 | 32 | ||
49 | // FIXME: this should just return name | 33 | // FIXME: this should just return name |
50 | pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> { | 34 | pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> { |
51 | if let Some(def_crate) = self.def_crate { | 35 | if let Some(frames) = &self.frames { |
52 | if name_ref.text() == "$crate" { | 36 | if name_ref.text() == "$crate" { |
53 | return Either::Right(def_crate); | 37 | if let Some(krate) = frames.root_crate(name_ref.syntax()) { |
38 | return Either::Right(krate); | ||
39 | } | ||
54 | } | 40 | } |
55 | } | 41 | } |
42 | |||
56 | Either::Left(name_ref.as_name()) | 43 | Either::Left(name_ref.as_name()) |
57 | } | 44 | } |
58 | 45 | ||
59 | pub fn local_inner_macros(&self) -> Option<CrateId> { | 46 | pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> { |
60 | if self.local_inner { | 47 | let mut token = path.syntax().first_token()?.text_range(); |
61 | self.def_crate | 48 | let frames = self.frames.as_ref()?; |
62 | } else { | 49 | let mut current = frames.0.clone(); |
63 | None | 50 | |
51 | loop { | ||
52 | let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(token)?; | ||
53 | if origin == Origin::Def { | ||
54 | return if current.local_inner { frames.root_crate(path.syntax()) } else { None }; | ||
55 | } | ||
56 | current = current.call_site.as_ref()?.clone(); | ||
57 | token = mapped.value; | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | #[derive(Clone, Debug)] | ||
63 | struct HygieneFrames(Arc<HygieneFrame>); | ||
64 | |||
65 | #[derive(Clone, Debug, Eq, PartialEq)] | ||
66 | pub struct HygieneFrame { | ||
67 | expansion: Option<HygieneInfo>, | ||
68 | |||
69 | // Indicate this is a local inner macro | ||
70 | local_inner: bool, | ||
71 | krate: Option<CrateId>, | ||
72 | |||
73 | call_site: Option<Arc<HygieneFrame>>, | ||
74 | def_site: Option<Arc<HygieneFrame>>, | ||
75 | } | ||
76 | |||
77 | impl HygieneFrames { | ||
78 | fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self { | ||
79 | HygieneFrames(Arc::new(HygieneFrame::new(db, file_id))) | ||
80 | } | ||
81 | |||
82 | fn root_crate(&self, node: &SyntaxNode) -> Option<CrateId> { | ||
83 | let mut token = node.first_token()?.text_range(); | ||
84 | let mut result = self.0.krate; | ||
85 | let mut current = self.0.clone(); | ||
86 | |||
87 | while let Some((mapped, origin)) = | ||
88 | current.expansion.as_ref().and_then(|it| it.map_ident_up(token)) | ||
89 | { | ||
90 | result = current.krate; | ||
91 | |||
92 | let site = match origin { | ||
93 | Origin::Def => ¤t.def_site, | ||
94 | Origin::Call => ¤t.call_site, | ||
95 | }; | ||
96 | |||
97 | let site = match site { | ||
98 | None => break, | ||
99 | Some(it) => it, | ||
100 | }; | ||
101 | |||
102 | current = site.clone(); | ||
103 | token = mapped.value; | ||
64 | } | 104 | } |
105 | |||
106 | result | ||
107 | } | ||
108 | } | ||
109 | |||
110 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
111 | struct HygieneInfo { | ||
112 | arg_start: InFile<TextSize>, | ||
113 | /// The `macro_rules!` arguments. | ||
114 | def_start: Option<InFile<TextSize>>, | ||
115 | |||
116 | macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>, | ||
117 | macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>, | ||
118 | exp_map: Arc<mbe::TokenMap>, | ||
119 | } | ||
120 | |||
121 | impl HygieneInfo { | ||
122 | fn map_ident_up(&self, token: TextRange) -> Option<(InFile<TextRange>, Origin)> { | ||
123 | let token_id = self.exp_map.token_by_range(token)?; | ||
124 | |||
125 | let (token_id, origin) = self.macro_def.0.map_id_up(token_id); | ||
126 | let (token_map, tt) = match origin { | ||
127 | mbe::Origin::Call => (&self.macro_arg.1, self.arg_start), | ||
128 | mbe::Origin::Def => ( | ||
129 | &self.macro_def.1, | ||
130 | self.def_start | ||
131 | .as_ref() | ||
132 | .expect("`Origin::Def` used with non-`macro_rules!` macro") | ||
133 | .clone(), | ||
134 | ), | ||
135 | }; | ||
136 | |||
137 | let range = token_map.range_by_token(token_id)?.by_kind(SyntaxKind::IDENT)?; | ||
138 | Some((tt.with_value(range + tt.value), origin)) | ||
139 | } | ||
140 | } | ||
141 | |||
142 | fn make_hygiene_info( | ||
143 | db: &dyn AstDatabase, | ||
144 | macro_file: MacroFile, | ||
145 | loc: &MacroCallLoc, | ||
146 | ) -> Option<HygieneInfo> { | ||
147 | let arg_tt = loc.kind.arg(db)?; | ||
148 | |||
149 | let def_offset = loc.def.ast_id.and_then(|id| { | ||
150 | let def_tt = match id.to_node(db) { | ||
151 | ast::Macro::MacroRules(mac) => mac.token_tree()?.syntax().text_range().start(), | ||
152 | ast::Macro::MacroDef(_) => return None, | ||
153 | }; | ||
154 | Some(InFile::new(id.file_id, def_tt)) | ||
155 | }); | ||
156 | |||
157 | let macro_def = db.macro_def(loc.def)?; | ||
158 | let (_, exp_map) = db.parse_macro_expansion(macro_file).value?; | ||
159 | let macro_arg = db.macro_arg(macro_file.macro_call_id)?; | ||
160 | |||
161 | Some(HygieneInfo { | ||
162 | arg_start: InFile::new(loc.kind.file_id(), arg_tt.text_range().start()), | ||
163 | def_start: def_offset, | ||
164 | macro_arg, | ||
165 | macro_def, | ||
166 | exp_map, | ||
167 | }) | ||
168 | } | ||
169 | |||
170 | impl HygieneFrame { | ||
171 | pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame { | ||
172 | let (info, krate, local_inner) = match file_id.0 { | ||
173 | HirFileIdRepr::FileId(_) => (None, None, false), | ||
174 | HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id { | ||
175 | MacroCallId::EagerMacro(_id) => (None, None, false), | ||
176 | MacroCallId::LazyMacro(id) => { | ||
177 | let loc = db.lookup_intern_macro(id); | ||
178 | let info = make_hygiene_info(db, macro_file, &loc); | ||
179 | match loc.def.kind { | ||
180 | MacroDefKind::Declarative => { | ||
181 | (info, Some(loc.def.krate), loc.def.local_inner) | ||
182 | } | ||
183 | MacroDefKind::BuiltIn(_) => (info, Some(loc.def.krate), false), | ||
184 | MacroDefKind::BuiltInDerive(_) => (info, None, false), | ||
185 | MacroDefKind::BuiltInEager(_) => (info, None, false), | ||
186 | MacroDefKind::ProcMacro(_) => (info, None, false), | ||
187 | } | ||
188 | } | ||
189 | }, | ||
190 | }; | ||
191 | |||
192 | let info = match info { | ||
193 | None => { | ||
194 | return HygieneFrame { | ||
195 | expansion: None, | ||
196 | local_inner, | ||
197 | krate, | ||
198 | call_site: None, | ||
199 | def_site: None, | ||
200 | }; | ||
201 | } | ||
202 | Some(it) => it, | ||
203 | }; | ||
204 | |||
205 | let def_site = info.def_start.map(|it| db.hygiene_frame(it.file_id)); | ||
206 | let call_site = Some(db.hygiene_frame(info.arg_start.file_id)); | ||
207 | |||
208 | HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site } | ||
65 | } | 209 | } |
66 | } | 210 | } |