]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | //! This modules handles hygiene information. |
2 | //! | |
3 | //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at | |
4 | //! this moment, this is horribly incomplete and handles only `$crate`. | |
5 | use std::sync::Arc; | |
6 | ||
7 | use base_db::CrateId; | |
8 | use db::TokenExpander; | |
9 | use either::Either; | |
10 | use mbe::Origin; | |
11 | use syntax::{ | |
12 | ast::{self, HasDocComments}, | |
13 | AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, | |
14 | }; | |
15 | ||
16 | use crate::{ | |
17 | db::{self, AstDatabase}, | |
18 | fixup, | |
19 | name::{AsName, Name}, | |
9c376795 | 20 | HirFileId, InFile, MacroCallKind, MacroCallLoc, MacroDefKind, MacroFile, |
064997fb FG |
21 | }; |
22 | ||
23 | #[derive(Clone, Debug)] | |
24 | pub struct Hygiene { | |
25 | frames: Option<HygieneFrames>, | |
26 | } | |
27 | ||
28 | impl Hygiene { | |
29 | pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { | |
30 | Hygiene { frames: Some(HygieneFrames::new(db, file_id)) } | |
31 | } | |
32 | ||
33 | pub fn new_unhygienic() -> Hygiene { | |
34 | Hygiene { frames: None } | |
35 | } | |
36 | ||
37 | // FIXME: this should just return name | |
38 | pub fn name_ref_to_name( | |
39 | &self, | |
40 | db: &dyn AstDatabase, | |
41 | name_ref: ast::NameRef, | |
42 | ) -> Either<Name, CrateId> { | |
43 | if let Some(frames) = &self.frames { | |
44 | if name_ref.text() == "$crate" { | |
45 | if let Some(krate) = frames.root_crate(db, name_ref.syntax()) { | |
46 | return Either::Right(krate); | |
47 | } | |
48 | } | |
49 | } | |
50 | ||
51 | Either::Left(name_ref.as_name()) | |
52 | } | |
53 | ||
54 | pub fn local_inner_macros(&self, db: &dyn AstDatabase, path: ast::Path) -> Option<CrateId> { | |
55 | let mut token = path.syntax().first_token()?.text_range(); | |
56 | let frames = self.frames.as_ref()?; | |
57 | let mut current = &frames.0; | |
58 | ||
59 | loop { | |
60 | let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(db, token)?; | |
61 | if origin == Origin::Def { | |
62 | return if current.local_inner { | |
63 | frames.root_crate(db, path.syntax()) | |
64 | } else { | |
65 | None | |
66 | }; | |
67 | } | |
68 | current = current.call_site.as_ref()?; | |
69 | token = mapped.value; | |
70 | } | |
71 | } | |
72 | } | |
73 | ||
74 | #[derive(Clone, Debug)] | |
75 | struct HygieneFrames(Arc<HygieneFrame>); | |
76 | ||
77 | #[derive(Clone, Debug, Eq, PartialEq)] | |
78 | pub struct HygieneFrame { | |
79 | expansion: Option<HygieneInfo>, | |
80 | ||
81 | // Indicate this is a local inner macro | |
82 | local_inner: bool, | |
83 | krate: Option<CrateId>, | |
84 | ||
85 | call_site: Option<Arc<HygieneFrame>>, | |
86 | def_site: Option<Arc<HygieneFrame>>, | |
87 | } | |
88 | ||
89 | impl HygieneFrames { | |
90 | fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self { | |
91 | // Note that this intentionally avoids the `hygiene_frame` query to avoid blowing up memory | |
92 | // usage. The query is only helpful for nested `HygieneFrame`s as it avoids redundant work. | |
93 | HygieneFrames(Arc::new(HygieneFrame::new(db, file_id))) | |
94 | } | |
95 | ||
96 | fn root_crate(&self, db: &dyn AstDatabase, node: &SyntaxNode) -> Option<CrateId> { | |
97 | let mut token = node.first_token()?.text_range(); | |
98 | let mut result = self.0.krate; | |
99 | let mut current = self.0.clone(); | |
100 | ||
101 | while let Some((mapped, origin)) = | |
102 | current.expansion.as_ref().and_then(|it| it.map_ident_up(db, token)) | |
103 | { | |
104 | result = current.krate; | |
105 | ||
106 | let site = match origin { | |
107 | Origin::Def => ¤t.def_site, | |
108 | Origin::Call => ¤t.call_site, | |
109 | }; | |
110 | ||
111 | let site = match site { | |
112 | None => break, | |
113 | Some(it) => it, | |
114 | }; | |
115 | ||
116 | current = site.clone(); | |
117 | token = mapped.value; | |
118 | } | |
119 | ||
120 | result | |
121 | } | |
122 | } | |
123 | ||
124 | #[derive(Debug, Clone, PartialEq, Eq)] | |
125 | struct HygieneInfo { | |
126 | file: MacroFile, | |
127 | /// The start offset of the `macro_rules!` arguments or attribute input. | |
128 | attr_input_or_mac_def_start: Option<InFile<TextSize>>, | |
129 | ||
130 | macro_def: Arc<TokenExpander>, | |
9ffffee4 | 131 | macro_arg: Arc<(crate::tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>, |
064997fb FG |
132 | macro_arg_shift: mbe::Shift, |
133 | exp_map: Arc<mbe::TokenMap>, | |
134 | } | |
135 | ||
136 | impl HygieneInfo { | |
137 | fn map_ident_up( | |
138 | &self, | |
139 | db: &dyn AstDatabase, | |
140 | token: TextRange, | |
141 | ) -> Option<(InFile<TextRange>, Origin)> { | |
142 | let token_id = self.exp_map.token_by_range(token)?; | |
143 | let (mut token_id, origin) = self.macro_def.map_id_up(token_id); | |
144 | ||
145 | let loc = db.lookup_intern_macro_call(self.file.macro_call_id); | |
146 | ||
147 | let (token_map, tt) = match &loc.kind { | |
148 | MacroCallKind::Attr { attr_args, .. } => match self.macro_arg_shift.unshift(token_id) { | |
149 | Some(unshifted) => { | |
150 | token_id = unshifted; | |
151 | (&attr_args.1, self.attr_input_or_mac_def_start?) | |
152 | } | |
153 | None => ( | |
154 | &self.macro_arg.1, | |
155 | InFile::new(loc.kind.file_id(), loc.kind.arg(db)?.text_range().start()), | |
156 | ), | |
157 | }, | |
158 | _ => match origin { | |
159 | mbe::Origin::Call => ( | |
160 | &self.macro_arg.1, | |
161 | InFile::new(loc.kind.file_id(), loc.kind.arg(db)?.text_range().start()), | |
162 | ), | |
163 | mbe::Origin::Def => match (&*self.macro_def, &self.attr_input_or_mac_def_start) { | |
164 | (TokenExpander::DeclarativeMacro { def_site_token_map, .. }, Some(tt)) => { | |
165 | (def_site_token_map, *tt) | |
166 | } | |
167 | _ => panic!("`Origin::Def` used with non-`macro_rules!` macro"), | |
168 | }, | |
169 | }, | |
170 | }; | |
171 | ||
172 | let range = token_map.first_range_by_token(token_id, SyntaxKind::IDENT)?; | |
173 | Some((tt.with_value(range + tt.value), origin)) | |
174 | } | |
175 | } | |
176 | ||
177 | fn make_hygiene_info( | |
178 | db: &dyn AstDatabase, | |
179 | macro_file: MacroFile, | |
180 | loc: &MacroCallLoc, | |
181 | ) -> Option<HygieneInfo> { | |
182 | let def = loc.def.ast_id().left().and_then(|id| { | |
183 | let def_tt = match id.to_node(db) { | |
184 | ast::Macro::MacroRules(mac) => mac.token_tree()?, | |
185 | ast::Macro::MacroDef(mac) => mac.body()?, | |
186 | }; | |
187 | Some(InFile::new(id.file_id, def_tt)) | |
188 | }); | |
189 | let attr_input_or_mac_def = def.or_else(|| match loc.kind { | |
190 | MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { | |
191 | let tt = ast_id | |
192 | .to_node(db) | |
193 | .doc_comments_and_attrs() | |
9ffffee4 | 194 | .nth(invoc_attr_index.ast_index()) |
064997fb FG |
195 | .and_then(Either::left)? |
196 | .token_tree()?; | |
197 | Some(InFile::new(ast_id.file_id, tt)) | |
198 | } | |
199 | _ => None, | |
200 | }); | |
201 | ||
202 | let macro_def = db.macro_def(loc.def).ok()?; | |
203 | let (_, exp_map) = db.parse_macro_expansion(macro_file).value?; | |
204 | let macro_arg = db.macro_arg(macro_file.macro_call_id)?; | |
205 | ||
206 | Some(HygieneInfo { | |
207 | file: macro_file, | |
208 | attr_input_or_mac_def_start: attr_input_or_mac_def | |
209 | .map(|it| it.map(|tt| tt.syntax().text_range().start())), | |
210 | macro_arg_shift: mbe::Shift::new(¯o_arg.0), | |
211 | macro_arg, | |
212 | macro_def, | |
213 | exp_map, | |
214 | }) | |
215 | } | |
216 | ||
217 | impl HygieneFrame { | |
218 | pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame { | |
9c376795 FG |
219 | let (info, krate, local_inner) = match file_id.macro_file() { |
220 | None => (None, None, false), | |
221 | Some(macro_file) => { | |
064997fb FG |
222 | let loc = db.lookup_intern_macro_call(macro_file.macro_call_id); |
223 | let info = | |
224 | make_hygiene_info(db, macro_file, &loc).map(|info| (loc.kind.file_id(), info)); | |
225 | match loc.def.kind { | |
226 | MacroDefKind::Declarative(_) => { | |
227 | (info, Some(loc.def.krate), loc.def.local_inner) | |
228 | } | |
229 | MacroDefKind::BuiltIn(..) => (info, Some(loc.def.krate), false), | |
230 | MacroDefKind::BuiltInAttr(..) => (info, None, false), | |
231 | MacroDefKind::BuiltInDerive(..) => (info, None, false), | |
232 | MacroDefKind::BuiltInEager(..) => (info, None, false), | |
233 | MacroDefKind::ProcMacro(..) => (info, None, false), | |
234 | } | |
235 | } | |
236 | }; | |
237 | ||
238 | let (calling_file, info) = match info { | |
239 | None => { | |
240 | return HygieneFrame { | |
241 | expansion: None, | |
242 | local_inner, | |
243 | krate, | |
244 | call_site: None, | |
245 | def_site: None, | |
246 | }; | |
247 | } | |
248 | Some(it) => it, | |
249 | }; | |
250 | ||
251 | let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id)); | |
252 | let call_site = Some(db.hygiene_frame(calling_file)); | |
253 | ||
254 | HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site } | |
255 | } | |
256 | } |