Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@
* calls and select statements with their evaluated result.
*/
public final class ConstantFoldingOptimizer implements CelAstOptimizer {
private static final ImmutableSet<String> BOOLEAN_RETURN_OPERATORS =
ImmutableSet.of(
Operator.LOGICAL_AND.getFunction(),
Operator.LOGICAL_OR.getFunction(),
Operator.LOGICAL_NOT.getFunction(),
Operator.EQUALS.getFunction(),
Operator.NOT_EQUALS.getFunction(),
Operator.LESS.getFunction(),
Operator.LESS_EQUALS.getFunction(),
Operator.GREATER.getFunction(),
Operator.GREATER_EQUALS.getFunction());

private static final ConstantFoldingOptimizer INSTANCE =
new ConstantFoldingOptimizer(ConstantFoldingOptions.newBuilder().build());

Expand Down Expand Up @@ -583,14 +595,31 @@ private Optional<CelMutableAst> maybeShortCircuitCall(
return Optional.of(astMutator.replaceSubtree(mutableAst, shortCircuitTarget, expr.id()));
}
if (newArgs.size() == 1) {
return Optional.of(astMutator.replaceSubtree(mutableAst, newArgs.get(0), expr.id()));
CelMutableExpr remainingArg = newArgs.get(0);
if (isBoolean(mutableAst, remainingArg)) {
return Optional.of(astMutator.replaceSubtree(mutableAst, remainingArg, expr.id()));
}
return Optional.empty();
}

// TODO: Support folding variadic AND/ORs.
throw new UnsupportedOperationException(
"Folding variadic logical operator is not supported yet.");
}

private boolean isBoolean(CelMutableAst mutableAst, CelMutableExpr expr) {
// The AST's type map relies on the type-checker having explicitly populated the type for a
// given node. However, during the optimization pipeline, mutated intermediate nodes might
// temporarily lack type metadata. Standard CEL operators like &&, ||, and == inherently
// always return a boolean, so checking the function name provides a reliable fallback when
// the type map is incomplete.
if (expr.getKind().equals(Kind.CALL)
&& BOOLEAN_RETURN_OPERATORS.contains(expr.call().function())) {
return true;
}
return mutableAst.getType(expr.id()).map(SimpleType.BOOL::equals).orElse(false);
}

private boolean isFoldedAggregateLiteral(CelMutableExpr expr) {
if (expr.getKind().equals(Kind.CONSTANT)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private static Cel setupEnv(CelBuilder celBuilder) {
return celBuilder
.addVar("x", SimpleType.DYN)
.addVar("y", SimpleType.DYN)
.addVar("bool_var", SimpleType.BOOL)
.addVar("list_var", ListType.create(SimpleType.STRING))
.addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.STRING))
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
Expand Down Expand Up @@ -127,17 +128,16 @@ private static Cel setupEnv(CelBuilder celBuilder) {
@TestParameters("{source: 'false || false', expected: 'false'}")
@TestParameters("{source: 'true && false || true', expected: 'true'}")
@TestParameters("{source: 'false && true || false', expected: 'false'}")
@TestParameters("{source: 'true && x', expected: 'x'}")
@TestParameters("{source: 'x && true', expected: 'x'}")
@TestParameters("{source: 'true && bool_var', expected: 'bool_var'}")
@TestParameters("{source: 'bool_var && false', expected: 'false'}")
@TestParameters("{source: 'bool_var && true', expected: 'bool_var'}")
@TestParameters("{source: 'false || [1 + 2, x][0]', expected: 'false || [3, x][0]'}")
@TestParameters("{source: 'false && x', expected: 'false'}")
@TestParameters("{source: 'x && false', expected: 'false'}")
@TestParameters("{source: 'true || x', expected: 'true'}")
@TestParameters("{source: 'x || true', expected: 'true'}")
@TestParameters("{source: 'false || x', expected: 'x'}")
@TestParameters("{source: 'x || false', expected: 'x'}")
@TestParameters("{source: 'true && x && true && x', expected: 'x && x'}")
@TestParameters("{source: 'false || x || false || x', expected: 'x || x'}")
@TestParameters("{source: 'false || x || false || y', expected: 'x || y'}")
@TestParameters("{source: 'false || bool_var', expected: 'bool_var'}")
@TestParameters("{source: 'bool_var || false', expected: 'bool_var'}")
@TestParameters("{source: 'true ? x + 1 : x + 2', expected: 'x + 1'}")
@TestParameters("{source: 'false ? x + 1 : x + 2', expected: 'x + 2'}")
@TestParameters(
Expand Down Expand Up @@ -498,6 +498,13 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E
@TestParameters("{source: '[true].exists(x, x == get_true())'}")
@TestParameters("{source: 'get_list([1, 2]).map(x, x * 2)'}")
@TestParameters("{source: '[(x - 1 > 3) ? (x - 1) : 5].exists(x, x - 1 > 3)'}")
@TestParameters("{source: 'true && x'}")
@TestParameters("{source: 'x && true'}")
@TestParameters("{source: 'false || x'}")
@TestParameters("{source: 'x || false'}")
@TestParameters("{source: 'true && x && true && x'}")
@TestParameters("{source: 'false || x || false || x'}")
@TestParameters("{source: 'false || x || false || y'}")
public void constantFold_noOp(String source) throws Exception {
CelAbstractSyntaxTree ast = cel.compile(source).getAst();

Expand Down
Loading