@samitouri / QOS-React-2 / commits / 19ba002a12

[rust] First pass of function context analysis

This is a quick "good enough" first pass at computing function expression context variables. It definitely needs to be overhauled, but it's enough to make a lot of common cases work correctly. First, this PR adds a hand-rolled Visitor trait for `estree`. Long-term that should probably be code-generated, but there are some subtleties to it such as the `visit_lvalue(callback)` helper which has to be wrapped around various calls (or we need some other way to distinguish identifiers within lvalues from identifiers within rvaluess). So for now it makes sense to hand-roll it until we are more confident in exactly how it should work. Given that visitor, i was able to port part of the existing `gatherCapturedDeps()` to Rust with some modifications. Note that we assume _something_ has run name resolution on the estree to match up identifiers to the declaration they refer to. But we don't store information about parent scopes so we can't walk up to check where things were defined. Instead we do the following: * Build up a list of all referenced identifiers * Also build up a set of bindings defined in the function itself * After visiting, filter our the first list to only include identifiers not defined by the function itself, and to eliminate duplicates. This covers the majority of cases: the most obvious gap is nested function expressions though actually that should just work.

Joe Savona committed Jul 13, 2023 at 15:57 UTC 19ba002a1295bccaf3e493474482224b4e6fc538
11 files changed +470 -36
compiler/forget/crates/build-hir/src/build.rs
+48 -21
@@ -1,19 +1,20 @@
1 -use bumpalo::{
2 - boxed::Box,
3 - collections::{String, Vec},
4 -};
1 +use std::collections::HashSet;
2 +
3 +use bumpalo::{boxed::Box, collections::String};
4 use estree::{
5 AssignmentTarget, BinaryExpression, BlockStatement, Expression, ForInit, ForStatement,
7 - FunctionExpression, IfStatement, JsValue, Literal, Pattern, Statement, VariableDeclarationKind,
6 + Function, FunctionExpression, IfStatement, JsValue, Literal, Pattern, Statement,
7 + VariableDeclarationKind,
8 };
9 use hir::{
10 - ArrayElement, BlockKind, BranchTerminal, Environment, ForTerminal, Function, GotoKind,
11 - IdentifierOperand, InstrIx, InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal,
12 - Operand, PrimitiveValue, TerminalValue,
10 + ArrayElement, BlockKind, BranchTerminal, Environment, ForTerminal, GotoKind, IdentifierOperand,
11 + InstrIx, InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal, Operand,
12 + PrimitiveValue, TerminalValue,
13 };
14
15 use crate::{
16 builder::{Binding, Builder, LoopScope},
17 + context::get_context_identifiers,
18 error::DiagnosticError,
19 BuildDiagnostic, ErrorSeverity,
20 };
@@ -26,8 +27,8 @@ use crate::{
27 /// that is not yet supported.
28 pub fn build<'a>(
29 env: &'a Environment<'a>,
29 - fun: estree::Function,
30 -) -> Result<Box<'a, Function<'a>>, BuildDiagnostic> {
30 + fun: Function,
31 +) -> Result<Box<'a, hir::Function<'a>>, BuildDiagnostic> {
32 let mut builder = Builder::new(env);
33
34 match fun.body {
@@ -78,7 +79,7 @@ pub fn build<'a>(
79 );
80
81 let body = builder.build()?;
81 - Ok(env.box_new(Function {
82 + Ok(env.box_new(hir::Function {
83 id: fun
84 .id
85 .map(|id| String::from_str_in(&id.name, &env.allocator)),
@@ -179,7 +180,7 @@ fn lower_statement<'a>(
180 } else {
181 if let Pattern::Identifier(id) = declaration.id {
182 // TODO: handle unbound variables
182 - let binding = builder.resolve_binding(&id)?;
183 + let binding = builder.resolve_identifier(&id)?;
184 let identifier = match binding {
185 Binding::Local(identifier) => identifier,
186 _ => {
@@ -348,7 +349,7 @@ fn lower_expression<'a>(
349 let value = match expr {
350 Expression::Identifier(expr) => {
351 // TODO: handle unbound variables
351 - let binding = builder.resolve_binding(&expr)?;
352 + let binding = builder.resolve_identifier(&expr)?;
353 match binding {
354 Binding::Local(identifier) => {
355 let place = IdentifierOperand {
@@ -425,13 +426,7 @@ fn lower_expression<'a>(
426 }
427
428 Expression::FunctionExpression(expr) => {
428 - let FunctionExpression { function, .. } = *expr;
429 - let fun = build(env, function)?;
430 - InstructionValue::Function(hir::FunctionExpression {
431 - // TODO: collect dependencies!
432 - dependencies: env.vec_new(),
433 - lowered_function: fun,
434 - })
429 + InstructionValue::Function(lower_function(env, builder, *expr)?)
430 }
431
432 _ => todo!("Lower expr {expr:#?}"),
@@ -439,6 +434,38 @@ fn lower_expression<'a>(
434 Ok(builder.push(value))
435 }
436
437 +fn lower_function<'a>(
438 + env: &'a Environment<'a>,
439 + builder: &mut Builder<'a>,
440 + expr: FunctionExpression,
441 +) -> Result<hir::FunctionExpression<'a>, BuildDiagnostic> {
442 + let FunctionExpression { function, .. } = expr;
443 + let context_identifiers = get_context_identifiers(env, &function);
444 + let mut context = env.vec_new();
445 + let mut seen = HashSet::new();
446 + for identifier in context_identifiers {
447 + match builder.resolve_identifier(identifier)? {
448 + Binding::Local(identifier) => {
449 + if !seen.insert(identifier.id) {
450 + continue;
451 + }
452 + context.push(IdentifierOperand {
453 + effect: None,
454 + identifier,
455 + });
456 + }
457 + _ => {}
458 + }
459 + }
460 + let mut fun = build(env, function)?;
461 + fun.context = context;
462 + Ok(hir::FunctionExpression {
463 + // TODO: collect dependencies!
464 + dependencies: env.vec_new(),
465 + lowered_function: fun,
466 + })
467 +}
468 +
469 fn lower_assignment<'a>(
470 env: &'a Environment<'a>,
471 builder: &mut Builder<'a>,
@@ -470,7 +497,7 @@ fn lower_identifier_for_assignment<'a>(
497 _kind: InstructionKind,
498 identifier: estree::Identifier,
499 ) -> Result<IdentifierOperand<'a>, BuildDiagnostic> {
473 - let binding = builder.resolve_binding(&identifier)?;
500 + let binding = builder.resolve_identifier(&identifier)?;
501 match binding {
502 Binding::Module(..) | Binding::Global => Err(BuildDiagnostic::new(
503 DiagnosticError::ReassignedGlobal,
compiler/forget/crates/build-hir/src/builder.rs
+1 -1
@@ -325,7 +325,7 @@ impl<'a> Builder<'a> {
325 ))
326 }
327
328 - pub(crate) fn resolve_binding(
328 + pub(crate) fn resolve_identifier(
329 &mut self,
330 identifier: &estree::Identifier,
331 ) -> Result<Binding<'a>, BuildDiagnostic> {
compiler/forget/crates/build-hir/src/context.rs new
+102
@@ -0,0 +1,102 @@
1 +use std::collections::HashSet;
2 +
3 +use estree::{Binding, BindingId, Expression, ExpressionOrSuper, Function, Identifier, Visitor};
4 +use hir::Environment;
5 +
6 +pub(crate) fn get_context_identifiers<'a, 'ast>(
7 + _env: &'a Environment<'a>,
8 + function: &'ast Function,
9 +) -> Vec<&'ast Identifier> {
10 + let mut visitor = ContextVisitor::new();
11 + visitor.visit_function(function);
12 + let ContextVisitor {
13 + free_variables,
14 + defined,
15 + ..
16 + } = visitor;
17 + free_variables
18 + .into_iter()
19 + .filter(|identifier| match &identifier.binding {
20 + Some(Binding::Local(id)) if !defined.contains(id) => true,
21 + _ => false,
22 + })
23 + .collect()
24 +}
25 +
26 +struct ContextVisitor<'ast> {
27 + free_variables: Vec<&'ast Identifier>,
28 + defined: HashSet<BindingId>,
29 + lvalue: bool,
30 +}
31 +
32 +impl<'ast> ContextVisitor<'ast> {
33 + fn new() -> Self {
34 + Self {
35 + free_variables: Default::default(),
36 + defined: Default::default(),
37 + lvalue: false,
38 + }
39 + }
40 +}
41 +
42 +impl<'ast> Visitor<'ast> for ContextVisitor<'ast> {
43 + fn visit_lvalue<F>(&mut self, f: F)
44 + where
45 + F: FnOnce(&mut Self) -> (),
46 + {
47 + let prev_lvalue = self.lvalue;
48 + self.lvalue = true;
49 + f(self);
50 + self.lvalue = prev_lvalue;
51 + }
52 +
53 + fn visit_function(&mut self, function: &'ast Function) {
54 + self.visit_lvalue(|visitor| {
55 + for param in &function.params {
56 + visitor.visit_pattern(param);
57 + }
58 + });
59 + self.default_visit_function(function);
60 + }
61 +
62 + fn visit_expression(&mut self, expr: &'ast Expression) {
63 + if let Expression::AssignmentExpression(expr) = expr {
64 + self.visit_lvalue(|visitor| visitor.visit_assignment_target(&expr.left));
65 + self.visit_expression(&expr.right);
66 + return;
67 + }
68 + let mut object = expr;
69 + while let Expression::MemberExpression(expr) = object {
70 + match &expr.object {
71 + ExpressionOrSuper::Super(_) => return,
72 + ExpressionOrSuper::Expression(expr) => {
73 + object = expr;
74 + }
75 + }
76 + if expr.computed {
77 + self.visit_expression(&expr.property);
78 + }
79 + }
80 + if let Expression::Identifier(identifier) = object {
81 + self.visit_identifier(identifier);
82 + } else {
83 + self.default_visit_expression(expr);
84 + }
85 + }
86 +
87 + fn visit_identifier(&mut self, identifier: &'ast Identifier) {
88 + let binding = identifier.binding.unwrap();
89 + match binding {
90 + Binding::Local(binding_id) => {
91 + if self.lvalue {
92 + // println!("lvalue {identifier:?}");
93 + self.defined.insert(binding_id);
94 + } else {
95 + // println!("rvalue {identifier:?}");
96 + self.free_variables.push(identifier);
97 + }
98 + }
99 + _ => {}
100 + }
101 + }
102 +}
compiler/forget/crates/build-hir/src/lib.rs
+1
@@ -1,5 +1,6 @@
1 mod build;
2 mod builder;
3 +mod context;
4 mod error;
5
6 pub use build::build;
compiler/forget/crates/estree/src/lib.rs
+2
@@ -3,11 +3,13 @@ mod generated;
3 mod generated_extensions;
4 mod js_value;
5 mod range;
6 +mod visit;
7
8 pub use binding::{Binding, BindingId};
9 pub use generated::*;
10 pub use js_value::JsValue;
11 pub use range::SourceRange;
12 +pub use visit::*;
13
14 #[cfg(test)]
15 mod tests {
compiler/forget/crates/estree/src/visit.rs new
+296
@@ -0,0 +1,296 @@
1 +use crate::{
2 + AssignmentTarget, Expression, ExpressionOrSpread, ForInInit, ForInit, Function, FunctionBody,
3 + Identifier, ImportDeclarationSpecifier, ImportOrExportDeclaration, Literal, ModuleItem,
4 + Pattern, Program, Statement, SwitchCase, VariableDeclarator,
5 +};
6 +
7 +/// Trait for visiting an estree
8 +pub trait Visitor<'ast> {
9 + fn visit_lvalue<F>(&mut self, f: F)
10 + where
11 + F: FnOnce(&mut Self) -> (),
12 + {
13 + f(self);
14 + }
15 +
16 + fn visit_program(&mut self, program: &'ast Program) {
17 + for item in &program.body {
18 + self.visit_module_item(item);
19 + }
20 + }
21 +
22 + fn visit_function(&mut self, function: &'ast Function) {
23 + self.default_visit_function(function);
24 + }
25 +
26 + fn default_visit_function(&mut self, function: &'ast Function) {
27 + // todo: params and such
28 + match &function.body {
29 + Some(FunctionBody::BlockStatement(body)) => {
30 + for stmt in &body.body {
31 + self.visit_statement(stmt)
32 + }
33 + }
34 + Some(FunctionBody::Expression(body)) => self.visit_expression(body),
35 + None => {}
36 + }
37 + }
38 +
39 + fn visit_module_item(&mut self, item: &'ast ModuleItem) {
40 + match item {
41 + ModuleItem::Statement(item) => self.visit_statement(item),
42 + ModuleItem::ImportOrExportDeclaration(item) => {
43 + self.visit_import_or_export_declaration(item)
44 + }
45 + }
46 + }
47 +
48 + fn visit_import_or_export_declaration(&mut self, declaration: &'ast ImportOrExportDeclaration) {
49 + match declaration {
50 + ImportOrExportDeclaration::ImportDeclaration(declaration) => {
51 + self.visit_lvalue(|visitor| {
52 + for specifier in &declaration.specifiers {
53 + visitor.visit_import_declaration_specifier(specifier, &declaration.source)
54 + }
55 + });
56 + self.visit_import_source(&declaration.source);
57 + }
58 + }
59 + }
60 +
61 + fn visit_import_declaration_specifier(
62 + &mut self,
63 + specifier: &'ast ImportDeclarationSpecifier,
64 + _source: &'ast Literal,
65 + ) {
66 + match specifier {
67 + ImportDeclarationSpecifier::ImportSpecifier(specifier) => {
68 + self.visit_identifier(&specifier.local);
69 + }
70 + ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => {
71 + self.visit_identifier(&specifier.local);
72 + }
73 + ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => {
74 + self.visit_identifier(&specifier.local);
75 + }
76 + }
77 + }
78 +
79 + fn visit_statement(&mut self, stmt: &'ast Statement) {
80 + self.default_visit_statement(stmt);
81 + }
82 +
83 + fn default_visit_statement(&mut self, stmt: &'ast Statement) {
84 + match stmt {
85 + Statement::BlockStatement(stmt) => {
86 + for stmt in &stmt.body {
87 + self.visit_statement(stmt)
88 + }
89 + }
90 + Statement::BreakStatement(_stmt) => {
91 + // todo
92 + }
93 + Statement::ContinueStatement(_stmt) => {
94 + // todo
95 + }
96 + Statement::DebuggerStatement(_stmt) => {
97 + // todo
98 + }
99 + Statement::DoWhileStatement(stmt) => {
100 + self.visit_statement(&stmt.body);
101 + self.visit_expression(&stmt.test);
102 + }
103 + Statement::EmptyStatement(_stmt) => {
104 + // nothing to do
105 + }
106 + Statement::ExpressionStatement(stmt) => {
107 + self.visit_expression(&stmt.expression);
108 + }
109 + Statement::ForInStatement(stmt) => {
110 + self.visit_for_in_init(&stmt.left);
111 + self.visit_expression(&stmt.right);
112 + self.visit_statement(&stmt.body);
113 + }
114 + Statement::ForOfStatement(stmt) => {
115 + self.visit_for_in_init(&stmt.left);
116 + self.visit_expression(&stmt.right);
117 + self.visit_statement(&stmt.body);
118 + }
119 + Statement::ForStatement(stmt) => {
120 + if let Some(init) = &stmt.init {
121 + self.visit_for_init(init);
122 + }
123 + if let Some(test) = &stmt.test {
124 + self.visit_expression(test);
125 + }
126 + if let Some(update) = &stmt.update {
127 + self.visit_expression(update);
128 + }
129 + self.visit_statement(&stmt.body);
130 + }
131 + Statement::FunctionDeclaration(stmt) => {
132 + self.visit_function(&stmt.function);
133 + }
134 + Statement::IfStatement(stmt) => {
135 + self.visit_expression(&stmt.test);
136 + self.visit_statement(&stmt.consequent);
137 + if let Some(alternate) = &stmt.alternate {
138 + self.visit_statement(alternate);
139 + }
140 + }
141 + Statement::LabeledStatement(stmt) => {
142 + self.visit_statement(&stmt.body);
143 + }
144 + Statement::ReturnStatement(stmt) => {
145 + if let Some(argument) = &stmt.argument {
146 + self.visit_expression(argument);
147 + }
148 + }
149 + Statement::SwitchStatement(stmt) => {
150 + self.visit_expression(&stmt.discriminant);
151 + for case_ in &stmt.cases {
152 + self.visit_case(case_);
153 + }
154 + }
155 + Statement::ThrowStatement(stmt) => {
156 + self.visit_expression(&stmt.argument);
157 + }
158 + Statement::TryStatement(stmt) => {
159 + for item in &stmt.block.body {
160 + self.visit_statement(item);
161 + }
162 + if let Some(handler) = &stmt.handler {
163 + self.visit_lvalue(|visitor| visitor.visit_pattern(&handler.param));
164 + for item in &handler.body.body {
165 + self.visit_statement(item);
166 + }
167 + }
168 + if let Some(finalizer) = &stmt.finalizer {
169 + for item in &finalizer.body {
170 + self.visit_statement(item);
171 + }
172 + }
173 + }
174 + Statement::VariableDeclaration(stmt) => {
175 + for decl in &stmt.declarations {
176 + self.visit_variable_declarator(decl);
177 + }
178 + }
179 + Statement::WhileStatement(stmt) => {
180 + self.visit_expression(&stmt.test);
181 + self.visit_statement(&stmt.body);
182 + }
183 + Statement::WithStatement(stmt) => {
184 + self.visit_expression(&stmt.object);
185 + self.visit_statement(&stmt.body);
186 + }
187 + }
188 + }
189 +
190 + fn visit_case(&mut self, case_: &'ast SwitchCase) {
191 + if let Some(test) = &case_.test {
192 + self.visit_expression(test);
193 + }
194 + for stmt in &case_.consequent {
195 + self.visit_statement(stmt)
196 + }
197 + }
198 +
199 + fn visit_for_init(&mut self, init: &'ast ForInit) {
200 + match init {
201 + ForInit::Expression(init) => {
202 + self.visit_expression(init);
203 + }
204 + ForInit::VariableDeclaration(init) => {
205 + for decl in &init.declarations {
206 + self.visit_variable_declarator(decl);
207 + }
208 + }
209 + }
210 + }
211 +
212 + fn visit_for_in_init(&mut self, init: &'ast ForInInit) {
213 + match init {
214 + ForInInit::Pattern(init) => {
215 + self.visit_pattern(init);
216 + }
217 + ForInInit::VariableDeclaration(init) => {
218 + for decl in &init.declarations {
219 + self.visit_variable_declarator(decl);
220 + }
221 + }
222 + }
223 + }
224 +
225 + fn visit_pattern(&mut self, pattern: &'ast Pattern) {
226 + match pattern {
227 + Pattern::Identifier(pattern) => self.visit_identifier(pattern),
228 + }
229 + }
230 +
231 + fn visit_variable_declarator(&mut self, decl: &'ast VariableDeclarator) {
232 + self.visit_lvalue(|visitor| {
233 + visitor.visit_pattern(&decl.id);
234 + });
235 + if let Some(init) = &decl.init {
236 + self.visit_expression(init);
237 + }
238 + }
239 +
240 + fn visit_assignment_target(&mut self, target: &'ast AssignmentTarget) {
241 + match target {
242 + AssignmentTarget::Expression(target) => {
243 + self.visit_expression(target);
244 + }
245 + AssignmentTarget::Pattern(target) => self.visit_pattern(target),
246 + }
247 + }
248 +
249 + fn visit_expression(&mut self, expr: &'ast Expression) {
250 + self.visit_expression(expr);
251 + }
252 +
253 + fn default_visit_expression(&mut self, expr: &'ast Expression) {
254 + match expr {
255 + Expression::ArrayExpression(expr) => {
256 + for item in &expr.elements {
257 + match item {
258 + Some(ExpressionOrSpread::SpreadElement(item)) => {
259 + self.visit_expression(&item.argument)
260 + }
261 + Some(ExpressionOrSpread::Expression(item)) => self.visit_expression(item),
262 + _ => {}
263 + }
264 + }
265 + }
266 + Expression::AssignmentExpression(expr) => {
267 + self.visit_lvalue(|visitor| visitor.visit_assignment_target(&expr.left));
268 + self.visit_expression(&expr.right);
269 + }
270 + Expression::BinaryExpression(expr) => {
271 + self.visit_expression(&expr.left);
272 + self.visit_expression(&expr.right);
273 + }
274 + Expression::Identifier(expr) => {
275 + self.visit_identifier(expr);
276 + }
277 + Expression::Literal(expr) => self.visit_literal(expr),
278 + Expression::FunctionExpression(expr) => self.visit_function(&expr.function),
279 + _ => {
280 + todo!("more expression types")
281 + }
282 + }
283 + }
284 +
285 + fn visit_identifier(&mut self, _identifier: &'ast Identifier) {
286 + // nothing to do unless overridden
287 + }
288 +
289 + fn visit_import_source(&mut self, literal: &'ast Literal) {
290 + self.visit_literal(literal);
291 + }
292 +
293 + fn visit_literal(&mut self, _literal: &'ast Literal) {
294 + // nothing to do unless overridden
295 + }
296 +}
compiler/forget/crates/fixtures/tests/fixtures_test.rs
+5
@@ -35,6 +35,11 @@ fn fixtures() {
35 }
36 match build(&environment, fun.function) {
37 Ok(mut fun) => {
38 + // println!("{fun:#?}");
39 + // let mut out = String::new();
40 + // fun.print(&fun.body, &mut out).unwrap();
41 + // println!("{out}");
42 +
43 enter_ssa(&environment, &mut fun).unwrap();
44 eliminate_redundant_phis(&environment, &mut fun);
45 constant_propagation(&environment, &mut fun);
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@function-expressions.js.snap
+2 -2
@@ -27,7 +27,7 @@ entry bb0
27 bb0 (block)
28 [0] #0 = 2
29 [1] #1 = StoreLocal Const unknown y$7 = unknown #0
30 - [2] #2 = Function @deps[] @context[]:
30 + [2] #2 = Function @deps[] @context[unknown y$7]:
31 function foo(
32 unknown x$8,
33 )
@@ -50,7 +50,7 @@ bb0 (block)
50 bb2 (block)
51 predecessors: bb3
52 [12] #12 = LoadLocal unknown x$8
53 - [13] #13 = LoadLocal unknown y$0
53 + [13] #13 = 2
54 [14] #14 = Binary unknown #12 + unknown #13
55 [15] #15 = 8
56 [16] #16 = Binary unknown #14 + unknown #15
compiler/forget/crates/hir-ssa/src/eliminate_redundant_phis.rs
+1 -1
@@ -1,6 +1,6 @@
1 use std::collections::{HashMap, HashSet};
2
3 -use hir::{BlockId, Environment, Function, Identifier, IdentifierId, InstructionValue, HIR};
3 +use hir::{BlockId, Environment, Function, Identifier, IdentifierId, InstructionValue};
4 use utils::RetainMut;
5
6 /// Pass to eliminate redundant phi nodes:
compiler/forget/crates/hir-ssa/src/enter.rs
+6 -5
@@ -13,7 +13,7 @@ use thiserror::Error;
13 pub struct SSAError;
14
15 pub fn enter_ssa<'a>(env: &Environment<'a>, fun: &mut Function<'a>) -> Result<(), SSAError> {
16 - // assert_eq!(fun.context.is_empty())
16 + assert!(fun.context.is_empty());
17 enter_ssa_impl(env, fun, None)
18 }
19
@@ -63,10 +63,11 @@ fn visit_instructions<'a, 'e, 'f>(
63 let context_defs: IndexMap<IdentifierId, Identifier> = fun
64 .lowered_function
65 .context
66 - .iter()
67 - .map(|id| {
68 - let identifier = builder.get_id_at(block.id, &id.identifier);
69 - (id.identifier.id, identifier)
66 + .iter_mut()
67 + .map(|identifier| {
68 + let old_id = identifier.identifier.id;
69 + builder.visit_load(identifier);
70 + (old_id, identifier.identifier.clone())
71 })
72 .collect();
73 enter_ssa_impl(env, &mut fun.lowered_function, Some(context_defs))?;
compiler/forget/crates/hir/src/print.rs
+6 -6
@@ -160,12 +160,12 @@ impl<'a> Print<'a> for InstructionValue<'a> {
160 dep.print(hir, out)?;
161 }
162 write!(out, "] @context[")?;
163 - // for (ix, dep) in value.lowered_function.context.iter().enumerate() {
164 - // if ix != 0 {
165 - // write!(out, ", ")?;
166 - // }
167 - // dep.print(hir, out)?;
168 - // }
163 + for (ix, dep) in value.lowered_function.context.iter().enumerate() {
164 + if ix != 0 {
165 + write!(out, ", ")?;
166 + }
167 + dep.print(hir, out)?;
168 + }
169 writeln!(out, "]:")?;
170 let mut inner_output = String::new();
171 value