diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index 96796cde..1da3e57d 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -50,11 +50,26 @@ public final class CelOptionalLibrary implements CelCompilerLibrary, CelRuntimeLibrary { public static final CelOptionalLibrary INSTANCE = new CelOptionalLibrary(); - private static final String VALUE_FUNCTION = "value"; - private static final String HAS_VALUE_FUNCTION = "hasValue"; - private static final String OPTIONAL_NONE_FUNCTION = "optional.none"; - private static final String OPTIONAL_OF_FUNCTION = "optional.of"; - private static final String OPTIONAL_OF_NON_ZERO_VALUE_FUNCTION = "optional.ofNonZeroValue"; + /** Enumerations of function names used for supporting optionals. */ + public enum Function { + VALUE("value"), + HAS_VALUE("hasValue"), + OPTIONAL_NONE("optional.none"), + OPTIONAL_OF("optional.of"), + OPTIONAL_OF_NON_ZERO_VALUE("optional.ofNonZeroValue"), + OR("or"), + OR_VALUE("orValue"); + private final String functionName; + + public String getFunction() { + return functionName; + } + + Function(String functionName) { + this.functionName = functionName; + } + } + private static final String UNUSED_ITER_VAR = "#unused"; @Override @@ -80,20 +95,20 @@ public void setCheckerOptions(CelCheckerBuilder checkerBuilder) { MapType mapTypeKv = MapType.create(paramTypeK, paramTypeV); checkerBuilder.addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( - OPTIONAL_OF_FUNCTION, + Function.OPTIONAL_OF.getFunction(), CelOverloadDecl.newGlobalOverload("optional_of", optionalTypeV, paramTypeV)), CelFunctionDecl.newFunctionDeclaration( - OPTIONAL_OF_NON_ZERO_VALUE_FUNCTION, + Function.OPTIONAL_OF_NON_ZERO_VALUE.getFunction(), CelOverloadDecl.newGlobalOverload( "optional_ofNonZeroValue", optionalTypeV, paramTypeV)), CelFunctionDecl.newFunctionDeclaration( - OPTIONAL_NONE_FUNCTION, + Function.OPTIONAL_NONE.getFunction(), CelOverloadDecl.newGlobalOverload("optional_none", optionalTypeV)), CelFunctionDecl.newFunctionDeclaration( - VALUE_FUNCTION, + Function.VALUE.getFunction(), CelOverloadDecl.newMemberOverload("optional_value", paramTypeV, optionalTypeV)), CelFunctionDecl.newFunctionDeclaration( - HAS_VALUE_FUNCTION, + Function.HAS_VALUE.getFunction(), CelOverloadDecl.newMemberOverload("optional_hasValue", SimpleType.BOOL, optionalTypeV)), // Note: Implementation of "or" and "orValue" are special-cased inside the interpreter. // Hence, their bindings are not provided here. @@ -218,18 +233,18 @@ private static Optional expandOptMap( return Optional.of( exprFactory.newGlobalCall( Operator.CONDITIONAL.getFunction(), - exprFactory.newReceiverCall(HAS_VALUE_FUNCTION, target), + exprFactory.newReceiverCall(Function.HAS_VALUE.getFunction(), target), exprFactory.newGlobalCall( - OPTIONAL_OF_FUNCTION, + Function.OPTIONAL_OF.getFunction(), exprFactory.fold( UNUSED_ITER_VAR, exprFactory.newList(), varName, - exprFactory.newReceiverCall(VALUE_FUNCTION, target), + exprFactory.newReceiverCall(Function.VALUE.getFunction(), target), exprFactory.newBoolLiteral(true), exprFactory.newIdentifier(varName), mapExpr)), - exprFactory.newGlobalCall(OPTIONAL_NONE_FUNCTION))); + exprFactory.newGlobalCall(Function.OPTIONAL_NONE.getFunction()))); } private static Optional expandOptFlatMap( @@ -252,16 +267,16 @@ private static Optional expandOptFlatMap( return Optional.of( exprFactory.newGlobalCall( Operator.CONDITIONAL.getFunction(), - exprFactory.newReceiverCall(HAS_VALUE_FUNCTION, target), + exprFactory.newReceiverCall(Function.HAS_VALUE.getFunction(), target), exprFactory.fold( UNUSED_ITER_VAR, exprFactory.newList(), varName, - exprFactory.newReceiverCall(VALUE_FUNCTION, target), + exprFactory.newReceiverCall(Function.VALUE.getFunction(), target), exprFactory.newBoolLiteral(true), exprFactory.newIdentifier(varName), mapExpr), - exprFactory.newGlobalCall(OPTIONAL_NONE_FUNCTION))); + exprFactory.newGlobalCall(Function.OPTIONAL_NONE.getFunction()))); } private CelOptionalLibrary() {} diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel index 77b3d289..785ddf74 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -21,6 +21,7 @@ java_library( "//common/ast", "//common/ast:expr_util", "//common/navigation", + "//extensions:optional_library", "//optimizer:ast_optimizer", "//optimizer:mutable_ast", "//optimizer:optimization_exception", diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java index 8ca19e94..6766ae3c 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -32,6 +32,7 @@ import dev.cel.common.ast.CelExprUtil; import dev.cel.common.navigation.CelNavigableAst; import dev.cel.common.navigation.CelNavigableExpr; +import dev.cel.extensions.CelOptionalLibrary.Function; import dev.cel.optimizer.CelAstOptimizer; import dev.cel.optimizer.CelOptimizationException; import dev.cel.optimizer.MutableAst; @@ -68,10 +69,9 @@ public static ConstantFoldingOptimizer newInstance( // Use optional.of and optional.none as sentinel function names for folding optional calls. // TODO: Leverage CelValue representation of Optionals instead when available. - private static final String OPTIONAL_OF_FUNCTION = "optional.of"; - private static final String OPTIONAL_NONE_FUNCTION = "optional.none"; private static final CelExpr OPTIONAL_NONE_EXPR = - CelExpr.ofCallExpr(0, Optional.empty(), OPTIONAL_NONE_FUNCTION, ImmutableList.of()); + CelExpr.ofCallExpr( + 0, Optional.empty(), Function.OPTIONAL_NONE.getFunction(), ImmutableList.of()); private final ConstantFoldingOptions constantFoldingOptions; private final MutableAst mutableAst; @@ -130,8 +130,8 @@ private static boolean canFold(CelNavigableExpr navigableExpr) { String functionName = celCall.function(); // These are already folded or do not need to be folded. - if (functionName.equals(OPTIONAL_OF_FUNCTION) - || functionName.equals(OPTIONAL_NONE_FUNCTION)) { + if (functionName.equals(Function.OPTIONAL_OF.getFunction()) + || functionName.equals(Function.OPTIONAL_NONE.getFunction())) { return false; } @@ -264,13 +264,13 @@ private Optional maybeAdaptEvaluatedResult(Object result) { private Optional maybeRewriteOptional( Optional optResult, CelAbstractSyntaxTree ast, CelExpr expr) { if (!optResult.isPresent()) { - if (!expr.callOrDefault().function().equals(OPTIONAL_NONE_FUNCTION)) { + if (!expr.callOrDefault().function().equals(Function.OPTIONAL_NONE.getFunction())) { // An empty optional value was encountered. Rewrite the tree with optional.none call. // This is to account for other optional functions returning an empty optional value // e.g: optional.ofNonZeroValue(0) return Optional.of(mutableAst.replaceSubtree(ast, OPTIONAL_NONE_EXPR, expr.id())); } - } else if (!expr.callOrDefault().function().equals(OPTIONAL_OF_FUNCTION)) { + } else if (!expr.callOrDefault().function().equals(Function.OPTIONAL_OF.getFunction())) { Object unwrappedResult = optResult.get(); if (!CelConstant.isConstantValue(unwrappedResult)) { // Evaluated result is not a constant. Leave the optional as is. @@ -281,7 +281,7 @@ private Optional maybeRewriteOptional( CelExpr.newBuilder() .setCall( CelCall.newBuilder() - .setFunction(OPTIONAL_OF_FUNCTION) + .setFunction(Function.OPTIONAL_OF.getFunction()) .addArgs( CelExpr.newBuilder() .setConstant(CelConstant.ofObjectValue(unwrappedResult)) @@ -444,12 +444,12 @@ private CelAbstractSyntaxTree pruneOptionalListElements(CelAbstractSyntaxTree as if (element.exprKind().getKind().equals(Kind.CALL)) { CelCall call = element.call(); - if (call.function().equals(OPTIONAL_NONE_FUNCTION)) { + if (call.function().equals(Function.OPTIONAL_NONE.getFunction())) { // Skip optional.none. // Skipping causes the list to get smaller. newOptIndex--; continue; - } else if (call.function().equals(OPTIONAL_OF_FUNCTION)) { + } else if (call.function().equals(Function.OPTIONAL_OF.getFunction())) { CelExpr arg = call.args().get(0); if (arg.exprKind().getKind().equals(Kind.CONSTANT)) { updatedElemBuilder.add(call.args().get(0)); @@ -491,11 +491,11 @@ private CelAbstractSyntaxTree pruneOptionalMapElements(CelAbstractSyntaxTree ast } CelCall call = value.call(); - if (call.function().equals(OPTIONAL_NONE_FUNCTION)) { + if (call.function().equals(Function.OPTIONAL_NONE.getFunction())) { // Skip the element. This is resolving an optional.none: ex {?1: optional.none()}. modified = true; continue; - } else if (call.function().equals(OPTIONAL_OF_FUNCTION)) { + } else if (call.function().equals(Function.OPTIONAL_OF.getFunction())) { CelExpr arg = call.args().get(0); if (arg.exprKind().getKind().equals(Kind.CONSTANT)) { modified = true; @@ -537,11 +537,11 @@ private CelAbstractSyntaxTree pruneOptionalStructElements( } CelCall call = value.call(); - if (call.function().equals(OPTIONAL_NONE_FUNCTION)) { + if (call.function().equals(Function.OPTIONAL_NONE.getFunction())) { // Skip the element. This is resolving an optional.none: ex msg{?field: optional.none()}. modified = true; continue; - } else if (call.function().equals(OPTIONAL_OF_FUNCTION)) { + } else if (call.function().equals(Function.OPTIONAL_OF.getFunction())) { CelExpr arg = call.args().get(0); if (arg.exprKind().getKind().equals(Kind.CONSTANT)) { modified = true;