swc_ecma_transforms_classes/
super_field.rs

1use std::iter;
2
3use swc_common::{util::take::Take, Mark, Span, SyntaxContext, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::helper;
6use swc_ecma_utils::{is_rest_arguments, quote_ident, ExprFactory};
7use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
8
9use super::get_prototype_of;
10
11/// Process function body.
12///
13/// # In
14///
15/// ```js
16/// super.foo(a)
17/// ```
18///
19/// # Out
20///
21///
22/// _get(Child.prototype.__proto__ || Object.getPrototypeOf(Child.prototype),
23/// 'foo', this).call(this, a);
24pub struct SuperFieldAccessFolder<'a> {
25    pub class_name: &'a Ident,
26
27    /// Mark for the `_this`. Used only when folding constructor.
28    pub constructor_this_mark: Option<Mark>,
29    pub is_static: bool,
30
31    pub folding_constructor: bool,
32
33    /// True while folding **injected** `_define_property` call
34    pub in_injected_define_property_call: bool,
35
36    /// True while folding a function / class.
37    pub in_nested_scope: bool,
38
39    /// `Some(mark)` if `var this2 = this`is required.
40    pub this_alias_mark: Option<Mark>,
41
42    /// assumes super is never changed, payload is the name of super class
43    pub constant_super: bool,
44
45    pub super_class: &'a Option<Ident>,
46
47    pub in_pat: bool,
48}
49
50macro_rules! mark_nested {
51    ($name:ident, $T:tt) => {
52        fn $name(&mut self, n: &mut $T) {
53            // injected `_define_property` should be handled like method
54            if self.folding_constructor && !self.in_injected_define_property_call {
55                let old = self.in_nested_scope;
56                self.in_nested_scope = true;
57                n.visit_mut_children_with(self);
58                self.in_nested_scope = old;
59            } else {
60                n.visit_mut_children_with(self)
61            }
62        }
63    };
64}
65
66impl VisitMut for SuperFieldAccessFolder<'_> {
67    noop_visit_mut_type!();
68
69    // mark_nested!(fold_function, Function);
70    mark_nested!(visit_mut_class, Class);
71
72    visit_mut_only_key!();
73
74    fn visit_mut_expr(&mut self, n: &mut Expr) {
75        match n {
76            Expr::This(ThisExpr { span }) if self.in_nested_scope => {
77                *n = quote_ident!(
78                    SyntaxContext::empty().apply_mark(
79                        *self
80                            .this_alias_mark
81                            .get_or_insert_with(|| Mark::fresh(Mark::root()))
82                    ),
83                    *span,
84                    "_this"
85                )
86                .into();
87            }
88            // We pretend method folding mode for while folding injected `_define_property`
89            // calls.
90            Expr::Call(CallExpr {
91                callee: Callee::Expr(expr),
92                ..
93            }) if expr.is_ident_ref_to("_define_property") => {
94                let old = self.in_injected_define_property_call;
95                self.in_injected_define_property_call = true;
96                n.visit_mut_children_with(self);
97                self.in_injected_define_property_call = old;
98            }
99            Expr::SuperProp(..) => {
100                self.visit_mut_super_member_get(n);
101            }
102            Expr::Update(UpdateExpr { arg, .. }) if arg.is_super_prop() => {
103                if let Expr::SuperProp(SuperPropExpr {
104                    obj: Super {
105                        span: super_token, ..
106                    },
107                    prop,
108                    ..
109                }) = &**arg
110                {
111                    *arg = self.super_to_update_call(*super_token, prop.clone()).into();
112                }
113            }
114            Expr::Assign(AssignExpr {
115                ref left,
116                op: op!("="),
117                right,
118                ..
119            }) if is_assign_to_super_prop(left) => {
120                right.visit_mut_with(self);
121                self.visit_mut_super_member_set(n)
122            }
123            Expr::Assign(AssignExpr { left, right, .. }) if is_assign_to_super_prop(left) => {
124                right.visit_mut_with(self);
125                self.visit_mut_super_member_update(n);
126            }
127            Expr::Call(CallExpr {
128                callee: Callee::Expr(callee_expr),
129                args,
130                ..
131            }) if callee_expr.is_super_prop() => {
132                args.visit_mut_children_with(self);
133
134                self.visit_mut_super_member_call(n);
135            }
136            _ => {
137                n.visit_mut_children_with(self);
138            }
139        }
140    }
141
142    fn visit_mut_pat(&mut self, n: &mut Pat) {
143        let in_pat = self.in_pat;
144        self.in_pat = true;
145        n.visit_mut_children_with(self);
146        self.in_pat = in_pat;
147    }
148
149    fn visit_mut_function(&mut self, n: &mut Function) {
150        if self.folding_constructor {
151            return;
152        }
153
154        if self.folding_constructor && !self.in_injected_define_property_call {
155            let old = self.in_nested_scope;
156            self.in_nested_scope = true;
157            n.visit_mut_children_with(self);
158            self.in_nested_scope = old;
159        } else {
160            n.visit_mut_children_with(self);
161        }
162    }
163}
164
165impl SuperFieldAccessFolder<'_> {
166    /// # In
167    /// ```js
168    /// super.foo(a)
169    /// ```
170    /// # out
171    /// ```js
172    /// _get(_get_prototype_of(Clazz.prototype), 'foo', this).call(this, a)
173    /// ```
174    fn visit_mut_super_member_call(&mut self, n: &mut Expr) {
175        if let Expr::Call(CallExpr {
176            callee: Callee::Expr(callee_expr),
177            args,
178            ..
179        }) = n
180        {
181            if let Expr::SuperProp(SuperPropExpr {
182                obj: Super {
183                    span: super_token, ..
184                },
185                prop,
186                ..
187            }) = &**callee_expr
188            {
189                let this = match self.this_alias_mark.or(self.constructor_this_mark) {
190                    Some(mark) => {
191                        let ident =
192                            quote_ident!(SyntaxContext::empty().apply_mark(mark), "_this").as_arg();
193                        // in constant super, call will be the only place where a assert is needed
194                        if self.constant_super {
195                            CallExpr {
196                                span: DUMMY_SP,
197                                callee: helper!(assert_this_initialized),
198                                args: vec![ident],
199                                ..Default::default()
200                            }
201                            .as_arg()
202                        } else {
203                            ident
204                        }
205                    }
206                    _ => ThisExpr { span: DUMMY_SP }.as_arg(),
207                };
208
209                let callee = self.super_to_get_call(*super_token, prop.clone());
210                let mut args = args.clone();
211
212                if args.len() == 1 && is_rest_arguments(&args[0]) {
213                    *n = CallExpr {
214                        span: DUMMY_SP,
215                        callee: callee.make_member(quote_ident!("apply")).as_callee(),
216                        args: iter::once(this)
217                            .chain(iter::once({
218                                let mut arg = args.pop().unwrap();
219                                arg.spread = None;
220                                arg
221                            }))
222                            .collect(),
223                        ..Default::default()
224                    }
225                    .into();
226                    return;
227                }
228
229                *n = CallExpr {
230                    span: DUMMY_SP,
231                    callee: callee.make_member(quote_ident!("call")).as_callee(),
232                    args: iter::once(this).chain(args).collect(),
233                    ..Default::default()
234                }
235                .into();
236            }
237        }
238    }
239
240    /// # In
241    /// ```js
242    /// super.foo = bar
243    /// # out
244    /// ```js
245    /// _set(_get_prototype_of(Clazz.prototype), "foo", bar, this, true)
246    /// ```
247    fn visit_mut_super_member_set(&mut self, n: &mut Expr) {
248        if let Expr::Assign(AssignExpr {
249            left:
250                AssignTarget::Simple(SimpleAssignTarget::SuperProp(SuperPropExpr {
251                    obj: Super { span: super_token },
252                    prop,
253                    ..
254                })),
255            op: op @ op!("="),
256            right,
257            ..
258        }) = n
259        {
260            *n = self.super_to_set_call(*super_token, prop.take(), *op, right.take());
261        }
262    }
263
264    /// # In
265    /// ```js
266    /// super.foo
267    /// ```
268    /// # out
269    /// ```js
270    /// _get(_get_prototype_of(Clazz.prototype), 'foo', this)
271    /// ```
272    fn visit_mut_super_member_get(&mut self, n: &mut Expr) {
273        if let Expr::SuperProp(SuperPropExpr {
274            obj: Super { span: super_token },
275            prop,
276            ..
277        }) = n
278        {
279            let super_token = *super_token;
280            prop.visit_mut_children_with(self);
281
282            let prop = prop.take();
283            *n = if self.in_pat {
284                self.super_to_update_call(super_token, prop).into()
285            } else {
286                *self.super_to_get_call(super_token, prop)
287            };
288        }
289    }
290
291    fn visit_mut_super_member_update(&mut self, n: &mut Expr) {
292        if let Expr::Assign(AssignExpr { left, op, .. }) = n {
293            debug_assert_ne!(*op, op!("="));
294
295            if let AssignTarget::Simple(expr) = left {
296                if let SimpleAssignTarget::SuperProp(SuperPropExpr {
297                    obj: Super { span: super_token },
298                    prop,
299                    ..
300                }) = expr.take()
301                {
302                    *expr = self.super_to_update_call(super_token, prop).into();
303                }
304            }
305        }
306    }
307
308    fn super_to_get_call(&mut self, super_token: Span, prop: SuperProp) -> Box<Expr> {
309        if self.constant_super {
310            MemberExpr {
311                span: super_token,
312                obj: Box::new({
313                    let name = self.super_class.clone().unwrap_or_else(|| {
314                        quote_ident!(if self.is_static { "Function" } else { "Object" }).into()
315                    });
316                    // in static default super class is Function.prototype
317                    if self.is_static && self.super_class.is_some() {
318                        name.into()
319                    } else {
320                        name.make_member(quote_ident!("prototype")).into()
321                    }
322                }),
323                prop: match prop {
324                    SuperProp::Ident(i) => MemberProp::Ident(i),
325                    SuperProp::Computed(c) => MemberProp::Computed(c),
326                },
327            }
328            .into()
329        } else {
330            let proto_arg = self.proto_arg();
331
332            let prop_arg = prop_arg(prop).as_arg();
333
334            let this_arg = self.this_arg(super_token).as_arg();
335
336            CallExpr {
337                span: super_token,
338                callee: helper!(get),
339                args: vec![proto_arg.as_arg(), prop_arg, this_arg],
340                ..Default::default()
341            }
342            .into()
343        }
344    }
345
346    fn super_to_set_call(
347        &mut self,
348        super_token: Span,
349        prop: SuperProp,
350        op: AssignOp,
351        rhs: Box<Expr>,
352    ) -> Expr {
353        debug_assert_eq!(op, op!("="));
354
355        let this_expr = Box::new(match self.constructor_this_mark {
356            Some(mark) => quote_ident!(
357                SyntaxContext::empty().apply_mark(mark),
358                super_token,
359                "_this"
360            )
361            .into(),
362            None => ThisExpr { span: super_token }.into(),
363        });
364
365        if self.constant_super {
366            let left = MemberExpr {
367                span: super_token,
368                obj: this_expr,
369                prop: match prop {
370                    SuperProp::Ident(i) => MemberProp::Ident(i),
371                    SuperProp::Computed(c) => MemberProp::Computed(c),
372                },
373            };
374
375            AssignExpr {
376                span: super_token,
377                left: left.into(),
378                op,
379                right: rhs,
380            }
381            .into()
382        } else {
383            let proto_arg = self.proto_arg();
384
385            let prop_arg = prop_arg(prop).as_arg();
386
387            CallExpr {
388                span: super_token,
389                callee: helper!(set),
390                args: vec![
391                    proto_arg.as_arg(),
392                    prop_arg,
393                    rhs.as_arg(),
394                    this_expr.as_arg(),
395                    // strict
396                    true.as_arg(),
397                ],
398                ..Default::default()
399            }
400            .into()
401        }
402    }
403
404    fn super_to_update_call(&mut self, super_token: Span, prop: SuperProp) -> MemberExpr {
405        let proto_arg = self.proto_arg();
406
407        let prop_arg = prop_arg(prop).as_arg();
408
409        let this_arg = self.this_arg(super_token).as_arg();
410
411        let expr: Expr = CallExpr {
412            span: super_token,
413            callee: helper!(update),
414            args: vec![
415                proto_arg.as_arg(),
416                prop_arg,
417                this_arg,
418                // strict
419                true.as_arg(),
420            ],
421            ..Default::default()
422        }
423        .into();
424
425        expr.make_member(quote_ident!("_"))
426    }
427
428    fn proto_arg(&mut self) -> Box<Expr> {
429        let expr = if self.is_static {
430            // Foo
431            self.class_name.clone().into()
432        } else {
433            // Foo.prototype
434            self.class_name
435                .clone()
436                .make_member(quote_ident!("prototype"))
437                .into()
438        };
439
440        if self.constant_super {
441            return expr;
442        }
443
444        let mut proto_arg = get_prototype_of(expr);
445
446        if let Some(mark) = self.constructor_this_mark {
447            let this = quote_ident!(SyntaxContext::empty().apply_mark(mark), "_this");
448
449            proto_arg = SeqExpr {
450                span: DUMMY_SP,
451                exprs: vec![
452                    Expr::Call(CallExpr {
453                        span: DUMMY_SP,
454                        callee: helper!(assert_this_initialized),
455                        args: vec![this.as_arg()],
456                        ..Default::default()
457                    })
458                    .into(),
459                    proto_arg,
460                ],
461            }
462            .into()
463        }
464
465        proto_arg
466    }
467
468    fn this_arg(&self, super_token: Span) -> Expr {
469        match self.constructor_this_mark {
470            Some(mark) => quote_ident!(
471                SyntaxContext::empty().apply_mark(mark),
472                super_token,
473                "_this"
474            )
475            .into(),
476            None => ThisExpr { span: super_token }.into(),
477        }
478    }
479}
480
481fn is_assign_to_super_prop(left: &AssignTarget) -> bool {
482    match left {
483        AssignTarget::Simple(expr) => expr.is_super_prop(),
484        _ => false,
485    }
486}
487
488fn prop_arg(prop: SuperProp) -> Expr {
489    match prop {
490        SuperProp::Ident(IdentName {
491            sym: value, span, ..
492        }) => Lit::Str(Str {
493            span,
494            raw: None,
495            value,
496        })
497        .into(),
498        SuperProp::Computed(c) => *c.expr,
499    }
500}