1- use rustc_hash:: FxHashSet ;
21use swc_common:: DUMMY_SP ;
32use swc_ecma_ast:: * ;
43
@@ -104,59 +103,48 @@ impl Optimizer<'_> {
104103 }
105104
106105 // Analyze each parameter
107- let mut params_to_inline: Vec < ( usize , Box < Expr > , bool ) > = Vec :: new ( ) ;
106+ let mut params_to_inline: Vec < ( usize , Box < Expr > ) > = Vec :: new ( ) ;
108107
109108 for ( param_idx, param) in f. params . iter ( ) . enumerate ( ) {
110109 // Only handle simple identifier parameters
111- let param_id = match & param. pat {
110+ let _param_id = match & param. pat {
112111 Pat :: Ident ( ident) => ident. id . to_id ( ) ,
113112 _ => continue , // Skip destructuring, rest params, etc.
114113 } ;
115114
116- // Check if parameter is reassigned within the function
117- // If so, we'll use 'let' instead of 'const'
118- let is_reassigned = if let Some ( param_usage) = self . data . vars . get ( & param_id) {
119- param_usage. flags . contains ( VarUsageInfoFlags :: REASSIGNED )
120- } else {
121- false
122- } ;
123-
124115 // Collect argument values for this parameter across all call sites
125- let mut common_value: Option < Box < Expr > > = None ;
116+ // Use Option<Option<Box<Expr>>> to track:
117+ // - None = no common value yet established
118+ // - Some(None) = all call sites have implicit undefined
119+ // - Some(Some(expr)) = all call sites have the same explicit expression
120+ let mut common_value: Option < Option < Box < Expr > > > = None ;
126121 let mut inlinable = true ;
127122
128123 for call_site in call_sites {
129- let arg_value = if param_idx < call_site. len ( ) {
130- call_site[ param_idx] . clone ( )
124+ // Get argument at this position, or None if implicit undefined
125+ let arg_value: Option < Box < Expr > > = if param_idx < call_site. len ( ) {
126+ Some ( call_site[ param_idx] . clone ( ) )
131127 } else {
132- // Implicit undefined - use unresolved context
133- Some ( Box :: new ( Expr :: Ident ( Ident :: new (
134- "undefined" . into ( ) ,
135- DUMMY_SP ,
136- self . ctx . expr_ctx . unresolved_ctxt ,
137- ) ) ) )
128+ None // Implicit undefined
138129 } ;
139130
140- let arg_value = match arg_value {
141- Some ( v ) => v ,
142- None => {
131+ // Check if this is a safe, constant value to inline
132+ if let Some ( ref expr ) = arg_value {
133+ if ! self . is_safe_constant_for_param_inline ( expr ) {
143134 inlinable = false ;
144135 break ;
145136 }
146- } ;
147-
148- // Check if this is a safe, constant value to inline
149- if !self . is_safe_constant_for_param_inline ( & arg_value) {
150- inlinable = false ;
151- break ;
152137 }
138+ // Implicit undefined (None) is always safe to inline
153139
154140 match & common_value {
155141 None => {
142+ // First call site, establish the common value
156143 common_value = Some ( arg_value) ;
157144 }
158145 Some ( existing) => {
159- if !self . expr_eq ( existing, & arg_value) {
146+ // Check if this call site has the same value
147+ if !self . arg_eq ( existing, & arg_value) {
160148 inlinable = false ;
161149 break ;
162150 }
@@ -166,8 +154,17 @@ impl Optimizer<'_> {
166154
167155 // If all call sites pass the same constant value, mark for inlining
168156 if inlinable {
169- if let Some ( value) = common_value {
170- params_to_inline. push ( ( param_idx, value, is_reassigned) ) ;
157+ if let Some ( Some ( value) ) = common_value {
158+ // Explicit value passed at all call sites
159+ params_to_inline. push ( ( param_idx, value) ) ;
160+ } else if let Some ( None ) = common_value {
161+ // All call sites have implicit undefined
162+ let undefined_expr = Box :: new ( Expr :: Ident ( Ident :: new (
163+ "undefined" . into ( ) ,
164+ DUMMY_SP ,
165+ self . ctx . expr_ctx . unresolved_ctxt ,
166+ ) ) ) ;
167+ params_to_inline. push ( ( param_idx, undefined_expr) ) ;
171168 }
172169 }
173170 }
@@ -182,7 +179,7 @@ impl Optimizer<'_> {
182179 fn apply_param_inlining (
183180 & mut self ,
184181 f : & mut Function ,
185- params_to_inline : & [ ( usize , Box < Expr > , bool ) ] ,
182+ params_to_inline : & [ ( usize , Box < Expr > ) ] ,
186183 fn_id : & Id ,
187184 ) {
188185 // Sort in reverse order to remove from the end first
@@ -192,25 +189,20 @@ impl Optimizer<'_> {
192189 // Collect variable declarations to add at the beginning of function body
193190 let mut var_decls = Vec :: new ( ) ;
194191
195- // Track which parameter indices are being inlined
196- let mut inlined_indices = FxHashSet :: default ( ) ;
192+ // Track which parameter indices are being inlined (will be sorted)
193+ let mut inlined_indices = Vec :: new ( ) ;
197194
198- for ( param_idx, value, is_reassigned ) in & sorted_params {
199- inlined_indices. insert ( * param_idx) ;
195+ for ( param_idx, value) in & sorted_params {
196+ inlined_indices. push ( * param_idx) ;
200197
201198 if let Some ( param) = f. params . get ( * param_idx) {
202199 if let Pat :: Ident ( ident) = & param. pat {
203- // Create variable declaration: const/let paramName = value;
204- // Use 'let' if the parameter is reassigned, 'const' otherwise
205- let var_kind = if * is_reassigned {
206- VarDeclKind :: Let
207- } else {
208- VarDeclKind :: Const
209- } ;
200+ // Create variable declaration: let paramName = value;
201+ // Always use 'let' to preserve parameter semantics (reassignable)
210202 var_decls. push ( Stmt :: Decl ( Decl :: Var ( Box :: new ( VarDecl {
211203 span : DUMMY_SP ,
212204 ctxt : Default :: default ( ) ,
213- kind : var_kind ,
205+ kind : VarDeclKind :: Let ,
214206 declare : false ,
215207 decls : vec ! [ VarDeclarator {
216208 span: DUMMY_SP ,
@@ -224,7 +216,7 @@ impl Optimizer<'_> {
224216 }
225217
226218 // Remove parameters (in reverse order)
227- for ( param_idx, _, _ ) in & sorted_params {
219+ for ( param_idx, _) in & sorted_params {
228220 f. params . remove ( * param_idx) ;
229221 }
230222
@@ -236,7 +228,8 @@ impl Optimizer<'_> {
236228 }
237229
238230 // Store the inlined parameter indices for later use when visiting call
239- // sites
231+ // sites. Reverse sort to get descending order for efficient removal.
232+ inlined_indices. sort_by ( |a, b| b. cmp ( a) ) ;
240233 self . inlined_params . insert ( fn_id. clone ( ) , inlined_indices) ;
241234
242235 self . changed = true ;
@@ -283,14 +276,31 @@ impl Optimizer<'_> {
283276 }
284277 }
285278
279+ /// Compare two optional argument values for equality.
280+ /// None represents implicit undefined.
281+ fn arg_eq ( & self , a : & Option < Box < Expr > > , b : & Option < Box < Expr > > ) -> bool {
282+ match ( a, b) {
283+ ( None , None ) => true , // Both implicit undefined
284+ ( Some ( a_expr) , Some ( b_expr) ) => self . expr_eq ( a_expr, b_expr) ,
285+ _ => false , // One is implicit undefined, the other is not
286+ }
287+ }
288+
286289 /// Compare two expressions for equality (structural equality for simple
287290 /// cases).
291+ ///
292+ /// We cannot use the derived `Eq` trait because:
293+ /// 1. NaN handling: We treat NaN as equal to NaN for parameter inlining
294+ /// purposes, whereas standard floating point comparison has NaN != NaN.
295+ /// 2. Context-aware identifier comparison: We only consider unresolved
296+ /// "undefined" identifiers as safe to inline, checking the syntax
297+ /// context.
288298 fn expr_eq ( & self , a : & Expr , b : & Expr ) -> bool {
289299 match ( a, b) {
290300 ( Expr :: Lit ( Lit :: Null ( _) ) , Expr :: Lit ( Lit :: Null ( _) ) ) => true ,
291301 ( Expr :: Lit ( Lit :: Bool ( a) ) , Expr :: Lit ( Lit :: Bool ( b) ) ) => a. value == b. value ,
292302 ( Expr :: Lit ( Lit :: Num ( a) ) , Expr :: Lit ( Lit :: Num ( b) ) ) => {
293- // Handle NaN specially
303+ // Handle NaN specially: treat NaN as equal to NaN for parameter inlining
294304 if a. value . is_nan ( ) && b. value . is_nan ( ) {
295305 true
296306 } else {
@@ -299,15 +309,9 @@ impl Optimizer<'_> {
299309 }
300310 ( Expr :: Lit ( Lit :: Str ( a) ) , Expr :: Lit ( Lit :: Str ( b) ) ) => a. value == b. value ,
301311 ( Expr :: Lit ( Lit :: BigInt ( a) ) , Expr :: Lit ( Lit :: BigInt ( b) ) ) => a. value == b. value ,
302- // Compare identifiers:
303- // Only allow unresolved "undefined" identifiers
304- ( Expr :: Ident ( a) , Expr :: Ident ( b) ) => {
305- let a_is_unresolved = a. ctxt == self . ctx . expr_ctx . unresolved_ctxt ;
306- let b_is_unresolved = b. ctxt == self . ctx . expr_ctx . unresolved_ctxt ;
307-
308- // Both must be unresolved "undefined"
309- a_is_unresolved && b_is_unresolved && a. sym == "undefined" && b. sym == "undefined"
310- }
312+ // Compare identifiers: we can compare naively since is_safe_constant_for_param_inline
313+ // already validated that only unresolved "undefined" identifiers reach here
314+ ( Expr :: Ident ( a) , Expr :: Ident ( b) ) => a. sym == b. sym && a. ctxt == b. ctxt ,
311315 (
312316 Expr :: Unary ( UnaryExpr {
313317 op : op_a,
@@ -348,11 +352,8 @@ impl Optimizer<'_> {
348352 } ;
349353
350354 // Remove arguments at the inlined parameter indices
351- // We need to iterate in reverse order to avoid index shifting issues
352- let mut indices_to_remove: Vec < usize > = inlined_indices. iter ( ) . copied ( ) . collect ( ) ;
353- indices_to_remove. sort_by ( |a, b| b. cmp ( a) ) ; // Sort in descending order
354-
355- for idx in indices_to_remove {
355+ // The indices are already sorted in descending order from apply_param_inlining
356+ for & idx in inlined_indices {
356357 if idx < call. args . len ( ) {
357358 call. args . remove ( idx) ;
358359 self . changed = true ;
0 commit comments