Skip to content

Commit 27f3fee

Browse files
committed
Ensure SpEL ternary and Elvis expressions are enclosed in parentheses in toStringAST()
Prior to this commit, ternary and Elvis expressions enclosed in parentheses (to account for operator precedence) were properly parsed and evaluated; however, the corresponding toStringAST() implementations did not enclose the results in parentheses. Consequently, the string representation of the ASTs did not reflect the original semantics of such expressions. For example, given "(4 % 2 == 0 ? 1 : 0) * 10" as the expression to parse and evaluate, the result of toStringAST() was previously "(((4 % 2) == 0) ? 1 : 0 * 10)" instead of "((((4 % 2) == 0) ? 1 : 0) * 10)", implying that 0 should be multiplied by 10 instead of multiplying the result of the ternary expression by 10. This commit addresses this by ensuring that SpEL ternary and Elvis expressions are enclosed in parentheses in toStringAST(). Closes gh-29463
1 parent b42b785 commit 27f3fee

File tree

4 files changed

+37
-7
lines changed

4 files changed

+37
-7
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/Elvis.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
*
3333
* @author Andy Clement
3434
* @author Juergen Hoeller
35+
* @author Sam Brannen
3536
* @since 3.0
3637
*/
3738
public class Elvis extends SpelNodeImpl {
@@ -64,7 +65,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
6465

6566
@Override
6667
public String toStringAST() {
67-
return getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST();
68+
return "(" + getChild(0).toStringAST() + " ?: " + getChild(1).toStringAST() + ")";
6869
}
6970

7071
@Override

spring-expression/src/main/java/org/springframework/expression/spel/ast/Ternary.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
*
3333
* @author Andy Clement
3434
* @author Juergen Hoeller
35+
* @author Sam Brannen
3536
* @since 3.0
3637
*/
3738
public class Ternary extends SpelNodeImpl {
@@ -62,7 +63,7 @@ public TypedValue getValueInternal(ExpressionState state) throws EvaluationExcep
6263

6364
@Override
6465
public String toStringAST() {
65-
return getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST();
66+
return "(" + getChild(0).toStringAST() + " ? " + getChild(1).toStringAST() + " : " + getChild(2).toStringAST() + ")";
6667
}
6768

6869
private void computeExitTypeDescriptor() {

spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ void createObjectsOnAttemptToReferenceNull() {
116116
void elvisOperator() {
117117
evaluate("'Andy'?:'Dave'", "Andy", String.class);
118118
evaluate("null?:'Dave'", "Dave", String.class);
119+
evaluate("3?:1", 3, Integer.class);
120+
evaluate("(2*3)?:1*10", 6, Integer.class);
121+
evaluate("null?:2*10", 20, Integer.class);
122+
evaluate("(null?:1)*10", 10, Integer.class);
119123
}
120124

121125
@Test
@@ -624,6 +628,24 @@ void ternaryOperator05() {
624628
evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class);
625629
}
626630

631+
@Test
632+
void ternaryOperator06() {
633+
evaluate("3?:#var=5", 3, Integer.class);
634+
evaluate("null?:#var=5", 5, Integer.class);
635+
evaluate("2>4?(3>2?true:false):(5<3?true:false)", false, Boolean.class);
636+
}
637+
638+
@Test
639+
void ternaryExpressionWithImplicitGrouping() {
640+
evaluate("4 % 2 == 0 ? 2 : 3 * 10", 2, Integer.class);
641+
evaluate("4 % 2 == 1 ? 2 : 3 * 10", 30, Integer.class);
642+
}
643+
644+
@Test
645+
void ternaryExpressionWithExplicitGrouping() {
646+
evaluate("((4 % 2 == 0) ? 2 : 1) * 10", 20, Integer.class);
647+
}
648+
627649
@Test
628650
void ternaryOperatorWithNullValue() {
629651
assertThatExceptionOfType(EvaluationException.class).isThrownBy(

spring-expression/src/test/java/org/springframework/expression/spel/ParsingTests.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ void relOperatorsGE02() {
162162

163163
@Test
164164
void elvis() {
165-
parseCheck("3?:1", "3 ?: 1");
165+
parseCheck("3?:1", "(3 ?: 1)");
166+
parseCheck("(2*3)?:1*10", "((2 * 3) ?: (1 * 10))");
167+
parseCheck("((2*3)?:1)*10", "(((2 * 3) ?: 1) * 10)");
166168
}
167169

168170
// void relOperatorsIn01() {
@@ -404,13 +406,17 @@ void assignmentToVariables01() {
404406

405407
@Test
406408
void ternaryOperator01() {
407-
parseCheck("1>2?3:4", "(1 > 2) ? 3 : 4");
409+
parseCheck("1>2?3:4", "((1 > 2) ? 3 : 4)");
410+
parseCheck("(a ? 1 : 0) * 10", "((a ? 1 : 0) * 10)");
411+
parseCheck("(a?1:0)*10", "((a ? 1 : 0) * 10)");
412+
parseCheck("(4 % 2 == 0 ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
413+
parseCheck("((4 % 2 == 0) ? 1 : 0) * 10", "((((4 % 2) == 0) ? 1 : 0) * 10)");
408414
}
409415

410416
@Test
411417
void ternaryOperator02() {
412418
parseCheck("{1}.#isEven(#this) == 'y'?'it is even':'it is odd'",
413-
"({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd'");
419+
"(({1}.#isEven(#this) == 'y') ? 'it is even' : 'it is odd')");
414420
}
415421

416422
//

0 commit comments

Comments
 (0)