@@ -144,9 +144,12 @@ public boolean visit(JsFunction x, JsContext ctx) {
144144 }
145145 }
146146
147+ /**
148+ * Describes how is the result of evaluation used.
149+ */
147150 private enum EvalMode {
148- BOOL ,
149- VOID
151+ BOOL , // result will be coerced to a boolean
152+ VOID // result will be discarded
150153 }
151154
152155 /**
@@ -158,7 +161,11 @@ private enum EvalMode {
158161 */
159162 private class StaticEvalVisitor extends JsModVisitor {
160163
161- private Map <JsExpression , EvalMode > evalContext = new HashMap <>();
164+ /**
165+ * Stores how are expression evaluations used.
166+ * Missing entry = no coercion, difference between null and false matters.
167+ */
168+ private final Map <JsExpression , EvalMode > evalContext = new HashMap <>();
162169
163170 /**
164171 * This is used by {@link #additionCoercesToString}.
@@ -175,9 +182,9 @@ public boolean visit(JsBinaryOperation x, JsContext ctx) {
175182 && (x .getOperator () == JsBinaryOperator .AND || x .getOperator () == JsBinaryOperator .OR )) {
176183 evalContext .put (x .getArg1 (), EvalMode .BOOL );
177184 evalContext .put (x .getArg2 (), evalContext .get (x ));
178- }
179- if (x .getOperator () == JsBinaryOperator .COMMA ) {
185+ } else if (x .getOperator () == JsBinaryOperator .COMMA ) {
180186 evalContext .put (x .getArg1 (), EvalMode .VOID );
187+ evalContext .put (x .getArg2 (), evalContext .get (x ));
181188 }
182189 return true ;
183190 }
@@ -194,15 +201,10 @@ public void endVisit(JsBinaryOperation x, JsContext ctx) {
194201 result = arg1 ;
195202 } else if (op == JsBinaryOperator .AND ) {
196203 result = shortCircuitAnd (x , evalContext );
197- evalContext .remove (arg1 );
198- evalContext .remove (arg2 );
199204 } else if (op == JsBinaryOperator .OR ) {
200205 result = shortCircuitOr (x , evalContext );
201- evalContext .remove (arg1 );
202- evalContext .remove (arg2 );
203206 } else if (op == JsBinaryOperator .COMMA ) {
204207 result = trySimplifyComma (x );
205- evalContext .remove (x .getArg1 ());
206208 } else if (op == JsBinaryOperator .EQ || op == JsBinaryOperator .REF_EQ ) {
207209 result = simplifyEqAndRefEq (x );
208210 } else if (op == JsBinaryOperator .NEQ || op == JsBinaryOperator .REF_NEQ ) {
@@ -224,7 +226,8 @@ public void endVisit(JsBinaryOperation x, JsContext ctx) {
224226 break ;
225227 }
226228 }
227-
229+ evalContext .remove (arg1 );
230+ evalContext .remove (arg2 );
228231 result = maybeReorderOperations (result );
229232
230233 if (result != x ) {
@@ -290,7 +293,13 @@ public void endVisit(JsConditional x, JsContext ctx) {
290293 JsExpression condExpr = x .getTestExpression ();
291294 JsExpression thenExpr = x .getThenExpression ();
292295 JsExpression elseExpr = x .getElseExpression ();
293- if (condExpr instanceof CanBooleanEval ) {
296+ if (condExpr instanceof JsBinaryOperation
297+ && ((JsBinaryOperation ) condExpr ).getOperator () == JsBinaryOperator .COMMA ) {
298+ JsBinaryOperation condition = (JsBinaryOperation ) condExpr ;
299+ JsExpression newConditional = accept (new JsConditional (x .getSourceInfo (),
300+ condition .getArg2 (), thenExpr , elseExpr ));
301+ ctx .replaceMe (withSideEffect (newConditional , condition .getArg1 (), x .getSourceInfo ()));
302+ } else if (condExpr instanceof CanBooleanEval ) {
294303 CanBooleanEval condEval = (CanBooleanEval ) condExpr ;
295304 if (condEval .isBooleanTrue ()) {
296305 JsBinaryOperation binOp = new JsBinaryOperation (x .getSourceInfo (),
@@ -652,8 +661,8 @@ protected static JsExpression shortCircuitAnd(JsBinaryOperation expr,
652661 JsExpression arg2 = expr .getArg2 ();
653662 if (arg1 instanceof CanBooleanEval ) {
654663 CanBooleanEval eval1 = (CanBooleanEval ) arg1 ;
655- if (eval1 .isBooleanTrue () && ! arg1 . hasSideEffects () ) {
656- return arg2 ;
664+ if (eval1 .isBooleanTrue ()) {
665+ return withSideEffect ( arg2 , arg1 , expr . getSourceInfo ()) ;
657666 } else if (eval1 .isBooleanFalse ()) {
658667 return arg1 ;
659668 }
@@ -662,13 +671,19 @@ protected static JsExpression shortCircuitAnd(JsBinaryOperation expr,
662671 CanBooleanEval eval2 = (CanBooleanEval ) arg2 ;
663672 if (eval2 .isBooleanTrue () && !arg2 .hasSideEffects ()) {
664673 return arg1 ;
665- } else if (eval2 .isBooleanFalse () && ! arg1 . hasSideEffects () ) {
666- return arg2 ;
674+ } else if (eval2 .isBooleanFalse ()) {
675+ return withSideEffect ( arg2 , arg1 , expr . getSourceInfo ()) ;
667676 }
668677 }
669678 return expr ;
670679 }
671680
681+ private static JsExpression withSideEffect (JsExpression main , JsExpression sideEffect ,
682+ SourceInfo sourceInfo ) {
683+ return !sideEffect .hasSideEffects () ? main
684+ : new JsBinaryOperation (sourceInfo , JsBinaryOperator .COMMA , sideEffect , main );
685+ }
686+
672687 /**
673688 * Simplify short circuit OR expressions.
674689 *
@@ -688,18 +703,18 @@ protected static JsExpression shortCircuitOr(JsBinaryOperation expr,
688703 JsExpression arg2 = expr .getArg2 ();
689704 if (arg1 instanceof CanBooleanEval ) {
690705 CanBooleanEval eval1 = (CanBooleanEval ) arg1 ;
691- if (eval1 .isBooleanTrue ()) {
706+ if (eval1 .isBooleanFalse ()) {
707+ return withSideEffect (arg2 , arg1 , expr .getSourceInfo ());
708+ } else if (eval1 .isBooleanTrue ()) {
692709 return arg1 ;
693- } else if (eval1 .isBooleanFalse () && !arg1 .hasSideEffects ()) {
694- return arg2 ;
695710 }
696711 }
697712 if (arg2 instanceof CanBooleanEval && evalContext .containsKey (arg1 )) {
698713 CanBooleanEval eval2 = (CanBooleanEval ) arg2 ;
699- if (eval2 .isBooleanTrue () && !arg1 .hasSideEffects ()) {
700- return arg2 ;
701- } else if (eval2 .isBooleanFalse () && !arg2 .hasSideEffects ()) {
714+ if (eval2 .isBooleanFalse () && !arg2 .hasSideEffects ()) {
702715 return arg1 ;
716+ } else if (eval2 .isBooleanTrue ()) {
717+ return withSideEffect (arg2 , arg1 , expr .getSourceInfo ());
703718 }
704719 }
705720 return expr ;
0 commit comments