diff options
Diffstat (limited to 'crates/hir_def/src/body.rs')
-rw-r--r-- | crates/hir_def/src/body.rs | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs new file mode 100644 index 000000000..9a9a605dd --- /dev/null +++ b/crates/hir_def/src/body.rs | |||
@@ -0,0 +1,360 @@ | |||
1 | //! Defines `Body`: a lowered representation of bodies of functions, statics and | ||
2 | //! consts. | ||
3 | mod lower; | ||
4 | pub mod scope; | ||
5 | |||
6 | use std::{mem, ops::Index, sync::Arc}; | ||
7 | |||
8 | use arena::{map::ArenaMap, Arena}; | ||
9 | use base_db::CrateId; | ||
10 | use cfg::CfgOptions; | ||
11 | use drop_bomb::DropBomb; | ||
12 | use either::Either; | ||
13 | use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId}; | ||
14 | use rustc_hash::FxHashMap; | ||
15 | use syntax::{ast, AstNode, AstPtr}; | ||
16 | use test_utils::mark; | ||
17 | |||
18 | pub(crate) use lower::LowerCtx; | ||
19 | |||
20 | use crate::{ | ||
21 | attr::Attrs, | ||
22 | db::DefDatabase, | ||
23 | expr::{Expr, ExprId, Pat, PatId}, | ||
24 | item_scope::BuiltinShadowMode, | ||
25 | item_scope::ItemScope, | ||
26 | nameres::CrateDefMap, | ||
27 | path::{ModPath, Path}, | ||
28 | src::HasSource, | ||
29 | AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId, | ||
30 | }; | ||
31 | |||
32 | /// A subset of Expander that only deals with cfg attributes. We only need it to | ||
33 | /// avoid cyclic queries in crate def map during enum processing. | ||
34 | pub(crate) struct CfgExpander { | ||
35 | cfg_options: CfgOptions, | ||
36 | hygiene: Hygiene, | ||
37 | } | ||
38 | |||
39 | pub(crate) struct Expander { | ||
40 | cfg_expander: CfgExpander, | ||
41 | crate_def_map: Arc<CrateDefMap>, | ||
42 | current_file_id: HirFileId, | ||
43 | ast_id_map: Arc<AstIdMap>, | ||
44 | module: ModuleId, | ||
45 | recursion_limit: usize, | ||
46 | } | ||
47 | |||
48 | #[cfg(test)] | ||
49 | const EXPANSION_RECURSION_LIMIT: usize = 32; | ||
50 | |||
51 | #[cfg(not(test))] | ||
52 | const EXPANSION_RECURSION_LIMIT: usize = 128; | ||
53 | |||
54 | impl CfgExpander { | ||
55 | pub(crate) fn new( | ||
56 | db: &dyn DefDatabase, | ||
57 | current_file_id: HirFileId, | ||
58 | krate: CrateId, | ||
59 | ) -> CfgExpander { | ||
60 | let hygiene = Hygiene::new(db.upcast(), current_file_id); | ||
61 | let cfg_options = db.crate_graph()[krate].cfg_options.clone(); | ||
62 | CfgExpander { cfg_options, hygiene } | ||
63 | } | ||
64 | |||
65 | pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs { | ||
66 | Attrs::new(owner, &self.hygiene) | ||
67 | } | ||
68 | |||
69 | pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { | ||
70 | let attrs = self.parse_attrs(owner); | ||
71 | attrs.is_cfg_enabled(&self.cfg_options) | ||
72 | } | ||
73 | } | ||
74 | |||
75 | impl Expander { | ||
76 | pub(crate) fn new( | ||
77 | db: &dyn DefDatabase, | ||
78 | current_file_id: HirFileId, | ||
79 | module: ModuleId, | ||
80 | ) -> Expander { | ||
81 | let cfg_expander = CfgExpander::new(db, current_file_id, module.krate); | ||
82 | let crate_def_map = db.crate_def_map(module.krate); | ||
83 | let ast_id_map = db.ast_id_map(current_file_id); | ||
84 | Expander { | ||
85 | cfg_expander, | ||
86 | crate_def_map, | ||
87 | current_file_id, | ||
88 | ast_id_map, | ||
89 | module, | ||
90 | recursion_limit: 0, | ||
91 | } | ||
92 | } | ||
93 | |||
94 | pub(crate) fn enter_expand<T: ast::AstNode>( | ||
95 | &mut self, | ||
96 | db: &dyn DefDatabase, | ||
97 | local_scope: Option<&ItemScope>, | ||
98 | macro_call: ast::MacroCall, | ||
99 | ) -> Option<(Mark, T)> { | ||
100 | self.recursion_limit += 1; | ||
101 | if self.recursion_limit > EXPANSION_RECURSION_LIMIT { | ||
102 | mark::hit!(your_stack_belongs_to_me); | ||
103 | return None; | ||
104 | } | ||
105 | |||
106 | let macro_call = InFile::new(self.current_file_id, ¯o_call); | ||
107 | |||
108 | if let Some(call_id) = macro_call.as_call_id(db, self.crate_def_map.krate, |path| { | ||
109 | if let Some(local_scope) = local_scope { | ||
110 | if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) { | ||
111 | return Some(def); | ||
112 | } | ||
113 | } | ||
114 | self.resolve_path_as_macro(db, &path) | ||
115 | }) { | ||
116 | let file_id = call_id.as_file(); | ||
117 | if let Some(node) = db.parse_or_expand(file_id) { | ||
118 | if let Some(expr) = T::cast(node) { | ||
119 | log::debug!("macro expansion {:#?}", expr.syntax()); | ||
120 | |||
121 | let mark = Mark { | ||
122 | file_id: self.current_file_id, | ||
123 | ast_id_map: mem::take(&mut self.ast_id_map), | ||
124 | bomb: DropBomb::new("expansion mark dropped"), | ||
125 | }; | ||
126 | self.cfg_expander.hygiene = Hygiene::new(db.upcast(), file_id); | ||
127 | self.current_file_id = file_id; | ||
128 | self.ast_id_map = db.ast_id_map(file_id); | ||
129 | return Some((mark, expr)); | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | |||
134 | // FIXME: Instead of just dropping the error from expansion | ||
135 | // report it | ||
136 | None | ||
137 | } | ||
138 | |||
139 | pub(crate) fn exit(&mut self, db: &dyn DefDatabase, mut mark: Mark) { | ||
140 | self.cfg_expander.hygiene = Hygiene::new(db.upcast(), mark.file_id); | ||
141 | self.current_file_id = mark.file_id; | ||
142 | self.ast_id_map = mem::take(&mut mark.ast_id_map); | ||
143 | self.recursion_limit -= 1; | ||
144 | mark.bomb.defuse(); | ||
145 | } | ||
146 | |||
147 | pub(crate) fn to_source<T>(&self, value: T) -> InFile<T> { | ||
148 | InFile { file_id: self.current_file_id, value } | ||
149 | } | ||
150 | |||
151 | pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { | ||
152 | self.cfg_expander.is_cfg_enabled(owner) | ||
153 | } | ||
154 | |||
155 | fn parse_path(&mut self, path: ast::Path) -> Option<Path> { | ||
156 | Path::from_src(path, &self.cfg_expander.hygiene) | ||
157 | } | ||
158 | |||
159 | fn resolve_path_as_macro(&self, db: &dyn DefDatabase, path: &ModPath) -> Option<MacroDefId> { | ||
160 | self.crate_def_map | ||
161 | .resolve_path(db, self.module.local_id, path, BuiltinShadowMode::Other) | ||
162 | .0 | ||
163 | .take_macros() | ||
164 | } | ||
165 | |||
166 | fn ast_id<N: AstNode>(&self, item: &N) -> AstId<N> { | ||
167 | let file_local_id = self.ast_id_map.ast_id(item); | ||
168 | AstId::new(self.current_file_id, file_local_id) | ||
169 | } | ||
170 | } | ||
171 | |||
172 | pub(crate) struct Mark { | ||
173 | file_id: HirFileId, | ||
174 | ast_id_map: Arc<AstIdMap>, | ||
175 | bomb: DropBomb, | ||
176 | } | ||
177 | |||
178 | /// The body of an item (function, const etc.). | ||
179 | #[derive(Debug, Eq, PartialEq)] | ||
180 | pub struct Body { | ||
181 | pub exprs: Arena<Expr>, | ||
182 | pub pats: Arena<Pat>, | ||
183 | /// The patterns for the function's parameters. While the parameter types are | ||
184 | /// part of the function signature, the patterns are not (they don't change | ||
185 | /// the external type of the function). | ||
186 | /// | ||
187 | /// If this `Body` is for the body of a constant, this will just be | ||
188 | /// empty. | ||
189 | pub params: Vec<PatId>, | ||
190 | /// The `ExprId` of the actual body expression. | ||
191 | pub body_expr: ExprId, | ||
192 | pub item_scope: ItemScope, | ||
193 | } | ||
194 | |||
195 | pub type ExprPtr = AstPtr<ast::Expr>; | ||
196 | pub type ExprSource = InFile<ExprPtr>; | ||
197 | |||
198 | pub type PatPtr = Either<AstPtr<ast::Pat>, AstPtr<ast::SelfParam>>; | ||
199 | pub type PatSource = InFile<PatPtr>; | ||
200 | |||
201 | /// An item body together with the mapping from syntax nodes to HIR expression | ||
202 | /// IDs. This is needed to go from e.g. a position in a file to the HIR | ||
203 | /// expression containing it; but for type inference etc., we want to operate on | ||
204 | /// a structure that is agnostic to the actual positions of expressions in the | ||
205 | /// file, so that we don't recompute types whenever some whitespace is typed. | ||
206 | /// | ||
207 | /// One complication here is that, due to macro expansion, a single `Body` might | ||
208 | /// be spread across several files. So, for each ExprId and PatId, we record | ||
209 | /// both the HirFileId and the position inside the file. However, we only store | ||
210 | /// AST -> ExprId mapping for non-macro files, as it is not clear how to handle | ||
211 | /// this properly for macros. | ||
212 | #[derive(Default, Debug, Eq, PartialEq)] | ||
213 | pub struct BodySourceMap { | ||
214 | expr_map: FxHashMap<ExprSource, ExprId>, | ||
215 | expr_map_back: ArenaMap<ExprId, Result<ExprSource, SyntheticSyntax>>, | ||
216 | pat_map: FxHashMap<PatSource, PatId>, | ||
217 | pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, | ||
218 | field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>, | ||
219 | expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, | ||
220 | } | ||
221 | |||
222 | #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] | ||
223 | pub struct SyntheticSyntax; | ||
224 | |||
225 | impl Body { | ||
226 | pub(crate) fn body_with_source_map_query( | ||
227 | db: &dyn DefDatabase, | ||
228 | def: DefWithBodyId, | ||
229 | ) -> (Arc<Body>, Arc<BodySourceMap>) { | ||
230 | let _p = profile::span("body_with_source_map_query"); | ||
231 | let mut params = None; | ||
232 | |||
233 | let (file_id, module, body) = match def { | ||
234 | DefWithBodyId::FunctionId(f) => { | ||
235 | let f = f.lookup(db); | ||
236 | let src = f.source(db); | ||
237 | params = src.value.param_list(); | ||
238 | (src.file_id, f.module(db), src.value.body().map(ast::Expr::from)) | ||
239 | } | ||
240 | DefWithBodyId::ConstId(c) => { | ||
241 | let c = c.lookup(db); | ||
242 | let src = c.source(db); | ||
243 | (src.file_id, c.module(db), src.value.body()) | ||
244 | } | ||
245 | DefWithBodyId::StaticId(s) => { | ||
246 | let s = s.lookup(db); | ||
247 | let src = s.source(db); | ||
248 | (src.file_id, s.module(db), src.value.body()) | ||
249 | } | ||
250 | }; | ||
251 | let expander = Expander::new(db, file_id, module); | ||
252 | let (body, source_map) = Body::new(db, def, expander, params, body); | ||
253 | (Arc::new(body), Arc::new(source_map)) | ||
254 | } | ||
255 | |||
256 | pub(crate) fn body_query(db: &dyn DefDatabase, def: DefWithBodyId) -> Arc<Body> { | ||
257 | db.body_with_source_map(def).0 | ||
258 | } | ||
259 | |||
260 | fn new( | ||
261 | db: &dyn DefDatabase, | ||
262 | def: DefWithBodyId, | ||
263 | expander: Expander, | ||
264 | params: Option<ast::ParamList>, | ||
265 | body: Option<ast::Expr>, | ||
266 | ) -> (Body, BodySourceMap) { | ||
267 | lower::lower(db, def, expander, params, body) | ||
268 | } | ||
269 | } | ||
270 | |||
271 | impl Index<ExprId> for Body { | ||
272 | type Output = Expr; | ||
273 | |||
274 | fn index(&self, expr: ExprId) -> &Expr { | ||
275 | &self.exprs[expr] | ||
276 | } | ||
277 | } | ||
278 | |||
279 | impl Index<PatId> for Body { | ||
280 | type Output = Pat; | ||
281 | |||
282 | fn index(&self, pat: PatId) -> &Pat { | ||
283 | &self.pats[pat] | ||
284 | } | ||
285 | } | ||
286 | |||
287 | impl BodySourceMap { | ||
288 | pub fn expr_syntax(&self, expr: ExprId) -> Result<ExprSource, SyntheticSyntax> { | ||
289 | self.expr_map_back[expr].clone() | ||
290 | } | ||
291 | |||
292 | pub fn node_expr(&self, node: InFile<&ast::Expr>) -> Option<ExprId> { | ||
293 | let src = node.map(|it| AstPtr::new(it)); | ||
294 | self.expr_map.get(&src).cloned() | ||
295 | } | ||
296 | |||
297 | pub fn node_macro_file(&self, node: InFile<&ast::MacroCall>) -> Option<HirFileId> { | ||
298 | let src = node.map(|it| AstPtr::new(it)); | ||
299 | self.expansions.get(&src).cloned() | ||
300 | } | ||
301 | |||
302 | pub fn pat_syntax(&self, pat: PatId) -> Result<PatSource, SyntheticSyntax> { | ||
303 | self.pat_map_back[pat].clone() | ||
304 | } | ||
305 | |||
306 | pub fn node_pat(&self, node: InFile<&ast::Pat>) -> Option<PatId> { | ||
307 | let src = node.map(|it| Either::Left(AstPtr::new(it))); | ||
308 | self.pat_map.get(&src).cloned() | ||
309 | } | ||
310 | |||
311 | pub fn node_self_param(&self, node: InFile<&ast::SelfParam>) -> Option<PatId> { | ||
312 | let src = node.map(|it| Either::Right(AstPtr::new(it))); | ||
313 | self.pat_map.get(&src).cloned() | ||
314 | } | ||
315 | |||
316 | pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> { | ||
317 | self.field_map[&(expr, field)].clone() | ||
318 | } | ||
319 | } | ||
320 | |||
321 | #[cfg(test)] | ||
322 | mod tests { | ||
323 | use base_db::{fixture::WithFixture, SourceDatabase}; | ||
324 | use test_utils::mark; | ||
325 | |||
326 | use crate::ModuleDefId; | ||
327 | |||
328 | use super::*; | ||
329 | |||
330 | fn lower(ra_fixture: &str) -> Arc<Body> { | ||
331 | let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture); | ||
332 | |||
333 | let krate = db.crate_graph().iter().next().unwrap(); | ||
334 | let def_map = db.crate_def_map(krate); | ||
335 | let module = def_map.modules_for_file(file_id).next().unwrap(); | ||
336 | let module = &def_map[module]; | ||
337 | let fn_def = match module.scope.declarations().next().unwrap() { | ||
338 | ModuleDefId::FunctionId(it) => it, | ||
339 | _ => panic!(), | ||
340 | }; | ||
341 | |||
342 | db.body(fn_def.into()) | ||
343 | } | ||
344 | |||
345 | #[test] | ||
346 | fn your_stack_belongs_to_me() { | ||
347 | mark::check!(your_stack_belongs_to_me); | ||
348 | lower( | ||
349 | " | ||
350 | macro_rules! n_nuple { | ||
351 | ($e:tt) => (); | ||
352 | ($($rest:tt)*) => {{ | ||
353 | (n_nuple!($($rest)*)None,) | ||
354 | }}; | ||
355 | } | ||
356 | fn main() { n_nuple!(1,2,3); } | ||
357 | ", | ||
358 | ); | ||
359 | } | ||
360 | } | ||