Skip to content

Commit 80d0b3e

Browse files
committed
perf(transformer/class-properties): fast path for instance prop initializer scope re-parenting (#7901)
Add a fast path for inserting instance property initializers into constructor, when no existing constructor or constructor has no bindings. This should be reasonably common. The `Scope flags mismatch` errors are due to #7900.
1 parent feac02e commit 80d0b3e

4 files changed

Lines changed: 180 additions & 22 deletions

File tree

crates/oxc_transformer/src/es2022/class_properties/instance_prop_init.rs

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
2626
value: &mut Expression<'a>,
2727
ctx: &mut TraverseCtx<'a>,
2828
) {
29-
let mut updater = InstanceInitializerVisitor::new(self, ctx);
30-
updater.visit_expression(value);
29+
if let Some(constructor_scope_id) = self.instance_inits_constructor_scope_id {
30+
// Re-parent first-level scopes, and check for symbol clashes
31+
let mut updater = InstanceInitializerVisitor::new(constructor_scope_id, self, ctx);
32+
updater.visit_expression(value);
33+
} else {
34+
// No symbol clashes possible. Just re-parent first-level scopes (faster).
35+
let mut updater = FastInstanceInitializerVisitor::new(self, ctx);
36+
updater.visit_expression(value);
37+
};
3138
}
3239
}
3340

