@@ -394,6 +394,13 @@ impl<'a> PeepholeOptimizations {
394394 ctx. state . changed = true ;
395395 }
396396 }
397+ if Self :: substitute_single_use_symbol_within_declaration (
398+ var_decl. kind ,
399+ & mut var_decl. declarations ,
400+ ctx,
401+ ) {
402+ ctx. state . changed = true ;
403+ }
397404
398405 // If `join_vars` is off, but there are unused declarators ... just join them to make our code simpler.
399406 if !ctx. options ( ) . join_vars
@@ -850,6 +857,13 @@ impl<'a> PeepholeOptimizations {
850857 ctx. state . changed = true ;
851858 }
852859 }
860+ if Self :: substitute_single_use_symbol_within_declaration (
861+ var_decl. kind ,
862+ & mut var_decl. declarations ,
863+ ctx,
864+ ) {
865+ ctx. state . changed = true ;
866+ }
853867 }
854868 match_expression ! ( ForStatementInit ) => {
855869 let init = init. to_expression_mut ( ) ;
@@ -1123,67 +1137,109 @@ impl<'a> PeepholeOptimizations {
11231137 if prev_var_decl. kind . is_using ( ) {
11241138 break ;
11251139 }
1140+ let old_len = prev_var_decl. declarations . len ( ) ;
1141+ let new_len = Self :: substitute_single_use_symbol_in_expression_from_declarators (
1142+ expr_in_stmt,
1143+ & mut prev_var_decl. declarations ,
1144+ ctx,
1145+ non_scoped_literal_only,
1146+ ) ;
1147+ if new_len == 0 {
1148+ inlined = true ;
1149+ stmts. pop ( ) ;
1150+ } else if old_len != new_len {
1151+ inlined = true ;
1152+ prev_var_decl. declarations . truncate ( new_len) ;
1153+ break ;
1154+ } else {
1155+ break ;
1156+ }
1157+ }
1158+ inlined
1159+ }
11261160
1127- let last_non_inlined_index =
1128- prev_var_decl. declarations . iter_mut ( ) . rposition ( |prev_decl| {
1129- let Some ( prev_decl_init) = & mut prev_decl. init else {
1130- return true ;
1131- } ;
1132- let BindingPatternKind :: BindingIdentifier ( prev_decl_id) = & prev_decl. id . kind
1133- else {
1134- return true ;
1135- } ;
1136- if ctx. is_expression_whose_name_needs_to_be_kept ( prev_decl_init) {
1137- return true ;
1138- }
1139- let Some ( symbol_value) =
1140- ctx. state . symbol_values . get_symbol_value ( prev_decl_id. symbol_id ( ) )
1141- else {
1142- return true ;
1143- } ;
1144- // we should check whether it's exported by `symbol_value.exported`
1145- // because the variable might be exported with `export { foo }` rather than `export var foo`
1146- if symbol_value. exported
1147- || symbol_value. read_references_count > 1
1148- || symbol_value. write_references_count > 0
1149- {
1150- return true ;
1151- }
1152- if non_scoped_literal_only && !prev_decl_init. is_literal_value ( false , ctx) {
1153- return true ;
1154- }
1155- let replaced = Self :: substitute_single_use_symbol_in_expression (
1156- expr_in_stmt,
1157- & prev_decl_id. name ,
1158- prev_decl_init,
1159- prev_decl_init. may_have_side_effects ( ctx) ,
1160- ctx,
1161- ) ;
1162- if replaced != Some ( true ) {
1163- return true ;
1164- }
1165- false
1166- } ) ;
1167- match last_non_inlined_index {
1168- None => {
1169- // all inlined
1170- stmts. pop ( ) ;
1171- inlined = true ;
1172- }
1173- Some ( last_non_inlined_index)
1174- if last_non_inlined_index + 1 == prev_var_decl. declarations . len ( ) =>
1175- {
1176- // no change
1177- break ;
1178- }
1179- Some ( last_non_inlined_index) => {
1180- prev_var_decl. declarations . truncate ( last_non_inlined_index + 1 ) ;
1181- inlined = true ;
1182- break ;
1161+ fn substitute_single_use_symbol_within_declaration (
1162+ kind : VariableDeclarationKind ,
1163+ declarations : & mut Vec < ' a , VariableDeclarator < ' a > > ,
1164+ ctx : & Ctx < ' a , ' _ > ,
1165+ ) -> bool {
1166+ // TODO: we should skip this compression when direct eval exists
1167+ // because the code inside eval may reference the variable
1168+
1169+ let mut changed = false ;
1170+ if !Self :: keep_top_level_var_in_script_mode ( ctx) && !kind. is_using ( ) {
1171+ let mut i = 1 ;
1172+ while i < declarations. len ( ) {
1173+ let ( prev_decls, [ decl, ..] ) = declarations. split_at_mut ( i) else { unreachable ! ( ) } ;
1174+ let Some ( decl_init) = & mut decl. init else {
1175+ i += 1 ;
1176+ continue ;
1177+ } ;
1178+ let old_len = prev_decls. len ( ) ;
1179+ let new_len = Self :: substitute_single_use_symbol_in_expression_from_declarators (
1180+ decl_init, prev_decls, ctx, false ,
1181+ ) ;
1182+ if old_len != new_len {
1183+ changed = true ;
1184+ let drop_count = old_len - new_len;
1185+ declarations. drain ( i - drop_count..i) ;
1186+ i -= drop_count;
11831187 }
1188+ i += 1 ;
11841189 }
11851190 }
1186- inlined
1191+ changed
1192+ }
1193+
1194+ /// Returns new length
1195+ fn substitute_single_use_symbol_in_expression_from_declarators (
1196+ target_expr : & mut Expression < ' a > ,
1197+ declarators : & mut [ VariableDeclarator < ' a > ] ,
1198+ ctx : & Ctx < ' a , ' _ > ,
1199+ non_scoped_literal_only : bool ,
1200+ ) -> usize {
1201+ let last_non_inlined_index = declarators. iter_mut ( ) . rposition ( |prev_decl| {
1202+ let Some ( prev_decl_init) = & mut prev_decl. init else {
1203+ return true ;
1204+ } ;
1205+ let BindingPatternKind :: BindingIdentifier ( prev_decl_id) = & prev_decl. id . kind else {
1206+ return true ;
1207+ } ;
1208+ if ctx. is_expression_whose_name_needs_to_be_kept ( prev_decl_init) {
1209+ return true ;
1210+ }
1211+ let Some ( symbol_value) =
1212+ ctx. state . symbol_values . get_symbol_value ( prev_decl_id. symbol_id ( ) )
1213+ else {
1214+ return true ;
1215+ } ;
1216+ // we should check whether it's exported by `symbol_value.exported`
1217+ // because the variable might be exported with `export { foo }` rather than `export var foo`
1218+ if symbol_value. exported
1219+ || symbol_value. read_references_count > 1
1220+ || symbol_value. write_references_count > 0
1221+ {
1222+ return true ;
1223+ }
1224+ if non_scoped_literal_only && !prev_decl_init. is_literal_value ( false , ctx) {
1225+ return true ;
1226+ }
1227+ let replaced = Self :: substitute_single_use_symbol_in_expression (
1228+ target_expr,
1229+ & prev_decl_id. name ,
1230+ prev_decl_init,
1231+ prev_decl_init. may_have_side_effects ( ctx) ,
1232+ ctx,
1233+ ) ;
1234+ if replaced != Some ( true ) {
1235+ return true ;
1236+ }
1237+ false
1238+ } ) ;
1239+ match last_non_inlined_index {
1240+ None => 0 ,
1241+ Some ( last_non_inlined_index) => last_non_inlined_index + 1 ,
1242+ }
11871243 }
11881244
11891245 /// Returns Some(true) when the expression is successfully replaced.
0 commit comments