@@ -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
@@ -858,6 +865,13 @@ impl<'a> PeepholeOptimizations {
858865 ctx. state . changed = true ;
859866 }
860867 }
868+ if Self :: substitute_single_use_symbol_within_declaration (
869+ var_decl. kind ,
870+ & mut var_decl. declarations ,
871+ ctx,
872+ ) {
873+ ctx. state . changed = true ;
874+ }
861875 }
862876 match_expression ! ( ForStatementInit ) => {
863877 let init = init. to_expression_mut ( ) ;
@@ -1140,67 +1154,109 @@ impl<'a> PeepholeOptimizations {
11401154 if prev_var_decl. kind . is_using ( ) {
11411155 break ;
11421156 }
1157+ let old_len = prev_var_decl. declarations . len ( ) ;
1158+ let new_len = Self :: substitute_single_use_symbol_in_expression_from_declarators (
1159+ expr_in_stmt,
1160+ & mut prev_var_decl. declarations ,
1161+ ctx,
1162+ non_scoped_literal_only,
1163+ ) ;
1164+ if new_len == 0 {
1165+ inlined = true ;
1166+ stmts. pop ( ) ;
1167+ } else if old_len != new_len {
1168+ inlined = true ;
1169+ prev_var_decl. declarations . truncate ( new_len) ;
1170+ break ;
1171+ } else {
1172+ break ;
1173+ }
1174+ }
1175+ inlined
1176+ }
11431177
1144- let last_non_inlined_index =
1145- prev_var_decl. declarations . iter_mut ( ) . rposition ( |prev_decl| {
1146- let Some ( prev_decl_init) = & mut prev_decl. init else {
1147- return true ;
1148- } ;
1149- let BindingPatternKind :: BindingIdentifier ( prev_decl_id) = & prev_decl. id . kind
1150- else {
1151- return true ;
1152- } ;
1153- if ctx. is_expression_whose_name_needs_to_be_kept ( prev_decl_init) {
1154- return true ;
1155- }
1156- let Some ( symbol_value) =
1157- ctx. state . symbol_values . get_symbol_value ( prev_decl_id. symbol_id ( ) )
1158- else {
1159- return true ;
1160- } ;
1161- // we should check whether it's exported by `symbol_value.exported`
1162- // because the variable might be exported with `export { foo }` rather than `export var foo`
1163- if symbol_value. exported
1164- || symbol_value. read_references_count > 1
1165- || symbol_value. write_references_count > 0
1166- {
1167- return true ;
1168- }
1169- if non_scoped_literal_only && !prev_decl_init. is_literal_value ( false , ctx) {
1170- return true ;
1171- }
1172- let replaced = Self :: substitute_single_use_symbol_in_expression (
1173- expr_in_stmt,
1174- & prev_decl_id. name ,
1175- prev_decl_init,
1176- prev_decl_init. may_have_side_effects ( ctx) ,
1177- ctx,
1178- ) ;
1179- if replaced != Some ( true ) {
1180- return true ;
1181- }
1182- false
1183- } ) ;
1184- match last_non_inlined_index {
1185- None => {
1186- // all inlined
1187- stmts. pop ( ) ;
1188- inlined = true ;
1189- }
1190- Some ( last_non_inlined_index)
1191- if last_non_inlined_index + 1 == prev_var_decl. declarations . len ( ) =>
1192- {
1193- // no change
1194- break ;
1195- }
1196- Some ( last_non_inlined_index) => {
1197- prev_var_decl. declarations . truncate ( last_non_inlined_index + 1 ) ;
1198- inlined = true ;
1199- break ;
1178+ fn substitute_single_use_symbol_within_declaration (
1179+ kind : VariableDeclarationKind ,
1180+ declarations : & mut Vec < ' a , VariableDeclarator < ' a > > ,
1181+ ctx : & Ctx < ' a , ' _ > ,
1182+ ) -> bool {
1183+ // TODO: we should skip this compression when direct eval exists
1184+ // because the code inside eval may reference the variable
1185+
1186+ let mut changed = false ;
1187+ if !Self :: keep_top_level_var_in_script_mode ( ctx) && !kind. is_using ( ) {
1188+ let mut i = 1 ;
1189+ while i < declarations. len ( ) {
1190+ let ( prev_decls, [ decl, ..] ) = declarations. split_at_mut ( i) else { unreachable ! ( ) } ;
1191+ let Some ( decl_init) = & mut decl. init else {
1192+ i += 1 ;
1193+ continue ;
1194+ } ;
1195+ let old_len = prev_decls. len ( ) ;
1196+ let new_len = Self :: substitute_single_use_symbol_in_expression_from_declarators (
1197+ decl_init, prev_decls, ctx, false ,
1198+ ) ;
1199+ if old_len != new_len {
1200+ changed = true ;
1201+ let drop_count = old_len - new_len;
1202+ declarations. drain ( i - drop_count..i) ;
1203+ i -= drop_count;
12001204 }
1205+ i += 1 ;
12011206 }
12021207 }
1203- inlined
1208+ changed
1209+ }
1210+
1211+ /// Returns new length
1212+ fn substitute_single_use_symbol_in_expression_from_declarators (
1213+ target_expr : & mut Expression < ' a > ,
1214+ declarators : & mut [ VariableDeclarator < ' a > ] ,
1215+ ctx : & Ctx < ' a , ' _ > ,
1216+ non_scoped_literal_only : bool ,
1217+ ) -> usize {
1218+ let last_non_inlined_index = declarators. iter_mut ( ) . rposition ( |prev_decl| {
1219+ let Some ( prev_decl_init) = & mut prev_decl. init else {
1220+ return true ;
1221+ } ;
1222+ let BindingPatternKind :: BindingIdentifier ( prev_decl_id) = & prev_decl. id . kind else {
1223+ return true ;
1224+ } ;
1225+ if ctx. is_expression_whose_name_needs_to_be_kept ( prev_decl_init) {
1226+ return true ;
1227+ }
1228+ let Some ( symbol_value) =
1229+ ctx. state . symbol_values . get_symbol_value ( prev_decl_id. symbol_id ( ) )
1230+ else {
1231+ return true ;
1232+ } ;
1233+ // we should check whether it's exported by `symbol_value.exported`
1234+ // because the variable might be exported with `export { foo }` rather than `export var foo`
1235+ if symbol_value. exported
1236+ || symbol_value. read_references_count > 1
1237+ || symbol_value. write_references_count > 0
1238+ {
1239+ return true ;
1240+ }
1241+ if non_scoped_literal_only && !prev_decl_init. is_literal_value ( false , ctx) {
1242+ return true ;
1243+ }
1244+ let replaced = Self :: substitute_single_use_symbol_in_expression (
1245+ target_expr,
1246+ & prev_decl_id. name ,
1247+ prev_decl_init,
1248+ prev_decl_init. may_have_side_effects ( ctx) ,
1249+ ctx,
1250+ ) ;
1251+ if replaced != Some ( true ) {
1252+ return true ;
1253+ }
1254+ false
1255+ } ) ;
1256+ match last_non_inlined_index {
1257+ None => 0 ,
1258+ Some ( last_non_inlined_index) => last_non_inlined_index + 1 ,
1259+ }
12041260 }
12051261
12061262 /// Returns Some(true) when the expression is successfully replaced.
0 commit comments