@@ -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
5658impl < ' 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+ }
0 commit comments