@@ -2803,6 +2803,13 @@ func SimplifyUnusedExpr(expr Expr, unsupportedFeatures compat.JSFeature, isUnbou
2803
2803
}
2804
2804
2805
2805
case BinOpLogicalAnd , BinOpLogicalOr , BinOpNullishCoalescing :
2806
+ // If this is a boolean logical operation and the result is unused, then
2807
+ // we know the left operand will only be used for its boolean value and
2808
+ // can be simplified under that assumption
2809
+ if e .Op != BinOpNullishCoalescing {
2810
+ e .Left = SimplifyBooleanExpr (e .Left )
2811
+ }
2812
+
2806
2813
// Preserve short-circuit behavior: the left expression is only unused if
2807
2814
// the right expression can be completely removed. Otherwise, the left
2808
2815
// expression is important for the branch.
@@ -3151,3 +3158,189 @@ func TryToInsertOptionalChain(test Expr, expr Expr) bool {
3151
3158
3152
3159
return false
3153
3160
}
3161
+
3162
+ type SideEffects uint8
3163
+
3164
+ const (
3165
+ CouldHaveSideEffects SideEffects = iota
3166
+ NoSideEffects
3167
+ )
3168
+
3169
+ func ToNullOrUndefinedWithSideEffects (data E ) (isNullOrUndefined bool , sideEffects SideEffects , ok bool ) {
3170
+ switch e := data .(type ) {
3171
+ case * EInlinedEnum :
3172
+ return ToNullOrUndefinedWithSideEffects (e .Value .Data )
3173
+
3174
+ // Never null or undefined
3175
+ case * EBoolean , * ENumber , * EString , * ERegExp ,
3176
+ * EFunction , * EArrow , * EBigInt :
3177
+ return false , NoSideEffects , true
3178
+
3179
+ // Never null or undefined
3180
+ case * EObject , * EArray , * EClass :
3181
+ return false , CouldHaveSideEffects , true
3182
+
3183
+ // Always null or undefined
3184
+ case * ENull , * EUndefined :
3185
+ return true , NoSideEffects , true
3186
+
3187
+ case * EUnary :
3188
+ switch e .Op {
3189
+ case
3190
+ // Always number or bigint
3191
+ UnOpPos , UnOpNeg , UnOpCpl ,
3192
+ UnOpPreDec , UnOpPreInc , UnOpPostDec , UnOpPostInc ,
3193
+ // Always boolean
3194
+ UnOpNot , UnOpTypeof , UnOpDelete :
3195
+ return false , CouldHaveSideEffects , true
3196
+
3197
+ // Always undefined
3198
+ case UnOpVoid :
3199
+ return true , CouldHaveSideEffects , true
3200
+ }
3201
+
3202
+ case * EBinary :
3203
+ switch e .Op {
3204
+ case
3205
+ // Always string or number or bigint
3206
+ BinOpAdd , BinOpAddAssign ,
3207
+ // Always number or bigint
3208
+ BinOpSub , BinOpMul , BinOpDiv , BinOpRem , BinOpPow ,
3209
+ BinOpSubAssign , BinOpMulAssign , BinOpDivAssign , BinOpRemAssign , BinOpPowAssign ,
3210
+ BinOpShl , BinOpShr , BinOpUShr ,
3211
+ BinOpShlAssign , BinOpShrAssign , BinOpUShrAssign ,
3212
+ BinOpBitwiseOr , BinOpBitwiseAnd , BinOpBitwiseXor ,
3213
+ BinOpBitwiseOrAssign , BinOpBitwiseAndAssign , BinOpBitwiseXorAssign ,
3214
+ // Always boolean
3215
+ BinOpLt , BinOpLe , BinOpGt , BinOpGe , BinOpIn , BinOpInstanceof ,
3216
+ BinOpLooseEq , BinOpLooseNe , BinOpStrictEq , BinOpStrictNe :
3217
+ return false , CouldHaveSideEffects , true
3218
+
3219
+ case BinOpComma :
3220
+ if isNullOrUndefined , _ , ok := ToNullOrUndefinedWithSideEffects (e .Right .Data ); ok {
3221
+ return isNullOrUndefined , CouldHaveSideEffects , true
3222
+ }
3223
+ }
3224
+ }
3225
+
3226
+ return false , NoSideEffects , false
3227
+ }
3228
+
3229
+ func ToBooleanWithSideEffects (data E ) (boolean bool , SideEffects SideEffects , ok bool ) {
3230
+ switch e := data .(type ) {
3231
+ case * EInlinedEnum :
3232
+ return ToBooleanWithSideEffects (e .Value .Data )
3233
+
3234
+ case * ENull , * EUndefined :
3235
+ return false , NoSideEffects , true
3236
+
3237
+ case * EBoolean :
3238
+ return e .Value , NoSideEffects , true
3239
+
3240
+ case * ENumber :
3241
+ return e .Value != 0 && ! math .IsNaN (e .Value ), NoSideEffects , true
3242
+
3243
+ case * EBigInt :
3244
+ return e .Value != "0" , NoSideEffects , true
3245
+
3246
+ case * EString :
3247
+ return len (e .Value ) > 0 , NoSideEffects , true
3248
+
3249
+ case * EFunction , * EArrow , * ERegExp :
3250
+ return true , NoSideEffects , true
3251
+
3252
+ case * EObject , * EArray , * EClass :
3253
+ return true , CouldHaveSideEffects , true
3254
+
3255
+ case * EUnary :
3256
+ switch e .Op {
3257
+ case UnOpVoid :
3258
+ return false , CouldHaveSideEffects , true
3259
+
3260
+ case UnOpTypeof :
3261
+ // Never an empty string
3262
+ return true , CouldHaveSideEffects , true
3263
+
3264
+ case UnOpNot :
3265
+ if boolean , SideEffects , ok := ToBooleanWithSideEffects (e .Value .Data ); ok {
3266
+ return ! boolean , SideEffects , true
3267
+ }
3268
+ }
3269
+
3270
+ case * EBinary :
3271
+ switch e .Op {
3272
+ case BinOpLogicalOr :
3273
+ // "anything || truthy" is truthy
3274
+ if boolean , _ , ok := ToBooleanWithSideEffects (e .Right .Data ); ok && boolean {
3275
+ return true , CouldHaveSideEffects , true
3276
+ }
3277
+
3278
+ case BinOpLogicalAnd :
3279
+ // "anything && falsy" is falsy
3280
+ if boolean , _ , ok := ToBooleanWithSideEffects (e .Right .Data ); ok && ! boolean {
3281
+ return false , CouldHaveSideEffects , true
3282
+ }
3283
+
3284
+ case BinOpComma :
3285
+ // "anything, truthy/falsy" is truthy/falsy
3286
+ if boolean , _ , ok := ToBooleanWithSideEffects (e .Right .Data ); ok {
3287
+ return boolean , CouldHaveSideEffects , true
3288
+ }
3289
+ }
3290
+ }
3291
+
3292
+ return false , CouldHaveSideEffects , false
3293
+ }
3294
+
3295
+ // Simplify syntax when we know it's used inside a boolean context
3296
+ func SimplifyBooleanExpr (expr Expr ) Expr {
3297
+ switch e := expr .Data .(type ) {
3298
+ case * EUnary :
3299
+ if e .Op == UnOpNot {
3300
+ // "!!a" => "a"
3301
+ if e2 , ok2 := e .Value .Data .(* EUnary ); ok2 && e2 .Op == UnOpNot {
3302
+ return SimplifyBooleanExpr (e2 .Value )
3303
+ }
3304
+
3305
+ e .Value = SimplifyBooleanExpr (e .Value )
3306
+ }
3307
+
3308
+ case * EBinary :
3309
+ switch e .Op {
3310
+ case BinOpLogicalAnd :
3311
+ if boolean , SideEffects , ok := ToBooleanWithSideEffects (e .Right .Data ); ok && boolean && SideEffects == NoSideEffects {
3312
+ // "if (anything && truthyNoSideEffects)" => "if (anything)"
3313
+ return e .Left
3314
+ }
3315
+
3316
+ case BinOpLogicalOr :
3317
+ if boolean , SideEffects , ok := ToBooleanWithSideEffects (e .Right .Data ); ok && ! boolean && SideEffects == NoSideEffects {
3318
+ // "if (anything || falsyNoSideEffects)" => "if (anything)"
3319
+ return e .Left
3320
+ }
3321
+ }
3322
+
3323
+ case * EIf :
3324
+ if boolean , SideEffects , ok := ToBooleanWithSideEffects (e .Yes .Data ); ok && SideEffects == NoSideEffects {
3325
+ if boolean {
3326
+ // "if (anything1 ? truthyNoSideEffects : anything2)" => "if (anything1 || anything2)"
3327
+ return JoinWithLeftAssociativeOp (BinOpLogicalOr , e .Test , e .No )
3328
+ } else {
3329
+ // "if (anything1 ? falsyNoSideEffects : anything2)" => "if (!anything1 || anything2)"
3330
+ return JoinWithLeftAssociativeOp (BinOpLogicalAnd , Not (e .Test ), e .No )
3331
+ }
3332
+ }
3333
+
3334
+ if boolean , SideEffects , ok := ToBooleanWithSideEffects (e .No .Data ); ok && SideEffects == NoSideEffects {
3335
+ if boolean {
3336
+ // "if (anything1 ? anything2 : truthyNoSideEffects)" => "if (!anything1 || anything2)"
3337
+ return JoinWithLeftAssociativeOp (BinOpLogicalOr , Not (e .Test ), e .Yes )
3338
+ } else {
3339
+ // "if (anything1 ? anything2 : falsyNoSideEffects)" => "if (anything1 && anything2)"
3340
+ return JoinWithLeftAssociativeOp (BinOpLogicalAnd , e .Test , e .Yes )
3341
+ }
3342
+ }
3343
+ }
3344
+
3345
+ return expr
3346
+ }
0 commit comments