]>
Commit | Line | Data |
---|---|---|
d9579d0f AL |
1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
11 | use super::metadata::file_metadata; | |
12 | use super::utils::DIB; | |
13 | ||
14 | use llvm; | |
15 | use llvm::debuginfo::{DIScope, DISubprogram}; | |
16 | use trans::common::CrateContext; | |
17 | use middle::pat_util; | |
e9174d1e | 18 | use rustc::util::nodemap::NodeMap; |
d9579d0f AL |
19 | |
20 | use libc::c_uint; | |
21 | use syntax::codemap::{Span, Pos}; | |
e9174d1e SL |
22 | use syntax::{ast, codemap}; |
23 | ||
24 | use rustc_front; | |
7453a54e | 25 | use rustc_front::hir::{self, PatKind}; |
d9579d0f AL |
26 | |
27 | // This procedure builds the *scope map* for a given function, which maps any | |
28 | // given ast::NodeId in the function's AST to the correct DIScope metadata instance. | |
29 | // | |
30 | // This builder procedure walks the AST in execution order and keeps track of | |
31 | // what belongs to which scope, creating DIScope DIEs along the way, and | |
32 | // introducing *artificial* lexical scope descriptors where necessary. These | |
33 | // artificial scopes allow GDB to correctly handle name shadowing. | |
34 | pub fn create_scope_map(cx: &CrateContext, | |
e9174d1e SL |
35 | args: &[hir::Arg], |
36 | fn_entry_block: &hir::Block, | |
d9579d0f AL |
37 | fn_metadata: DISubprogram, |
38 | fn_ast_id: ast::NodeId) | |
39 | -> NodeMap<DIScope> { | |
40 | let mut scope_map = NodeMap(); | |
41 | ||
42 | let def_map = &cx.tcx().def_map; | |
43 | ||
44 | let mut scope_stack = vec!(ScopeStackEntry { scope_metadata: fn_metadata, name: None }); | |
45 | scope_map.insert(fn_ast_id, fn_metadata); | |
46 | ||
47 | // Push argument identifiers onto the stack so arguments integrate nicely | |
48 | // with variable shadowing. | |
49 | for arg in args { | |
7453a54e | 50 | pat_util::pat_bindings_ident(def_map, &arg.pat, |_, node_id, _, path1| { |
d9579d0f | 51 | scope_stack.push(ScopeStackEntry { scope_metadata: fn_metadata, |
92a42be0 | 52 | name: Some(path1.node.unhygienic_name) }); |
d9579d0f AL |
53 | scope_map.insert(node_id, fn_metadata); |
54 | }) | |
55 | } | |
56 | ||
57 | // Clang creates a separate scope for function bodies, so let's do this too. | |
58 | with_new_scope(cx, | |
59 | fn_entry_block.span, | |
60 | &mut scope_stack, | |
61 | &mut scope_map, | |
62 | |cx, scope_stack, scope_map| { | |
63 | walk_block(cx, fn_entry_block, scope_stack, scope_map); | |
64 | }); | |
65 | ||
66 | return scope_map; | |
67 | } | |
68 | ||
69 | // local helper functions for walking the AST. | |
70 | fn with_new_scope<F>(cx: &CrateContext, | |
71 | scope_span: Span, | |
72 | scope_stack: &mut Vec<ScopeStackEntry> , | |
73 | scope_map: &mut NodeMap<DIScope>, | |
74 | inner_walk: F) where | |
75 | F: FnOnce(&CrateContext, &mut Vec<ScopeStackEntry>, &mut NodeMap<DIScope>), | |
76 | { | |
77 | // Create a new lexical scope and push it onto the stack | |
78 | let loc = cx.sess().codemap().lookup_char_pos(scope_span.lo); | |
79 | let file_metadata = file_metadata(cx, &loc.file.name); | |
80 | let parent_scope = scope_stack.last().unwrap().scope_metadata; | |
81 | ||
82 | let scope_metadata = unsafe { | |
83 | llvm::LLVMDIBuilderCreateLexicalBlock( | |
84 | DIB(cx), | |
85 | parent_scope, | |
86 | file_metadata, | |
87 | loc.line as c_uint, | |
88 | loc.col.to_usize() as c_uint) | |
89 | }; | |
90 | ||
91 | scope_stack.push(ScopeStackEntry { scope_metadata: scope_metadata, name: None }); | |
92 | ||
93 | inner_walk(cx, scope_stack, scope_map); | |
94 | ||
95 | // pop artificial scopes | |
96 | while scope_stack.last().unwrap().name.is_some() { | |
97 | scope_stack.pop(); | |
98 | } | |
99 | ||
100 | if scope_stack.last().unwrap().scope_metadata != scope_metadata { | |
101 | cx.sess().span_bug(scope_span, "debuginfo: Inconsistency in scope management."); | |
102 | } | |
103 | ||
104 | scope_stack.pop(); | |
105 | } | |
106 | ||
107 | struct ScopeStackEntry { | |
108 | scope_metadata: DIScope, | |
109 | name: Option<ast::Name> | |
110 | } | |
111 | ||
112 | fn walk_block(cx: &CrateContext, | |
e9174d1e | 113 | block: &hir::Block, |
d9579d0f AL |
114 | scope_stack: &mut Vec<ScopeStackEntry> , |
115 | scope_map: &mut NodeMap<DIScope>) { | |
116 | scope_map.insert(block.id, scope_stack.last().unwrap().scope_metadata); | |
117 | ||
118 | // The interesting things here are statements and the concluding expression. | |
119 | for statement in &block.stmts { | |
92a42be0 | 120 | scope_map.insert(rustc_front::util::stmt_id(statement), |
d9579d0f AL |
121 | scope_stack.last().unwrap().scope_metadata); |
122 | ||
123 | match statement.node { | |
e9174d1e | 124 | hir::StmtDecl(ref decl, _) => |
7453a54e | 125 | walk_decl(cx, &decl, scope_stack, scope_map), |
e9174d1e SL |
126 | hir::StmtExpr(ref exp, _) | |
127 | hir::StmtSemi(ref exp, _) => | |
7453a54e | 128 | walk_expr(cx, &exp, scope_stack, scope_map), |
d9579d0f AL |
129 | } |
130 | } | |
131 | ||
132 | if let Some(ref exp) = block.expr { | |
7453a54e | 133 | walk_expr(cx, &exp, scope_stack, scope_map); |
d9579d0f AL |
134 | } |
135 | } | |
136 | ||
137 | fn walk_decl(cx: &CrateContext, | |
e9174d1e | 138 | decl: &hir::Decl, |
d9579d0f AL |
139 | scope_stack: &mut Vec<ScopeStackEntry> , |
140 | scope_map: &mut NodeMap<DIScope>) { | |
141 | match *decl { | |
e9174d1e | 142 | codemap::Spanned { node: hir::DeclLocal(ref local), .. } => { |
d9579d0f AL |
143 | scope_map.insert(local.id, scope_stack.last().unwrap().scope_metadata); |
144 | ||
7453a54e | 145 | walk_pattern(cx, &local.pat, scope_stack, scope_map); |
d9579d0f AL |
146 | |
147 | if let Some(ref exp) = local.init { | |
7453a54e | 148 | walk_expr(cx, &exp, scope_stack, scope_map); |
d9579d0f AL |
149 | } |
150 | } | |
151 | _ => () | |
152 | } | |
153 | } | |
154 | ||
155 | fn walk_pattern(cx: &CrateContext, | |
e9174d1e | 156 | pat: &hir::Pat, |
d9579d0f AL |
157 | scope_stack: &mut Vec<ScopeStackEntry> , |
158 | scope_map: &mut NodeMap<DIScope>) { | |
159 | ||
160 | let def_map = &cx.tcx().def_map; | |
161 | ||
162 | // Unfortunately, we cannot just use pat_util::pat_bindings() or | |
163 | // ast_util::walk_pat() here because we have to visit *all* nodes in | |
164 | // order to put them into the scope map. The above functions don't do that. | |
165 | match pat.node { | |
7453a54e | 166 | PatKind::Ident(_, ref path1, ref sub_pat_opt) => { |
d9579d0f AL |
167 | |
168 | // Check if this is a binding. If so we need to put it on the | |
169 | // scope stack and maybe introduce an artificial scope | |
7453a54e | 170 | if pat_util::pat_is_binding(&def_map.borrow(), &pat) { |
d9579d0f | 171 | |
92a42be0 | 172 | let name = path1.node.unhygienic_name; |
d9579d0f AL |
173 | |
174 | // LLVM does not properly generate 'DW_AT_start_scope' fields | |
175 | // for variable DIEs. For this reason we have to introduce | |
176 | // an artificial scope at bindings whenever a variable with | |
177 | // the same name is declared in *any* parent scope. | |
178 | // | |
179 | // Otherwise the following error occurs: | |
180 | // | |
181 | // let x = 10; | |
182 | // | |
183 | // do_something(); // 'gdb print x' correctly prints 10 | |
184 | // | |
185 | // { | |
186 | // do_something(); // 'gdb print x' prints 0, because it | |
187 | // // already reads the uninitialized 'x' | |
188 | // // from the next line... | |
189 | // let x = 100; | |
190 | // do_something(); // 'gdb print x' correctly prints 100 | |
191 | // } | |
192 | ||
193 | // Is there already a binding with that name? | |
194 | // N.B.: this comparison must be UNhygienic... because | |
195 | // gdb knows nothing about the context, so any two | |
196 | // variables with the same name will cause the problem. | |
197 | let need_new_scope = scope_stack | |
198 | .iter() | |
199 | .any(|entry| entry.name == Some(name)); | |
200 | ||
201 | if need_new_scope { | |
202 | // Create a new lexical scope and push it onto the stack | |
203 | let loc = cx.sess().codemap().lookup_char_pos(pat.span.lo); | |
204 | let file_metadata = file_metadata(cx, &loc.file.name); | |
205 | let parent_scope = scope_stack.last().unwrap().scope_metadata; | |
206 | ||
207 | let scope_metadata = unsafe { | |
208 | llvm::LLVMDIBuilderCreateLexicalBlock( | |
209 | DIB(cx), | |
210 | parent_scope, | |
211 | file_metadata, | |
212 | loc.line as c_uint, | |
213 | loc.col.to_usize() as c_uint) | |
214 | }; | |
215 | ||
216 | scope_stack.push(ScopeStackEntry { | |
217 | scope_metadata: scope_metadata, | |
218 | name: Some(name) | |
219 | }); | |
220 | ||
221 | } else { | |
222 | // Push a new entry anyway so the name can be found | |
223 | let prev_metadata = scope_stack.last().unwrap().scope_metadata; | |
224 | scope_stack.push(ScopeStackEntry { | |
225 | scope_metadata: prev_metadata, | |
226 | name: Some(name) | |
227 | }); | |
228 | } | |
229 | } | |
230 | ||
231 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); | |
232 | ||
233 | if let Some(ref sub_pat) = *sub_pat_opt { | |
7453a54e | 234 | walk_pattern(cx, &sub_pat, scope_stack, scope_map); |
d9579d0f AL |
235 | } |
236 | } | |
237 | ||
7453a54e | 238 | PatKind::Wild => { |
d9579d0f AL |
239 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
240 | } | |
241 | ||
7453a54e | 242 | PatKind::TupleStruct(_, ref sub_pats_opt) => { |
d9579d0f AL |
243 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
244 | ||
245 | if let Some(ref sub_pats) = *sub_pats_opt { | |
246 | for p in sub_pats { | |
7453a54e | 247 | walk_pattern(cx, &p, scope_stack, scope_map); |
d9579d0f AL |
248 | } |
249 | } | |
250 | } | |
251 | ||
7453a54e | 252 | PatKind::Path(..) | PatKind::QPath(..) => { |
d9579d0f AL |
253 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
254 | } | |
255 | ||
7453a54e | 256 | PatKind::Struct(_, ref field_pats, _) => { |
d9579d0f AL |
257 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
258 | ||
259 | for &codemap::Spanned { | |
e9174d1e | 260 | node: hir::FieldPat { pat: ref sub_pat, .. }, |
d9579d0f | 261 | .. |
62682a34 | 262 | } in field_pats { |
7453a54e | 263 | walk_pattern(cx, &sub_pat, scope_stack, scope_map); |
d9579d0f AL |
264 | } |
265 | } | |
266 | ||
7453a54e | 267 | PatKind::Tup(ref sub_pats) => { |
d9579d0f AL |
268 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
269 | ||
270 | for sub_pat in sub_pats { | |
7453a54e | 271 | walk_pattern(cx, &sub_pat, scope_stack, scope_map); |
d9579d0f AL |
272 | } |
273 | } | |
274 | ||
7453a54e | 275 | PatKind::Box(ref sub_pat) | PatKind::Ref(ref sub_pat, _) => { |
d9579d0f | 276 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
7453a54e | 277 | walk_pattern(cx, &sub_pat, scope_stack, scope_map); |
d9579d0f AL |
278 | } |
279 | ||
7453a54e | 280 | PatKind::Lit(ref exp) => { |
d9579d0f | 281 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
7453a54e | 282 | walk_expr(cx, &exp, scope_stack, scope_map); |
d9579d0f AL |
283 | } |
284 | ||
7453a54e | 285 | PatKind::Range(ref exp1, ref exp2) => { |
d9579d0f | 286 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
7453a54e SL |
287 | walk_expr(cx, &exp1, scope_stack, scope_map); |
288 | walk_expr(cx, &exp2, scope_stack, scope_map); | |
d9579d0f AL |
289 | } |
290 | ||
7453a54e | 291 | PatKind::Vec(ref front_sub_pats, ref middle_sub_pats, ref back_sub_pats) => { |
d9579d0f AL |
292 | scope_map.insert(pat.id, scope_stack.last().unwrap().scope_metadata); |
293 | ||
294 | for sub_pat in front_sub_pats { | |
7453a54e | 295 | walk_pattern(cx, &sub_pat, scope_stack, scope_map); |
d9579d0f AL |
296 | } |
297 | ||
298 | if let Some(ref sub_pat) = *middle_sub_pats { | |
7453a54e | 299 | walk_pattern(cx, &sub_pat, scope_stack, scope_map); |
d9579d0f AL |
300 | } |
301 | ||
302 | for sub_pat in back_sub_pats { | |
7453a54e | 303 | walk_pattern(cx, &sub_pat, scope_stack, scope_map); |
d9579d0f AL |
304 | } |
305 | } | |
d9579d0f AL |
306 | } |
307 | } | |
308 | ||
309 | fn walk_expr(cx: &CrateContext, | |
e9174d1e | 310 | exp: &hir::Expr, |
d9579d0f AL |
311 | scope_stack: &mut Vec<ScopeStackEntry> , |
312 | scope_map: &mut NodeMap<DIScope>) { | |
313 | ||
314 | scope_map.insert(exp.id, scope_stack.last().unwrap().scope_metadata); | |
315 | ||
316 | match exp.node { | |
e9174d1e SL |
317 | hir::ExprLit(_) | |
318 | hir::ExprBreak(_) | | |
319 | hir::ExprAgain(_) | | |
320 | hir::ExprPath(..) => {} | |
321 | ||
322 | hir::ExprCast(ref sub_exp, _) | | |
9cc50fc6 | 323 | hir::ExprType(ref sub_exp, _) | |
e9174d1e SL |
324 | hir::ExprAddrOf(_, ref sub_exp) | |
325 | hir::ExprField(ref sub_exp, _) | | |
b039eaaf | 326 | hir::ExprTupField(ref sub_exp, _) => |
7453a54e | 327 | walk_expr(cx, &sub_exp, scope_stack, scope_map), |
d9579d0f | 328 | |
b039eaaf | 329 | hir::ExprBox(ref sub_expr) => { |
7453a54e | 330 | walk_expr(cx, &sub_expr, scope_stack, scope_map); |
d9579d0f AL |
331 | } |
332 | ||
e9174d1e | 333 | hir::ExprRet(ref exp_opt) => match *exp_opt { |
7453a54e | 334 | Some(ref sub_exp) => walk_expr(cx, &sub_exp, scope_stack, scope_map), |
d9579d0f AL |
335 | None => () |
336 | }, | |
337 | ||
e9174d1e | 338 | hir::ExprUnary(_, ref sub_exp) => { |
7453a54e | 339 | walk_expr(cx, &sub_exp, scope_stack, scope_map); |
d9579d0f AL |
340 | } |
341 | ||
e9174d1e SL |
342 | hir::ExprAssignOp(_, ref lhs, ref rhs) | |
343 | hir::ExprIndex(ref lhs, ref rhs) | | |
344 | hir::ExprBinary(_, ref lhs, ref rhs) => { | |
7453a54e SL |
345 | walk_expr(cx, &lhs, scope_stack, scope_map); |
346 | walk_expr(cx, &rhs, scope_stack, scope_map); | |
d9579d0f AL |
347 | } |
348 | ||
e9174d1e | 349 | hir::ExprRange(ref start, ref end) => { |
7453a54e SL |
350 | start.as_ref().map(|e| walk_expr(cx, &e, scope_stack, scope_map)); |
351 | end.as_ref().map(|e| walk_expr(cx, &e, scope_stack, scope_map)); | |
d9579d0f AL |
352 | } |
353 | ||
e9174d1e SL |
354 | hir::ExprVec(ref init_expressions) | |
355 | hir::ExprTup(ref init_expressions) => { | |
d9579d0f | 356 | for ie in init_expressions { |
7453a54e | 357 | walk_expr(cx, &ie, scope_stack, scope_map); |
d9579d0f AL |
358 | } |
359 | } | |
360 | ||
e9174d1e SL |
361 | hir::ExprAssign(ref sub_exp1, ref sub_exp2) | |
362 | hir::ExprRepeat(ref sub_exp1, ref sub_exp2) => { | |
7453a54e SL |
363 | walk_expr(cx, &sub_exp1, scope_stack, scope_map); |
364 | walk_expr(cx, &sub_exp2, scope_stack, scope_map); | |
d9579d0f AL |
365 | } |
366 | ||
e9174d1e | 367 | hir::ExprIf(ref cond_exp, ref then_block, ref opt_else_exp) => { |
7453a54e | 368 | walk_expr(cx, &cond_exp, scope_stack, scope_map); |
d9579d0f AL |
369 | |
370 | with_new_scope(cx, | |
371 | then_block.span, | |
372 | scope_stack, | |
373 | scope_map, | |
374 | |cx, scope_stack, scope_map| { | |
7453a54e | 375 | walk_block(cx, &then_block, scope_stack, scope_map); |
d9579d0f AL |
376 | }); |
377 | ||
378 | match *opt_else_exp { | |
379 | Some(ref else_exp) => | |
7453a54e | 380 | walk_expr(cx, &else_exp, scope_stack, scope_map), |
d9579d0f AL |
381 | _ => () |
382 | } | |
383 | } | |
384 | ||
e9174d1e | 385 | hir::ExprWhile(ref cond_exp, ref loop_body, _) => { |
7453a54e | 386 | walk_expr(cx, &cond_exp, scope_stack, scope_map); |
d9579d0f AL |
387 | |
388 | with_new_scope(cx, | |
389 | loop_body.span, | |
390 | scope_stack, | |
391 | scope_map, | |
392 | |cx, scope_stack, scope_map| { | |
7453a54e | 393 | walk_block(cx, &loop_body, scope_stack, scope_map); |
d9579d0f AL |
394 | }) |
395 | } | |
396 | ||
e9174d1e SL |
397 | hir::ExprLoop(ref block, _) | |
398 | hir::ExprBlock(ref block) => { | |
d9579d0f AL |
399 | with_new_scope(cx, |
400 | block.span, | |
401 | scope_stack, | |
402 | scope_map, | |
403 | |cx, scope_stack, scope_map| { | |
7453a54e | 404 | walk_block(cx, &block, scope_stack, scope_map); |
d9579d0f AL |
405 | }) |
406 | } | |
407 | ||
e9174d1e | 408 | hir::ExprClosure(_, ref decl, ref block) => { |
d9579d0f AL |
409 | with_new_scope(cx, |
410 | block.span, | |
411 | scope_stack, | |
412 | scope_map, | |
413 | |cx, scope_stack, scope_map| { | |
e9174d1e | 414 | for &hir::Arg { pat: ref pattern, .. } in &decl.inputs { |
7453a54e | 415 | walk_pattern(cx, &pattern, scope_stack, scope_map); |
d9579d0f AL |
416 | } |
417 | ||
7453a54e | 418 | walk_block(cx, &block, scope_stack, scope_map); |
d9579d0f AL |
419 | }) |
420 | } | |
421 | ||
e9174d1e | 422 | hir::ExprCall(ref fn_exp, ref args) => { |
7453a54e | 423 | walk_expr(cx, &fn_exp, scope_stack, scope_map); |
d9579d0f AL |
424 | |
425 | for arg_exp in args { | |
7453a54e | 426 | walk_expr(cx, &arg_exp, scope_stack, scope_map); |
d9579d0f AL |
427 | } |
428 | } | |
429 | ||
e9174d1e | 430 | hir::ExprMethodCall(_, _, ref args) => { |
d9579d0f | 431 | for arg_exp in args { |
7453a54e | 432 | walk_expr(cx, &arg_exp, scope_stack, scope_map); |
d9579d0f AL |
433 | } |
434 | } | |
435 | ||
e9174d1e | 436 | hir::ExprMatch(ref discriminant_exp, ref arms, _) => { |
7453a54e | 437 | walk_expr(cx, &discriminant_exp, scope_stack, scope_map); |
d9579d0f AL |
438 | |
439 | // For each arm we have to first walk the pattern as these might | |
440 | // introduce new artificial scopes. It should be sufficient to | |
441 | // walk only one pattern per arm, as they all must contain the | |
442 | // same binding names. | |
443 | ||
444 | for arm_ref in arms { | |
445 | let arm_span = arm_ref.pats[0].span; | |
446 | ||
447 | with_new_scope(cx, | |
448 | arm_span, | |
449 | scope_stack, | |
450 | scope_map, | |
451 | |cx, scope_stack, scope_map| { | |
452 | for pat in &arm_ref.pats { | |
7453a54e | 453 | walk_pattern(cx, &pat, scope_stack, scope_map); |
d9579d0f AL |
454 | } |
455 | ||
456 | if let Some(ref guard_exp) = arm_ref.guard { | |
7453a54e | 457 | walk_expr(cx, &guard_exp, scope_stack, scope_map) |
d9579d0f AL |
458 | } |
459 | ||
7453a54e | 460 | walk_expr(cx, &arm_ref.body, scope_stack, scope_map); |
d9579d0f AL |
461 | }) |
462 | } | |
463 | } | |
464 | ||
e9174d1e SL |
465 | hir::ExprStruct(_, ref fields, ref base_exp) => { |
466 | for &hir::Field { expr: ref exp, .. } in fields { | |
7453a54e | 467 | walk_expr(cx, &exp, scope_stack, scope_map); |
d9579d0f AL |
468 | } |
469 | ||
470 | match *base_exp { | |
7453a54e | 471 | Some(ref exp) => walk_expr(cx, &exp, scope_stack, scope_map), |
d9579d0f AL |
472 | None => () |
473 | } | |
474 | } | |
475 | ||
e9174d1e | 476 | hir::ExprInlineAsm(hir::InlineAsm { ref inputs, |
d9579d0f AL |
477 | ref outputs, |
478 | .. }) => { | |
479 | // inputs, outputs: Vec<(String, P<Expr>)> | |
480 | for &(_, ref exp) in inputs { | |
7453a54e | 481 | walk_expr(cx, &exp, scope_stack, scope_map); |
d9579d0f AL |
482 | } |
483 | ||
9cc50fc6 | 484 | for out in outputs { |
7453a54e | 485 | walk_expr(cx, &out.expr, scope_stack, scope_map); |
d9579d0f AL |
486 | } |
487 | } | |
488 | } | |
92a42be0 | 489 | } |