34-
// TODO: If no `constructor_scope_id`, then don't need to traverse beyond first-level scope,
35-
// as all we need to do is update scopes. Add a faster visitor for this more limited traversal.
36-
3741
/// Visitor to change parent scope of first-level scopes in instance property initializer,
3842
/// and find any `IdentifierReference`s which would be shadowed by bindings in constructor,
3943
/// once initializer moves into constructor body.
@@ -43,10 +47,8 @@ struct InstanceInitializerVisitor<'a, 'v> {
4347
scope_ids_stack: Stack<ScopeId>,
4448
/// New parent scope for first-level scopes in initializer
4549
parent_scope_id: ScopeId,
46-
/// Constructor scope, if need to check for clashing bindings with constructor.
47-
/// `None` if constructor is newly created, or inits are being inserted in `_super` function
48-
/// outside class, because in those cases there are no bindings which can clash.
49-
constructor_scope_id: Option<ScopeId>,
50+
/// Constructor's scope, for checking symbol clashes against
51+
constructor_scope_id: ScopeId,
5052
/// Clashing symbols
5153
clashing_symbols: &'v mut FxHashMap<SymbolId, Atom<'a>>,
5254
/// `TraverseCtx` object.
@@ -55,6 +57,7 @@ struct InstanceInitializerVisitor<'a, 'v> {
5557

5658
impl<'a, 'v> InstanceInitializerVisitor<'a, 'v> {
5759
fn new(
60+
constructor_scope_id: ScopeId,
5861
class_properties: &'v mut ClassProperties<'a, '_>,
5962
ctx: &'v mut TraverseCtx<'a>,
6063
) -> Self {
@@ -63,7 +66,7 @@ impl<'a, 'v> InstanceInitializerVisitor<'a, 'v> {
6366
// to avoid an allocation in most cases
6467
scope_ids_stack: Stack::new(),
6568
parent_scope_id: class_properties.instance_inits_scope_id,
66-
constructor_scope_id: class_properties.instance_inits_constructor_scope_id,
69+
constructor_scope_id,
6770
clashing_symbols: &mut class_properties.clashing_constructor_symbols,
6871
ctx,
6972
}
@@ -90,12 +93,10 @@ impl<'a, 'v> Visit<'a> for InstanceInitializerVisitor<'a, 'v> {
9093
self.scope_ids_stack.pop();
9194
}
9295

93-
// `#[inline]` because this is a hot path
96+
// `#[inline]` because this function just delegates to `check_for_symbol_clash`
9497
#[inline]
9598
fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) {
96-
let Some(constructor_scope_id) = self.constructor_scope_id else { return };
97-
98-
self.check_for_symbol_clash(ident, constructor_scope_id);
99+
self.check_for_symbol_clash(ident);
99100
}
100101
}
101102

@@ -106,17 +107,13 @@ impl<'a, 'v> InstanceInitializerVisitor<'a, 'v> {
106107
}
107108

108109
/// Check if symbol referenced by `ident` is shadowed by a binding in constructor's scope.
109-
fn check_for_symbol_clash(
110-
&mut self,
111-
ident: &IdentifierReference<'a>,
112-
constructor_scope_id: ScopeId,
113-
) {
110+
fn check_for_symbol_clash(&mut self, ident: &IdentifierReference<'a>) {
114111
// TODO: It would be ideal if could get reference `&Bindings` for constructor
115112
// in `InstanceInitializerVisitor::new` rather than indexing into `ScopeTree::bindings`
116113
// with same `ScopeId` every time here, but `ScopeTree` doesn't allow that, and we also
117114
// take a `&mut ScopeTree` in `reparent_scope`, so borrow-checker doesn't allow that.
118115
let Some(constructor_symbol_id) =
119-
self.ctx.scopes().get_binding(constructor_scope_id, &ident.name)
116+
self.ctx.scopes().get_binding(self.constructor_scope_id, &ident.name)
120117
else {
121118
return;
122119
};
@@ -138,3 +135,85 @@ impl<'a, 'v> InstanceInitializerVisitor<'a, 'v> {
138135
self.clashing_symbols.entry(constructor_symbol_id).or_insert(ident.name.clone());
139136
}
140137
}
138+
139+
/// Visitor to change parent scope of first-level scopes in instance property initializer.
140+
///
141+
/// Unlike `InstanceInitializerVisitor`, does not check for symbol clashes.
142+
///
143+
/// Therefore only needs to walk until find a node which has a scope. No point continuing to traverse
144+
/// inside that scope, as by definition and nested scopes can't be first level.
145+
///
146+
/// The visitors here are for the only types which can be the first scope reached when starting
147+
/// traversal from an `Expression`.
148+
struct FastInstanceInitializerVisitor<'a, 'v> {
149+
/// Parent scope
150+
parent_scope_id: ScopeId,
151+
/// `TraverseCtx` object.
152+
ctx: &'v mut TraverseCtx<'a>,
153+
}
154+
155+
impl<'a, 'v> FastInstanceInitializerVisitor<'a, 'v> {
156+
fn new(
157+
class_properties: &'v mut ClassProperties<'a, '_>,
158+
ctx: &'v mut TraverseCtx<'a>,
159+
) -> Self {
160+
Self { parent_scope_id: class_properties.instance_inits_scope_id, ctx }
161+
}
162+
}
163+
164+
impl<'a, 'v> Visit<'a> for FastInstanceInitializerVisitor<'a, 'v> {
165+
#[inline]
166+
fn visit_function(&mut self, func: &Function<'a>, _flags: ScopeFlags) {
167+
self.reparent_scope(&func.scope_id);
168+
}
169+
170+
#[inline]
171+
fn visit_arrow_function_expression(&mut self, func: &ArrowFunctionExpression<'a>) {
172+
self.reparent_scope(&func.scope_id);
173+
}
174+
175+
#[inline]
176+
fn visit_class(&mut self, class: &Class<'a>) {
177+
// `decorators` is outside `Class`'s scope and can contain scopes itself
178+
self.visit_decorators(&class.decorators);
179+
180+
self.reparent_scope(&class.scope_id);
181+
}
182+
183+
#[inline]
184+
fn visit_ts_conditional_type(&mut self, conditional: &TSConditionalType<'a>) {
185+
// `check_type` is outside `TSConditionalType`'s scope and can contain scopes itself
186+
self.visit_ts_type(&conditional.check_type);
187+
188+
self.reparent_scope(&conditional.scope_id);
189+
190+
// `false_type` is outside `TSConditionalType`'s scope and can contain scopes itself
191+
self.visit_ts_type(&conditional.false_type);
192+
}
193+
194+
#[inline]
195+
fn visit_ts_method_signature(&mut self, signature: &TSMethodSignature<'a>) {
196+
self.reparent_scope(&signature.scope_id);
197+
}
198+
199+
#[inline]
200+
fn visit_ts_construct_signature_declaration(
201+
&mut self,
202+
signature: &TSConstructSignatureDeclaration<'a>,
203+
) {
204+
self.reparent_scope(&signature.scope_id);
205+
}
206+
207+
#[inline]
208+
fn visit_ts_mapped_type(&mut self, mapped: &TSMappedType<'a>) {
209+
self.reparent_scope(&mapped.scope_id);
210+
}
211+
}
212+
213+
impl<'a, 'v> FastInstanceInitializerVisitor<'a, 'v> {
214+
/// Update parent of scope.
215+
fn reparent_scope(&mut self, scope_id: &Cell<Option<ScopeId>>) {
216+
let scope_id = scope_id.get().unwrap();
217+
self.ctx.scopes_mut().change_parent_id(scope_id, Some(self.parent_scope_id));
218+
}
219+
}

tasks/transform_conformance/snapshots/oxc.snap.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
commit: 54a8389f
22

3-
Passed: 110/124
3+
Passed: 110/125
44

55
# All Passed:
66
* babel-plugin-transform-class-static-block
@@ -16,7 +16,24 @@ Passed: 110/124
1616
* regexp
1717

1818

19-
# babel-plugin-transform-class-properties (11/13)
19+
# babel-plugin-transform-class-properties (11/14)
20+
* instance-prop-initializer-no-existing-constructor/input.js
21+
Scope flags mismatch:
22+
after transform: ScopeId(12): ScopeFlags(StrictMode)
23+
rebuilt : ScopeId(13): ScopeFlags(StrictMode | Constructor)
24+
Scope flags mismatch:
25+
after transform: ScopeId(13): ScopeFlags(StrictMode)
26+
rebuilt : ScopeId(14): ScopeFlags(StrictMode | Constructor)
27+
Scope flags mismatch:
28+
after transform: ScopeId(14): ScopeFlags(StrictMode)
29+
rebuilt : ScopeId(15): ScopeFlags(StrictMode | Constructor)
30+
Scope flags mismatch:
31+
after transform: ScopeId(15): ScopeFlags(StrictMode)
32+
rebuilt : ScopeId(16): ScopeFlags(StrictMode | Constructor)
33+
Scope flags mismatch:
34+
after transform: ScopeId(20): ScopeFlags(StrictMode)
35+
rebuilt : ScopeId(21): ScopeFlags(StrictMode | Constructor)
36+
2037
* typescript/optional-call/input.ts
2138
Symbol reference IDs mismatch for "X":
2239
after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(11), ReferenceId(16)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class C {
2+
function = function() {};
3+
functions = [
4+
function() {
5+
function foo() {}
6+
},
7+
function() {
8+
function foo() {}
9+
}
10+
];
11+
arrow = () => {};
12+
arrows = [() => () => {}, () => () => {}];
13+
klass = class {};
14+
classExtends = class extends class {} {};
15+
classes = [
16+
class {
17+
method() {
18+
class D {}
19+
}
20+
method2() {
21+
class E {}
22+
}
23+
},
24+
class {
25+
method() {
26+
class D {}
27+
}
28+
method2() {
29+
class E {}
30+
}
31+
}
32+
];
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class C {
2+
constructor() {
3+
babelHelpers.defineProperty(this, "function", function() {});
4+
babelHelpers.defineProperty(this, "functions", [function() {
5+
function foo() {}
6+
}, function() {
7+
function foo() {}
8+
}]);
9+
babelHelpers.defineProperty(this, "arrow", () => {});
10+
babelHelpers.defineProperty(this, "arrows", [() => () => {}, () => () => {}]);
11+
babelHelpers.defineProperty(this, "klass", class {});
12+
babelHelpers.defineProperty(this, "classExtends", class extends class {} {});
13+
babelHelpers.defineProperty(this, "classes", [class {
14+
method() {
15+
class D {}
16+
}
17+
method2() {
18+
class E {}
19+
}
20+
}, class {
21+
method() {
22+
class D {}
23+
}
24+
method2() {
25+
class E {}
26+
}
27+
}]);
28+
}
29+
}

0 commit comments

Comments
 (0)