@@ -65,6 +65,9 @@ impl<'a> PeepholeOptimizations {
65
65
"toString" => Self :: try_fold_to_string ( * span, arguments, object, ctx) ,
66
66
"pow" => self . try_fold_pow ( * span, arguments, object, ctx) ,
67
67
"sqrt" | "cbrt" => Self :: try_fold_roots ( * span, arguments, name, object, ctx) ,
68
+ "abs" | "ceil" | "floor" | "round" | "fround" | "trunc" | "sign" => {
69
+ Self :: try_fold_math_unary ( * span, arguments, name, object, ctx)
70
+ }
68
71
_ => None ,
69
72
} ;
70
73
if let Some ( replacement) = replacement {
@@ -348,6 +351,15 @@ impl<'a> PeepholeOptimizations {
348
351
result. into_iter ( ) . rev ( ) . collect ( )
349
352
}
350
353
354
+ fn validate_global_reference ( expr : & Expression < ' a > , target : & str , ctx : Ctx < ' a , ' _ > ) -> bool {
355
+ let Expression :: Identifier ( ident) = expr else { return false } ;
356
+ ctx. is_global_reference ( ident) && ident. name == target
357
+ }
358
+
359
+ fn validate_arguments ( args : & Arguments , expected_len : usize ) -> bool {
360
+ args. len ( ) == expected_len && args. iter ( ) . all ( Argument :: is_expression)
361
+ }
362
+
351
363
/// `Math.pow(a, b)` -> `+(a) ** +b`
352
364
fn try_fold_pow (
353
365
& self ,
@@ -359,12 +371,9 @@ impl<'a> PeepholeOptimizations {
359
371
if self . target < ESTarget :: ES2016 {
360
372
return None ;
361
373
}
362
-
363
- let Expression :: Identifier ( ident) = object else { return None } ;
364
- if ident. name != "Math" || !ctx. is_global_reference ( ident) {
365
- return None ;
366
- }
367
- if arguments. len ( ) != 2 || arguments. iter ( ) . any ( |arg| !arg. is_expression ( ) ) {
374
+ if !Self :: validate_global_reference ( object, "Math" , ctx)
375
+ || !Self :: validate_arguments ( arguments, 2 )
376
+ {
368
377
return None ;
369
378
}
370
379
@@ -404,11 +413,9 @@ impl<'a> PeepholeOptimizations {
404
413
object : & Expression < ' a > ,
405
414
ctx : Ctx < ' a , ' _ > ,
406
415
) -> Option < Expression < ' a > > {
407
- let Expression :: Identifier ( ident) = object else { return None } ;
408
- if ident. name != "Math" || !ctx. is_global_reference ( ident) {
409
- return None ;
410
- }
411
- if arguments. len ( ) != 1 || !arguments[ 0 ] . is_expression ( ) {
416
+ if !Self :: validate_global_reference ( object, "Math" , ctx)
417
+ || !Self :: validate_arguments ( arguments, 1 )
418
+ {
412
419
return None ;
413
420
}
414
421
let arg_val = ctx. get_side_free_number_value ( arguments[ 0 ] . to_expression ( ) ) ?;
@@ -433,15 +440,54 @@ impl<'a> PeepholeOptimizations {
433
440
"cbrt" => arg_val. cbrt ( ) ,
434
441
_ => unreachable ! ( ) ,
435
442
} ;
436
- if calculated_val. fract ( ) == 0.0 {
437
- return Some ( ctx. ast . expression_numeric_literal (
438
- span,
439
- calculated_val,
440
- None ,
441
- NumberBase :: Decimal ,
442
- ) ) ;
443
+ ( calculated_val. fract ( ) == 0.0 ) . then ( || {
444
+ ctx. ast . expression_numeric_literal ( span, calculated_val, None , NumberBase :: Decimal )
445
+ } )
446
+ }
447
+
448
+ fn try_fold_math_unary (
449
+ span : Span ,
450
+ arguments : & Arguments ,
451
+ name : & str ,
452
+ object : & Expression < ' a > ,
453
+ ctx : Ctx < ' a , ' _ > ,
454
+ ) -> Option < Expression < ' a > > {
455
+ if !Self :: validate_global_reference ( object, "Math" , ctx)
456
+ || !Self :: validate_arguments ( arguments, 1 )
457
+ {
458
+ return None ;
443
459
}
444
- None
460
+ let arg_val = ctx. get_side_free_number_value ( arguments[ 0 ] . to_expression ( ) ) ?;
461
+ let result = match name {
462
+ "abs" => arg_val. abs ( ) ,
463
+ "ceil" => arg_val. ceil ( ) ,
464
+ "floor" => arg_val. floor ( ) ,
465
+ "round" => {
466
+ // We should be aware that the behavior in JavaScript and Rust towards `round` is different.
467
+ // In Rust, when facing `.5`, it may follow `half-away-from-zero` instead of round to upper bound.
468
+ // So we need to handle it manually.
469
+ let frac_part = arg_val. fract ( ) ;
470
+ let epsilon = 2f64 . powf ( -52f64 ) ;
471
+ if ( frac_part. abs ( ) - 0.5 ) . abs ( ) < epsilon {
472
+ // We should ceil it.
473
+ arg_val. ceil ( )
474
+ } else {
475
+ arg_val. round ( )
476
+ }
477
+ }
478
+ #[ allow( clippy:: cast_possible_truncation) ]
479
+ "fround" if arg_val. fract ( ) == 0f64 || arg_val. is_nan ( ) || arg_val. is_infinite ( ) => {
480
+ f64:: from ( arg_val as f32 )
481
+ }
482
+ "fround" => return None ,
483
+ "trunc" => arg_val. trunc ( ) ,
484
+ "sign" if arg_val. to_bits ( ) == 0f64 . to_bits ( ) => 0f64 ,
485
+ "sign" if arg_val. to_bits ( ) == ( -0f64 ) . to_bits ( ) => -0f64 ,
486
+ "sign" => arg_val. signum ( ) ,
487
+ _ => unreachable ! ( ) ,
488
+ } ;
489
+ // These results are always shorter to return as a number, so we can just return them as NumericLiteral.
490
+ Some ( ctx. ast . expression_numeric_literal ( span, result, None , NumberBase :: Decimal ) )
445
491
}
446
492
447
493
/// `[].concat(a).concat(b)` -> `[].concat(a, b)`
@@ -705,6 +751,14 @@ mod test {
705
751
assert_eq ! ( run( code, Some ( opts) ) , run( expected, None ) ) ;
706
752
}
707
753
754
+ fn test_value ( code : & str , expected : & str ) {
755
+ test ( format ! ( "x = {code}" ) . as_str ( ) , format ! ( "x = {expected}" ) . as_str ( ) ) ;
756
+ }
757
+
758
+ fn test_same_value ( code : & str ) {
759
+ test_same ( format ! ( "x = {code}" ) . as_str ( ) ) ;
760
+ }
761
+
708
762
#[ test]
709
763
fn test_string_index_of ( ) {
710
764
test ( "x = 'abcdef'.indexOf('g')" , "x = -1" ) ;
@@ -1108,99 +1162,106 @@ mod test {
1108
1162
}
1109
1163
1110
1164
#[ test]
1111
- #[ ignore]
1112
1165
fn test_fold_math_functions_abs ( ) {
1113
- test_same ( "Math.abs(Math.random())" ) ;
1114
-
1115
- test ( "Math.abs('-1')" , "1" ) ;
1116
- test ( "Math.abs(-2)" , "2" ) ;
1117
- test ( "Math.abs(null)" , "0" ) ;
1118
- test ( "Math.abs('')" , "0" ) ;
1119
- test ( "Math.abs([])" , "0" ) ;
1120
- test ( "Math.abs([2])" , "2" ) ;
1121
- test ( "Math.abs([1,2])" , "NaN" ) ;
1122
- test ( "Math.abs({})" , "NaN" ) ;
1123
- test ( "Math.abs('string');" , "NaN" ) ;
1166
+ test_same_value ( "Math.abs(Math.random())" ) ;
1167
+
1168
+ test_value ( "Math.abs('-1')" , "1" ) ;
1169
+ test_value ( "Math.abs(-2)" , "2" ) ;
1170
+ test_value ( "Math.abs(null)" , "0" ) ;
1171
+ test_value ( "Math.abs('')" , "0" ) ;
1172
+ test_value ( "Math.abs(NaN)" , "NaN" ) ;
1173
+ test_value ( "Math.abs(-0)" , "0" ) ;
1174
+ test_value ( "Math.abs(-Infinity)" , "Infinity" ) ;
1175
+ // TODO
1176
+ // test_value("Math.abs([])", "0");
1177
+ // test_value("Math.abs([2])", "2");
1178
+ // test_value("Math.abs([1,2])", "NaN");
1179
+ test_value ( "Math.abs({})" , "NaN" ) ;
1180
+ test_value ( "Math.abs('string');" , "NaN" ) ;
1124
1181
}
1125
1182
1126
1183
#[ test]
1127
1184
#[ ignore]
1128
1185
fn test_fold_math_functions_imul ( ) {
1129
- test_same ( "Math.imul(Math.random(),2)" ) ;
1130
- test ( "Math.imul(-1,1)" , "-1" ) ;
1131
- test ( "Math.imul(2,2)" , "4" ) ;
1132
- test ( "Math.imul(2)" , "0" ) ;
1133
- test ( "Math.imul(2,3,5)" , "6" ) ;
1134
- test ( "Math.imul(0xfffffffe, 5)" , "-10" ) ;
1135
- test ( "Math.imul(0xffffffff, 5)" , "-5" ) ;
1136
- test ( "Math.imul(0xfffffffffffff34f, 0xfffffffffff342)" , "13369344" ) ;
1137
- test ( "Math.imul(0xfffffffffffff34f, -0xfffffffffff342)" , "-13369344" ) ;
1138
- test ( "Math.imul(NaN, 2)" , "0" ) ;
1186
+ test_same_value ( "Math.imul(Math.random(),2)" ) ;
1187
+ test_value ( "Math.imul(-1,1)" , "-1" ) ;
1188
+ test_value ( "Math.imul(2,2)" , "4" ) ;
1189
+ test_value ( "Math.imul(2)" , "0" ) ;
1190
+ test_value ( "Math.imul(2,3,5)" , "6" ) ;
1191
+ test_value ( "Math.imul(0xfffffffe, 5)" , "-10" ) ;
1192
+ test_value ( "Math.imul(0xffffffff, 5)" , "-5" ) ;
1193
+ test_value ( "Math.imul(0xfffffffffffff34f, 0xfffffffffff342)" , "13369344" ) ;
1194
+ test_value ( "Math.imul(0xfffffffffffff34f, -0xfffffffffff342)" , "-13369344" ) ;
1195
+ test_value ( "Math.imul(NaN, 2)" , "0" ) ;
1139
1196
}
1140
1197
1141
1198
#[ test]
1142
- #[ ignore]
1143
1199
fn test_fold_math_functions_ceil ( ) {
1144
- test_same ( "Math.ceil(Math.random())" ) ;
1200
+ test_same_value ( "Math.ceil(Math.random())" ) ;
1145
1201
1146
- test ( "Math.ceil(1)" , "1" ) ;
1147
- test ( "Math.ceil(1.5)" , "2" ) ;
1148
- test ( "Math.ceil(1.3)" , "2" ) ;
1149
- test ( "Math.ceil(-1.3)" , "-1" ) ;
1202
+ test_value ( "Math.ceil(1)" , "1" ) ;
1203
+ test_value ( "Math.ceil(1.5)" , "2" ) ;
1204
+ test_value ( "Math.ceil(1.3)" , "2" ) ;
1205
+ test_value ( "Math.ceil(-1.3)" , "-1" ) ;
1150
1206
}
1151
1207
1152
1208
#[ test]
1153
- #[ ignore]
1154
1209
fn test_fold_math_functions_floor ( ) {
1155
- test_same ( "Math.floor(Math.random())" ) ;
1210
+ test_same_value ( "Math.floor(Math.random())" ) ;
1156
1211
1157
- test ( "Math.floor(1)" , "1" ) ;
1158
- test ( "Math.floor(1.5)" , "1" ) ;
1159
- test ( "Math.floor(1.3)" , "1" ) ;
1160
- test ( "Math.floor(-1.3)" , "-2" ) ;
1212
+ test_value ( "Math.floor(1)" , "1" ) ;
1213
+ test_value ( "Math.floor(1.5)" , "1" ) ;
1214
+ test_value ( "Math.floor(1.3)" , "1" ) ;
1215
+ test_value ( "Math.floor(-1.3)" , "-2" ) ;
1161
1216
}
1162
1217
1163
1218
#[ test]
1164
- #[ ignore]
1165
1219
fn test_fold_math_functions_fround ( ) {
1166
- test_same ( "Math.fround(Math.random())" ) ;
1220
+ test_same_value ( "Math.fround(Math.random())" ) ;
1167
1221
1168
- test ( "Math.fround(NaN)" , "NaN" ) ;
1169
- test ( "Math.fround(Infinity)" , "Infinity" ) ;
1170
- test ( "Math.fround(1)" , "1" ) ;
1171
- test ( "Math.fround(0)" , "0" ) ;
1222
+ test_value ( "Math.fround(NaN)" , "NaN" ) ;
1223
+ test_value ( "Math.fround(Infinity)" , "Infinity" ) ;
1224
+ test_value ( "Math.fround(-Infinity)" , "-Infinity" ) ;
1225
+ test_value ( "Math.fround(1)" , "1" ) ;
1226
+ test_value ( "Math.fround(0)" , "0" ) ;
1227
+ test_value ( "Math.fround(16777217)" , "16777216" ) ;
1228
+ test_value ( "Math.fround(16777218)" , "16777218" ) ;
1172
1229
}
1173
1230
1174
1231
#[ test]
1175
1232
fn test_fold_math_functions_fround_j2cl ( ) {
1176
- test_same ( "Math.fround(1.2)" ) ;
1233
+ test_same_value ( "Math.fround(1.2)" ) ;
1177
1234
}
1178
1235
1179
1236
#[ test]
1180
- #[ ignore]
1181
1237
fn test_fold_math_functions_round ( ) {
1182
- test_same ( "Math.round(Math.random())" ) ;
1183
- test ( "Math.round(NaN)" , "NaN" ) ;
1184
- test ( "Math.round(3.5)" , "4" ) ;
1185
- test ( "Math.round(-3.5)" , "-3" ) ;
1238
+ test_same_value ( "Math.round(Math.random())" ) ;
1239
+ test_value ( "Math.round(NaN)" , "NaN" ) ;
1240
+ test_value ( "Math.round(3)" , "3" ) ;
1241
+ test_value ( "Math.round(3.5)" , "4" ) ;
1242
+ test_value ( "Math.round(-3.5)" , "-3" ) ;
1186
1243
}
1187
1244
1188
1245
#[ test]
1189
- #[ ignore]
1190
1246
fn test_fold_math_functions_sign ( ) {
1191
- test_same ( "Math.sign(Math.random())" ) ;
1192
- test ( "Math.sign(NaN)" , "NaN" ) ;
1193
- test ( "Math.sign(3.5)" , "1" ) ;
1194
- test ( "Math.sign(-3.5)" , "-1" ) ;
1247
+ test_same_value ( "Math.sign(Math.random())" ) ;
1248
+ test_value ( "Math.sign(NaN)" , "NaN" ) ;
1249
+ test_value ( "Math.sign(0.0)" , "0" ) ;
1250
+ test_value ( "Math.sign(-0.0)" , "-0" ) ;
1251
+ test_value ( "Math.sign(0.01)" , "1" ) ;
1252
+ test_value ( "Math.sign(-0.01)" , "-1" ) ;
1253
+ test_value ( "Math.sign(3.5)" , "1" ) ;
1254
+ test_value ( "Math.sign(-3.5)" , "-1" ) ;
1195
1255
}
1196
1256
1197
1257
#[ test]
1198
- #[ ignore]
1199
1258
fn test_fold_math_functions_trunc ( ) {
1200
- test_same ( "Math.trunc(Math.random())" ) ;
1201
- test ( "Math.sign(NaN)" , "NaN" ) ;
1202
- test ( "Math.trunc(3.5)" , "3" ) ;
1203
- test ( "Math.trunc(-3.5)" , "-3" ) ;
1259
+ test_same_value ( "Math.trunc(Math.random())" ) ;
1260
+ test_value ( "Math.sign(NaN)" , "NaN" ) ;
1261
+ test_value ( "Math.trunc(3.5)" , "3" ) ;
1262
+ test_value ( "Math.trunc(-3.5)" , "-3" ) ;
1263
+ test_value ( "Math.trunc(0.5)" , "0" ) ;
1264
+ test_value ( "Math.trunc(-0.5)" , "-0" ) ;
1204
1265
}
1205
1266
1206
1267
#[ test]
0 commit comments