swc_ecma_transforms_proposal/
explicit_resource_management.rs

1use swc_common::{util::take::Take, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_transforms_base::helper;
4use swc_ecma_utils::{
5    private_ident, quote_ident, stack_size::maybe_grow_default, ExprFactory, ModuleItemLike,
6    StmtLike,
7};
8use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
9
10pub fn explicit_resource_management() -> impl Pass {
11    visit_mut_pass(ExplicitResourceManagement::default())
12}
13
14#[derive(Default)]
15struct ExplicitResourceManagement {
16    state: Option<State>,
17
18    is_not_top_level: bool,
19}
20
21struct State {
22    env: Ident,
23    has_await: bool,
24
25    vars: Vec<(Pat, Box<Expr>)>,
26}
27
28impl Default for State {
29    fn default() -> Self {
30        Self {
31            env: private_ident!("env"),
32            has_await: false,
33            vars: Vec::new(),
34        }
35    }
36}
37
38impl ExplicitResourceManagement {
39    fn visit_mut_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
40    where
41        T: StmtLike + ModuleItemLike,
42        Vec<T>: VisitMutWith<Self>,
43    {
44        let old_state = self.state.take();
45
46        stmts.visit_mut_children_with(self);
47
48        if let Some(state) = self.state.take() {
49            self.wrap_with_try(state, stmts);
50        }
51
52        self.state = old_state;
53    }
54
55    fn wrap_with_try<T>(&mut self, state: State, stmts: &mut Vec<T>)
56    where
57        T: StmtLike + ModuleItemLike,
58    {
59        let mut new = Vec::new();
60        let mut extras = Vec::new();
61
62        let env = state.env;
63        let catch_e = private_ident!("e");
64
65        let is_async = state.has_await;
66
67        // const env_1 = { stack: [], error: void 0, hasError: false };
68        new.push(T::from(Stmt::Decl(Decl::Var(Box::new(VarDecl {
69            span: DUMMY_SP,
70            kind: VarDeclKind::Const,
71            declare: false,
72            decls: vec![VarDeclarator {
73                span: DUMMY_SP,
74                name: Pat::Ident(env.clone().into()),
75                init: Some(Box::new(Expr::Object(ObjectLit {
76                    span: DUMMY_SP,
77                    props: vec![
78                        PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
79                            key: PropName::Ident(quote_ident!("stack")),
80                            value: Box::new(Expr::Array(ArrayLit {
81                                span: DUMMY_SP,
82                                elems: vec![],
83                            })),
84                        }))),
85                        PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
86                            key: PropName::Ident(quote_ident!("error")),
87                            value: Expr::undefined(DUMMY_SP),
88                        }))),
89                        PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
90                            key: PropName::Ident(quote_ident!("hasError")),
91                            value: Box::new(Expr::Lit(Lit::Bool(Bool {
92                                span: DUMMY_SP,
93                                value: false,
94                            }))),
95                        }))),
96                    ],
97                }))),
98                definite: false,
99            }],
100            ..Default::default()
101        })))));
102
103        let mut try_block = BlockStmt {
104            stmts: vec![],
105            ..Default::default()
106        };
107
108        for (name, disposable) in state.vars {
109            let init_var_decl = Stmt::Decl(Decl::Var(Box::new(VarDecl {
110                span: DUMMY_SP,
111                kind: VarDeclKind::Const,
112                declare: false,
113                decls: vec![VarDeclarator {
114                    span: DUMMY_SP,
115                    name,
116                    init: Some(
117                        CallExpr {
118                            callee: helper!(ts, ts_add_disposable_resource),
119                            args: vec![
120                                env.clone().as_arg(),
121                                disposable.as_arg(),
122                                is_async.as_arg(),
123                            ],
124                            ..Default::default()
125                        }
126                        .into(),
127                    ),
128                    definite: false,
129                }],
130                ..Default::default()
131            })));
132
133            try_block.stmts.push(init_var_decl);
134        }
135
136        for stmt in stmts.take() {
137            match stmt.try_into_stmt() {
138                Ok(mut stmt) => match stmt {
139                    // top level function declarations should preserve original level
140                    Stmt::Decl(Decl::Fn(..)) if !self.is_not_top_level => {
141                        extras.push(stmt.into());
142                    }
143                    Stmt::Decl(Decl::Class(ClassDecl { ident, class, .. }))
144                        if !self.is_not_top_level =>
145                    {
146                        // var C = class C { ... };
147                        try_block.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
148                            decls: vec![VarDeclarator {
149                                span: DUMMY_SP,
150                                name: Pat::Ident(ident.clone().into()),
151                                init: Some(
152                                    ClassExpr {
153                                        ident: Some(ident),
154                                        class,
155                                    }
156                                    .into(),
157                                ),
158                                definite: false,
159                            }],
160                            ..Default::default()
161                        }))));
162                    }
163                    // top level variable declarations should hoist from inner scope
164                    Stmt::Decl(Decl::Var(ref mut var)) if !self.is_not_top_level => {
165                        var.kind = VarDeclKind::Var;
166                        try_block.stmts.push(stmt);
167                    }
168                    _ => {
169                        try_block.stmts.push(stmt);
170                    }
171                },
172                Err(t) => match t.try_into_module_decl() {
173                    Ok(ModuleDecl::ExportDecl(ExportDecl {
174                        decl: Decl::Class(ClassDecl { ident, class, .. }),
175                        span,
176                    })) => {
177                        // export { C };
178                        extras.push(
179                            T::try_from_module_decl(ModuleDecl::ExportNamed(NamedExport {
180                                specifiers: vec![ExportNamedSpecifier {
181                                    span: DUMMY_SP,
182                                    orig: ident.clone().into(),
183                                    exported: None,
184                                    is_type_only: false,
185                                }
186                                .into()],
187                                ..NamedExport::dummy()
188                            }))
189                            .unwrap(),
190                        );
191
192                        // var C = class C { ... };
193                        try_block.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
194                            decls: vec![VarDeclarator {
195                                span: DUMMY_SP,
196                                name: Pat::Ident(ident.clone().into()),
197                                init: Some(
198                                    ClassExpr {
199                                        ident: Some(ident),
200                                        class,
201                                    }
202                                    .into(),
203                                ),
204                                definite: false,
205                            }],
206                            span,
207                            ..Default::default()
208                        }))));
209                    }
210                    Ok(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
211                        decl: DefaultDecl::Class(class_expr),
212                        span,
213                        ..
214                    })) => {
215                        let ident = class_expr
216                            .ident
217                            .clone()
218                            .unwrap_or_else(|| private_ident!("_default"));
219
220                        // export { C as default };
221                        extras.push(
222                            T::try_from_module_decl(ModuleDecl::ExportNamed(NamedExport {
223                                specifiers: vec![ExportNamedSpecifier {
224                                    span: DUMMY_SP,
225                                    orig: ident.clone().into(),
226                                    exported: Some(quote_ident!("default").into()),
227                                    is_type_only: false,
228                                }
229                                .into()],
230                                ..NamedExport::dummy()
231                            }))
232                            .unwrap(),
233                        );
234
235                        // var C = class { ... };
236
237                        try_block.stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl {
238                            decls: vec![VarDeclarator {
239                                span: DUMMY_SP,
240                                name: Pat::Ident(ident.into()),
241                                init: Some(class_expr.into()),
242                                definite: false,
243                            }],
244                            span,
245                            ..Default::default()
246                        }))));
247                    }
248                    Ok(module_decl) => {
249                        extras.push(T::try_from_module_decl(module_decl).unwrap());
250                    }
251                    Err(t) => extras.push(t),
252                },
253            }
254        }
255
256        let catch_clause = CatchClause {
257            span: DUMMY_SP,
258            param: Some(Pat::Ident(catch_e.clone().into())),
259            body: BlockStmt {
260                span: DUMMY_SP,
261                stmts: vec![
262                    // env.e = e;
263                    AssignExpr {
264                        span: DUMMY_SP,
265                        left: MemberExpr {
266                            span: DUMMY_SP,
267                            obj: Box::new(env.clone().into()),
268                            prop: MemberProp::Ident(quote_ident!("error")),
269                        }
270                        .into(),
271                        op: op!("="),
272                        right: Box::new(catch_e.clone().into()),
273                    }
274                    .into_stmt(),
275                    // env.hasError = true;
276                    AssignExpr {
277                        span: DUMMY_SP,
278                        left: MemberExpr {
279                            span: DUMMY_SP,
280                            obj: Box::new(env.clone().into()),
281                            prop: MemberProp::Ident(quote_ident!("hasError")),
282                        }
283                        .into(),
284                        op: op!("="),
285                        right: Box::new(Expr::Lit(Lit::Bool(Bool {
286                            span: DUMMY_SP,
287                            value: true,
288                        }))),
289                    }
290                    .into_stmt(),
291                ],
292                ..Default::default()
293            },
294        };
295
296        let finally_block = if is_async {
297            // Code:
298            // const result_1 = _ts_dispose_resources(env_1);
299            // if (result_1)
300            //      await result_1;
301
302            let result = private_ident!("result");
303
304            let var_decl = Stmt::Decl(Decl::Var(Box::new(VarDecl {
305                kind: VarDeclKind::Const,
306                decls: vec![VarDeclarator {
307                    span: DUMMY_SP,
308                    name: Pat::Ident(result.clone().into()),
309                    init: Some(
310                        CallExpr {
311                            callee: helper!(ts, ts_dispose_resources),
312                            args: vec![env.clone().as_arg()],
313                            ..Default::default()
314                        }
315                        .into(),
316                    ),
317                    definite: false,
318                }],
319                ..Default::default()
320            })));
321            let if_stmt = Stmt::If(IfStmt {
322                span: DUMMY_SP,
323                test: result.clone().into(),
324                // Code:
325                //      await result_1;
326                cons: Stmt::Expr(ExprStmt {
327                    expr: Box::new(Expr::Await(AwaitExpr {
328                        arg: result.clone().into(),
329                        ..Default::default()
330                    })),
331                    ..Default::default()
332                })
333                .into(),
334                ..Default::default()
335            });
336
337            vec![var_decl, if_stmt]
338        } else {
339            // Code:
340            // _ts_dispose_resources(env_1);
341            vec![CallExpr {
342                callee: helper!(ts, ts_dispose_resources),
343                args: vec![env.clone().as_arg()],
344                ..Default::default()
345            }
346            .into_stmt()]
347        };
348
349        let try_stmt = TryStmt {
350            span: DUMMY_SP,
351            block: try_block,
352            handler: Some(catch_clause),
353            finalizer: Some(BlockStmt {
354                stmts: finally_block,
355                ..Default::default()
356            }),
357        };
358
359        new.push(T::from(try_stmt.into()));
360        new.extend(extras);
361
362        *stmts = new;
363    }
364}
365
366impl VisitMut for ExplicitResourceManagement {
367    noop_visit_mut_type!();
368
369    fn visit_mut_expr(&mut self, n: &mut Expr) {
370        maybe_grow_default(|| n.visit_mut_children_with(self));
371    }
372
373    fn visit_mut_for_of_stmt(&mut self, n: &mut ForOfStmt) {
374        n.visit_mut_children_with(self);
375
376        if let ForHead::UsingDecl(using) = &mut n.left {
377            let mut state = State::default();
378            state.has_await |= using.is_await;
379
380            let mut inner_var_decl = VarDecl {
381                kind: VarDeclKind::Const,
382                ..Default::default()
383            };
384
385            for decl in &mut using.decls {
386                let new_var = private_ident!("_");
387
388                inner_var_decl.decls.push(VarDeclarator {
389                    span: DUMMY_SP,
390                    name: decl.name.take(),
391                    init: Some(
392                        CallExpr {
393                            callee: helper!(ts, ts_add_disposable_resource),
394                            args: vec![
395                                state.env.clone().as_arg(),
396                                new_var.clone().as_arg(),
397                                using.is_await.as_arg(),
398                            ],
399                            ..Default::default()
400                        }
401                        .into(),
402                    ),
403                    definite: false,
404                });
405
406                decl.name = Pat::Ident(new_var.clone().into());
407            }
408
409            let var_decl = Box::new(VarDecl {
410                span: DUMMY_SP,
411                kind: VarDeclKind::Const,
412                declare: false,
413                decls: using.decls.take(),
414                ..Default::default()
415            });
416
417            let mut body = vec![
418                Stmt::Decl(Decl::Var(Box::new(inner_var_decl))),
419                *n.body.take(),
420            ];
421            self.wrap_with_try(state, &mut body);
422
423            n.left = ForHead::VarDecl(var_decl);
424            n.body = Box::new(
425                BlockStmt {
426                    span: DUMMY_SP,
427                    stmts: body,
428                    ..Default::default()
429                }
430                .into(),
431            )
432        }
433    }
434
435    fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
436        self.visit_mut_stmt_likes(stmts)
437    }
438
439    fn visit_mut_stmt(&mut self, s: &mut Stmt) {
440        maybe_grow_default(|| s.visit_mut_children_with(self));
441
442        if let Stmt::Decl(Decl::Using(using)) = s {
443            let state = self.state.get_or_insert_with(Default::default);
444
445            let decl = handle_using_decl(using, state);
446            if decl.decls.is_empty() {
447                s.take();
448                return;
449            }
450
451            *s = Stmt::Decl(Decl::Var(decl));
452        }
453    }
454
455    fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
456        let old_is_not_top_level = self.is_not_top_level;
457        self.is_not_top_level = true;
458        self.visit_mut_stmt_likes(stmts);
459        self.is_not_top_level = old_is_not_top_level;
460    }
461}
462
463fn handle_using_decl(using: &mut UsingDecl, state: &mut State) -> Box<VarDecl> {
464    state.has_await |= using.is_await;
465
466    for decl in &mut using.decls {
467        decl.init = Some(
468            CallExpr {
469                callee: helper!(ts, ts_add_disposable_resource),
470                args: vec![
471                    state.env.clone().as_arg(),
472                    decl.init.take().unwrap().as_arg(),
473                    using.is_await.as_arg(),
474                ],
475                ..Default::default()
476            }
477            .into(),
478        );
479    }
480
481    Box::new(VarDecl {
482        span: DUMMY_SP,
483        kind: VarDeclKind::Const,
484        declare: false,
485        decls: using.decls.take(),
486        ..Default::default()
487    })
488}