Refer to README.md for available functions.
+ *
+ *
This will include only the specific functions denoted by {@link
+ * CelOptionalLibrary.Function}.
+ */
+ public static CelOptionalLibrary optional(CelOptionalLibrary.Function... functions) {
+ return optional(ImmutableSet.copyOf(functions));
+ }
+
+ /**
+ * Implementation of optional values.
+ *
+ *
Refer to README.md for available functions.
+ *
+ *
This will include only the specific functions denoted by {@link
+ * CelOptionalLibrary.Function}.
+ */
+ public static CelOptionalLibrary optional(Set functions) {
+ return new CelOptionalLibrary(functions);
+ }
+
/**
* Extended functions for string manipulation.
*
diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalCompilerLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalCompilerLibrary.java
new file mode 100644
index 000000000..766fbb8cc
--- /dev/null
+++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalCompilerLibrary.java
@@ -0,0 +1,492 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.extensions;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.HAS_VALUE;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.OPTIONAL_NONE;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.OPTIONAL_OF;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.OPTIONAL_OF_NON_ZERO_VALUE;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.OPTIONAL_UNWRAP;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.OR;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.OR_VALUE;
+import static dev.cel.extensions.CelOptionalCompilerLibrary.Function.VALUE;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.Immutable;
+import dev.cel.checker.CelCheckerBuilder;
+import dev.cel.common.CelFunctionDecl;
+import dev.cel.common.CelIssue;
+import dev.cel.common.CelOverloadDecl;
+import dev.cel.common.CelVarDecl;
+import dev.cel.common.Operator;
+import dev.cel.common.ast.CelExpr;
+import dev.cel.common.types.ListType;
+import dev.cel.common.types.MapType;
+import dev.cel.common.types.OptionalType;
+import dev.cel.common.types.SimpleType;
+import dev.cel.common.types.TypeParamType;
+import dev.cel.common.types.TypeType;
+import dev.cel.compiler.CelCompilerLibrary;
+import dev.cel.parser.CelMacro;
+import dev.cel.parser.CelMacroExprFactory;
+import dev.cel.parser.CelParserBuilder;
+import java.util.Optional;
+import java.util.Set;
+
+/** Internal implementation of CEL optional compile-time extensions. */
+@Immutable
+public final class CelOptionalCompilerLibrary
+ implements CelCompilerLibrary, CelExtensionLibrary.FeatureSet {
+
+ private static final class Types {
+ private static final TypeParamType PARAM_TYPE_K = TypeParamType.create("K");
+ private static final TypeParamType PARAM_TYPE_V = TypeParamType.create("V");
+ private static final OptionalType OPTIONAL_TYPE_V = OptionalType.create(PARAM_TYPE_V);
+ private static final ListType LIST_TYPE_V = ListType.create(PARAM_TYPE_V);
+ private static final MapType MAP_TYPE_KV = MapType.create(PARAM_TYPE_K, PARAM_TYPE_V);
+ }
+
+ /** Declarations for the optional extension library. */
+ public enum OptionalDeclaration implements CelFunctionDecl.Declarer {
+ OPTIONAL_OF(
+ CelFunctionDecl.newFunctionDeclaration(
+ "optional.of",
+ CelOverloadDecl.newGlobalOverload(
+ "optional_of", Types.OPTIONAL_TYPE_V, Types.PARAM_TYPE_V))),
+ OPTIONAL_OF_NON_ZERO_VALUE(
+ CelFunctionDecl.newFunctionDeclaration(
+ "optional.ofNonZeroValue",
+ CelOverloadDecl.newGlobalOverload(
+ "optional_ofNonZeroValue", Types.OPTIONAL_TYPE_V, Types.PARAM_TYPE_V))),
+ OPTIONAL_NONE(
+ CelFunctionDecl.newFunctionDeclaration(
+ "optional.none",
+ CelOverloadDecl.newGlobalOverload("optional_none", Types.OPTIONAL_TYPE_V))),
+ OPTIONAL_VALUE(
+ CelFunctionDecl.newFunctionDeclaration(
+ "value",
+ CelOverloadDecl.newMemberOverload(
+ "optional_value", Types.PARAM_TYPE_V, Types.OPTIONAL_TYPE_V))),
+ OPTIONAL_HAS_VALUE(
+ CelFunctionDecl.newFunctionDeclaration(
+ "hasValue",
+ CelOverloadDecl.newMemberOverload(
+ "optional_hasValue", SimpleType.BOOL, Types.OPTIONAL_TYPE_V))),
+ OPTIONAL_UNWRAP(
+ CelFunctionDecl.newFunctionDeclaration(
+ "optional.unwrap",
+ CelOverloadDecl.newGlobalOverload(
+ "optional_unwrap_list",
+ Types.LIST_TYPE_V,
+ ListType.create(Types.OPTIONAL_TYPE_V)))),
+ OPTIONAL_OR(
+ CelFunctionDecl.newFunctionDeclaration(
+ "or",
+ CelOverloadDecl.newMemberOverload(
+ "optional_or_optional",
+ Types.OPTIONAL_TYPE_V,
+ Types.OPTIONAL_TYPE_V,
+ Types.OPTIONAL_TYPE_V))),
+ OPTIONAL_OR_VALUE(
+ CelFunctionDecl.newFunctionDeclaration(
+ "orValue",
+ CelOverloadDecl.newMemberOverload(
+ "optional_orValue_value",
+ Types.PARAM_TYPE_V,
+ Types.OPTIONAL_TYPE_V,
+ Types.PARAM_TYPE_V))),
+ OPTIONAL_SELECT(
+ CelFunctionDecl.newFunctionDeclaration(
+ Operator.OPTIONAL_SELECT.getFunction(),
+ CelOverloadDecl.newGlobalOverload(
+ "select_optional_field",
+ Types.OPTIONAL_TYPE_V,
+ SimpleType.DYN,
+ SimpleType.STRING))),
+ OPTIONAL_INDEX(
+ CelFunctionDecl.newFunctionDeclaration(
+ Operator.OPTIONAL_INDEX.getFunction(),
+ CelOverloadDecl.newGlobalOverload(
+ "list_optindex_optional_int",
+ Types.OPTIONAL_TYPE_V,
+ Types.LIST_TYPE_V,
+ SimpleType.INT),
+ CelOverloadDecl.newGlobalOverload(
+ "optional_list_optindex_optional_int",
+ Types.OPTIONAL_TYPE_V,
+ OptionalType.create(Types.LIST_TYPE_V),
+ SimpleType.INT),
+ CelOverloadDecl.newGlobalOverload(
+ "map_optindex_optional_value",
+ Types.OPTIONAL_TYPE_V,
+ Types.MAP_TYPE_KV,
+ Types.PARAM_TYPE_K),
+ CelOverloadDecl.newGlobalOverload(
+ "optional_map_optindex_optional_value",
+ Types.OPTIONAL_TYPE_V,
+ OptionalType.create(Types.MAP_TYPE_KV),
+ Types.PARAM_TYPE_K))),
+ OPTIONAL_INDEX_OPERAND(
+ CelFunctionDecl.newFunctionDeclaration(
+ Operator.INDEX.getFunction(),
+ CelOverloadDecl.newGlobalOverload(
+ "optional_list_index_int",
+ Types.OPTIONAL_TYPE_V,
+ OptionalType.create(Types.LIST_TYPE_V),
+ SimpleType.INT),
+ CelOverloadDecl.newGlobalOverload(
+ "optional_map_index_value",
+ Types.OPTIONAL_TYPE_V,
+ OptionalType.create(Types.MAP_TYPE_KV),
+ Types.PARAM_TYPE_K)));
+
+ private final CelFunctionDecl celFunctionDecl;
+
+ OptionalDeclaration(CelFunctionDecl celFunctionDecl) {
+ this.celFunctionDecl = celFunctionDecl;
+ }
+
+ @Override
+ public CelFunctionDecl functionDecl() {
+ return celFunctionDecl;
+ }
+ }
+
+ /** Enumerations of function names used for supporting optionals. */
+ public enum Function {
+ VALUE(OptionalDeclaration.OPTIONAL_VALUE.functionDecl()),
+ HAS_VALUE(OptionalDeclaration.OPTIONAL_HAS_VALUE.functionDecl()),
+ OPTIONAL_NONE(OptionalDeclaration.OPTIONAL_NONE.functionDecl()),
+ OPTIONAL_OF(OptionalDeclaration.OPTIONAL_OF.functionDecl()),
+ OPTIONAL_UNWRAP(OptionalDeclaration.OPTIONAL_UNWRAP.functionDecl()),
+ OPTIONAL_OF_NON_ZERO_VALUE(OptionalDeclaration.OPTIONAL_OF_NON_ZERO_VALUE.functionDecl()),
+ OR(OptionalDeclaration.OPTIONAL_OR.functionDecl()),
+ OR_VALUE(OptionalDeclaration.OPTIONAL_OR_VALUE.functionDecl()),
+ FIRST(
+ CelFunctionDecl.newFunctionDeclaration(
+ "first",
+ CelOverloadDecl.newMemberOverload(
+ "optional_list_first",
+ "Return the first value in a list if present, otherwise optional.none()",
+ Types.OPTIONAL_TYPE_V,
+ Types.LIST_TYPE_V))),
+ LAST(
+ CelFunctionDecl.newFunctionDeclaration(
+ "last",
+ CelOverloadDecl.newMemberOverload(
+ "optional_list_last",
+ "Return the last value in a list if present, otherwise optional.none()",
+ Types.OPTIONAL_TYPE_V,
+ Types.LIST_TYPE_V)));
+
+ private final CelFunctionDecl functionDecl;
+
+ public String getFunction() {
+ return functionDecl.name();
+ }
+
+ CelFunctionDecl getFunctionDecl() {
+ return functionDecl;
+ }
+
+ Function(CelFunctionDecl functionDecl) {
+ this.functionDecl = functionDecl;
+ }
+ }
+
+ private static final String UNUSED_ITER_VAR = "#unused";
+ private static final String OPTIONAL_MAP_VAR = "@target";
+
+ private static final ImmutableSet OPERATOR_FUNCTION_DECLS =
+ ImmutableSet.of(
+ OptionalDeclaration.OPTIONAL_SELECT.functionDecl(),
+ OptionalDeclaration.OPTIONAL_INDEX.functionDecl(),
+ OptionalDeclaration.OPTIONAL_INDEX_OPERAND.functionDecl());
+
+ private static final class Library implements CelExtensionLibrary {
+ private final CelOptionalCompilerLibrary version0 =
+ new CelOptionalCompilerLibrary(
+ 0,
+ ImmutableSet.of(
+ OPTIONAL_OF,
+ OPTIONAL_OF_NON_ZERO_VALUE,
+ OPTIONAL_NONE,
+ VALUE,
+ HAS_VALUE,
+ OPTIONAL_UNWRAP,
+ OR,
+ OR_VALUE),
+ ImmutableSet.of(
+ CelMacro.newReceiverMacro("optMap", 2, CelOptionalCompilerLibrary::expandOptMap)),
+ ImmutableSet.of(
+ CelVarDecl.newVarDeclaration(
+ OptionalType.NAME, TypeType.create(Types.OPTIONAL_TYPE_V))));
+
+ private final CelOptionalCompilerLibrary version1 =
+ new CelOptionalCompilerLibrary(
+ 1,
+ ImmutableSet.of(
+ OPTIONAL_OF,
+ OPTIONAL_OF_NON_ZERO_VALUE,
+ OPTIONAL_NONE,
+ VALUE,
+ HAS_VALUE,
+ OPTIONAL_UNWRAP,
+ OR,
+ OR_VALUE),
+ ImmutableSet.builder()
+ .addAll(version0.macros)
+ .add(
+ CelMacro.newReceiverMacro(
+ "optFlatMap", 2, CelOptionalCompilerLibrary::expandOptFlatMap))
+ .build(),
+ version0.variables);
+
+ private final CelOptionalCompilerLibrary version2 =
+ new CelOptionalCompilerLibrary(
+ 2, ImmutableSet.copyOf(Function.values()), version1.macros, version1.variables);
+
+ @Override
+ public String name() {
+ return "optional";
+ }
+
+ @Override
+ public ImmutableSet versions() {
+ return ImmutableSet.of(version0, version1, version2);
+ }
+ }
+
+ private static final Library LIBRARY = new Library();
+
+ public static CelExtensionLibrary library() {
+ return LIBRARY;
+ }
+
+ /** Returns the latest version of the 'optional' compiler extension. */
+ public static CelOptionalCompilerLibrary optional() {
+ return library().latest();
+ }
+
+ /** Returns the specified version of the 'optional' compiler extension. */
+ public static CelOptionalCompilerLibrary optional(int version) {
+ return library().version(version);
+ }
+
+ /** Returns the 'optional' compiler extension with only the specified functions. */
+ public static CelOptionalCompilerLibrary optional(Function... functions) {
+ return optional(ImmutableSet.copyOf(functions));
+ }
+
+ /** Returns the 'optional' compiler extension with only the specified functions. */
+ public static CelOptionalCompilerLibrary optional(Set functions) {
+ return new CelOptionalCompilerLibrary(functions);
+ }
+
+ private final int version;
+ private final ImmutableSet functions;
+ private final ImmutableSet macros;
+ private final ImmutableSet variables;
+
+ CelOptionalCompilerLibrary(Set functions) {
+ this(
+ -1,
+ functions,
+ ImmutableSet.of(
+ CelMacro.newReceiverMacro("optMap", 2, CelOptionalCompilerLibrary::expandOptMap),
+ CelMacro.newReceiverMacro(
+ "optFlatMap", 2, CelOptionalCompilerLibrary::expandOptFlatMap)),
+ ImmutableSet.of(
+ CelVarDecl.newVarDeclaration(
+ OptionalType.NAME, TypeType.create(Types.OPTIONAL_TYPE_V))));
+ }
+
+ private CelOptionalCompilerLibrary(
+ int version, Set functions, Set macros, Set variables) {
+ this.version = version;
+ this.functions =
+ ImmutableSet.builder()
+ .addAll(functions.stream().map(Function::getFunctionDecl).collect(toImmutableSet()))
+ .addAll(OPERATOR_FUNCTION_DECLS)
+ .build();
+ this.macros = ImmutableSet.copyOf(macros);
+ this.variables = ImmutableSet.copyOf(variables);
+ }
+
+ @Override
+ public int version() {
+ return version;
+ }
+
+ @Override
+ public ImmutableSet functions() {
+ return functions;
+ }
+
+ @Override
+ public ImmutableSet variables() {
+ return variables;
+ }
+
+ @Override
+ public ImmutableSet macros() {
+ return macros;
+ }
+
+ @Override
+ public void setParserOptions(CelParserBuilder parserBuilder) {
+ if (!parserBuilder.getOptions().enableOptionalSyntax()) {
+ // Enable optional syntax by default if the optional library has been added to the
+ // environment
+ parserBuilder.setOptions(
+ parserBuilder.getOptions().toBuilder().enableOptionalSyntax(true).build());
+ }
+ parserBuilder.addMacros(macros());
+ }
+
+ @Override
+ public void setCheckerOptions(CelCheckerBuilder checkerBuilder) {
+ checkerBuilder.addVarDeclarations(variables());
+ checkerBuilder.addFunctionDeclarations(functions());
+ }
+
+ private static Optional expandOptMap(
+ CelMacroExprFactory exprFactory, CelExpr target, ImmutableList arguments) {
+ checkNotNull(exprFactory);
+ checkNotNull(target);
+ checkArgument(arguments.size() == 2);
+
+ CelExpr varIdent = checkNotNull(arguments.get(0));
+ if (varIdent.exprKind().getKind() != CelExpr.ExprKind.Kind.IDENT) {
+ return Optional.of(
+ exprFactory.reportError(
+ CelIssue.formatError(
+ exprFactory.getSourceLocation(varIdent),
+ "optMap() variable name must be a simple identifier")));
+ }
+ CelExpr mapExpr = checkNotNull(arguments.get(1));
+ String varName = varIdent.ident().name();
+
+ if (target.exprKind().getKind() == CelExpr.ExprKind.Kind.IDENT) {
+ return Optional.of(
+ exprFactory.newGlobalCall(
+ Operator.CONDITIONAL.getFunction(),
+ exprFactory.newReceiverCall(HAS_VALUE.getFunction(), target),
+ exprFactory.newGlobalCall(
+ OPTIONAL_OF.getFunction(),
+ exprFactory.fold(
+ UNUSED_ITER_VAR,
+ exprFactory.newList(),
+ varName,
+ exprFactory.newReceiverCall(VALUE.getFunction(), exprFactory.copy(target)),
+ exprFactory.newBoolLiteral(true),
+ exprFactory.newIdentifier(varName),
+ mapExpr)),
+ exprFactory.newGlobalCall(OPTIONAL_NONE.getFunction())));
+ }
+
+ CelExpr localVar = exprFactory.newIdentifier(OPTIONAL_MAP_VAR);
+ CelExpr localVarCopy = exprFactory.copy(localVar);
+ CelExpr conditionalExpr =
+ exprFactory.newGlobalCall(
+ Operator.CONDITIONAL.getFunction(),
+ exprFactory.newReceiverCall(HAS_VALUE.getFunction(), localVar),
+ exprFactory.newGlobalCall(
+ OPTIONAL_OF.getFunction(),
+ exprFactory.fold(
+ UNUSED_ITER_VAR,
+ exprFactory.newList(),
+ varName,
+ exprFactory.newReceiverCall(VALUE.getFunction(), localVarCopy),
+ exprFactory.newBoolLiteral(true),
+ exprFactory.newIdentifier(varName),
+ mapExpr)),
+ exprFactory.newGlobalCall(OPTIONAL_NONE.getFunction()));
+
+ return Optional.of(
+ exprFactory.fold(
+ UNUSED_ITER_VAR,
+ exprFactory.newList(),
+ OPTIONAL_MAP_VAR,
+ target,
+ exprFactory.newBoolLiteral(false),
+ exprFactory.newIdentifier(OPTIONAL_MAP_VAR),
+ conditionalExpr));
+ }
+
+ private static Optional expandOptFlatMap(
+ CelMacroExprFactory exprFactory, CelExpr target, ImmutableList arguments) {
+ checkNotNull(exprFactory);
+ checkNotNull(target);
+ checkArgument(arguments.size() == 2);
+
+ CelExpr varIdent = checkNotNull(arguments.get(0));
+ if (varIdent.exprKind().getKind() != CelExpr.ExprKind.Kind.IDENT) {
+ return Optional.of(
+ exprFactory.reportError(
+ CelIssue.formatError(
+ exprFactory.getSourceLocation(varIdent),
+ "optFlatMap() variable name must be a simple identifier")));
+ }
+ CelExpr mapExpr = checkNotNull(arguments.get(1));
+ String varName = varIdent.ident().name();
+
+ if (target.exprKind().getKind() == CelExpr.ExprKind.Kind.IDENT) {
+ return Optional.of(
+ exprFactory.newGlobalCall(
+ Operator.CONDITIONAL.getFunction(),
+ exprFactory.newReceiverCall(HAS_VALUE.getFunction(), target),
+ exprFactory.fold(
+ UNUSED_ITER_VAR,
+ exprFactory.newList(),
+ varName,
+ exprFactory.newReceiverCall(VALUE.getFunction(), exprFactory.copy(target)),
+ exprFactory.newBoolLiteral(true),
+ exprFactory.newIdentifier(varName),
+ mapExpr),
+ exprFactory.newGlobalCall(OPTIONAL_NONE.getFunction())));
+ }
+
+ CelExpr localVar = exprFactory.newIdentifier(OPTIONAL_MAP_VAR);
+ CelExpr localVarCopy = exprFactory.copy(localVar);
+ CelExpr conditionalExpr =
+ exprFactory.newGlobalCall(
+ Operator.CONDITIONAL.getFunction(),
+ exprFactory.newReceiverCall(HAS_VALUE.getFunction(), localVar),
+ exprFactory.fold(
+ UNUSED_ITER_VAR,
+ exprFactory.newList(),
+ varName,
+ exprFactory.newReceiverCall(VALUE.getFunction(), localVarCopy),
+ exprFactory.newBoolLiteral(true),
+ exprFactory.newIdentifier(varName),
+ mapExpr),
+ exprFactory.newGlobalCall(OPTIONAL_NONE.getFunction()));
+
+ return Optional.of(
+ exprFactory.fold(
+ UNUSED_ITER_VAR,
+ exprFactory.newList(),
+ OPTIONAL_MAP_VAR,
+ target,
+ exprFactory.newBoolLiteral(false),
+ exprFactory.newIdentifier(OPTIONAL_MAP_VAR),
+ conditionalExpr));
+ }
+}
diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java
index 85ee9c756..a83130457 100644
--- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java
+++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java
@@ -14,280 +14,115 @@
package dev.cel.extensions;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static dev.cel.common.Operator.INDEX;
-import static dev.cel.common.Operator.OPTIONAL_INDEX;
-import static dev.cel.common.Operator.OPTIONAL_SELECT;
-import static dev.cel.extensions.CelOptionalLibrary.Function.FIRST;
-import static dev.cel.extensions.CelOptionalLibrary.Function.HAS_VALUE;
-import static dev.cel.extensions.CelOptionalLibrary.Function.LAST;
-import static dev.cel.extensions.CelOptionalLibrary.Function.OPTIONAL_NONE;
-import static dev.cel.extensions.CelOptionalLibrary.Function.OPTIONAL_OF;
-import static dev.cel.extensions.CelOptionalLibrary.Function.OPTIONAL_OF_NON_ZERO_VALUE;
-import static dev.cel.extensions.CelOptionalLibrary.Function.OPTIONAL_UNWRAP;
-import static dev.cel.extensions.CelOptionalLibrary.Function.VALUE;
-import static dev.cel.runtime.CelFunctionBinding.fromOverloads;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.primitives.Ints;
-import com.google.common.primitives.UnsignedLong;
-import com.google.protobuf.Duration;
-import com.google.protobuf.Message;
-import com.google.protobuf.Timestamp;
+import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.CelFunctionDecl;
-import dev.cel.common.CelIssue;
import dev.cel.common.CelOptions;
-import dev.cel.common.CelOverloadDecl;
import dev.cel.common.CelVarDecl;
-import dev.cel.common.Operator;
-import dev.cel.common.ast.CelExpr;
-import dev.cel.common.types.ListType;
-import dev.cel.common.types.MapType;
-import dev.cel.common.types.OptionalType;
-import dev.cel.common.types.SimpleType;
-import dev.cel.common.types.TypeParamType;
-import dev.cel.common.types.TypeType;
-import dev.cel.common.values.CelByteString;
-import dev.cel.common.values.CelValue;
-import dev.cel.common.values.NullValue;
import dev.cel.compiler.CelCompilerLibrary;
import dev.cel.parser.CelMacro;
-import dev.cel.parser.CelMacroExprFactory;
import dev.cel.parser.CelParserBuilder;
-import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelInternalRuntimeLibrary;
import dev.cel.runtime.CelRuntimeBuilder;
import dev.cel.runtime.RuntimeEquality;
-import java.time.Instant;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.Set;
/** Internal implementation of CEL optional values. */
+@Immutable
public final class CelOptionalLibrary
implements CelCompilerLibrary, CelInternalRuntimeLibrary, CelExtensionLibrary.FeatureSet {
/** 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_UNWRAP("optional.unwrap"),
- OPTIONAL_OF_NON_ZERO_VALUE("optional.ofNonZeroValue"),
- OR("or"),
- OR_VALUE("orValue"),
- FIRST("first"),
- LAST("last");
+ VALUE(CelOptionalCompilerLibrary.Function.VALUE, CelOptionalRuntimeLibrary.Function.VALUE),
+ HAS_VALUE(
+ CelOptionalCompilerLibrary.Function.HAS_VALUE,
+ CelOptionalRuntimeLibrary.Function.HAS_VALUE),
+ OPTIONAL_NONE(
+ CelOptionalCompilerLibrary.Function.OPTIONAL_NONE,
+ CelOptionalRuntimeLibrary.Function.OPTIONAL_NONE),
+ OPTIONAL_OF(
+ CelOptionalCompilerLibrary.Function.OPTIONAL_OF,
+ CelOptionalRuntimeLibrary.Function.OPTIONAL_OF),
+ OPTIONAL_UNWRAP(
+ CelOptionalCompilerLibrary.Function.OPTIONAL_UNWRAP,
+ CelOptionalRuntimeLibrary.Function.OPTIONAL_UNWRAP),
+ OPTIONAL_OF_NON_ZERO_VALUE(
+ CelOptionalCompilerLibrary.Function.OPTIONAL_OF_NON_ZERO_VALUE,
+ CelOptionalRuntimeLibrary.Function.OPTIONAL_OF_NON_ZERO_VALUE),
+ OR(CelOptionalCompilerLibrary.Function.OR, CelOptionalRuntimeLibrary.Function.OR),
+ OR_VALUE(
+ CelOptionalCompilerLibrary.Function.OR_VALUE, CelOptionalRuntimeLibrary.Function.OR_VALUE),
+ FIRST(CelOptionalCompilerLibrary.Function.FIRST, CelOptionalRuntimeLibrary.Function.FIRST),
+ LAST(CelOptionalCompilerLibrary.Function.LAST, CelOptionalRuntimeLibrary.Function.LAST);
- private final String functionName;
+ private final CelOptionalCompilerLibrary.Function compilerFunction;
+ private final CelOptionalRuntimeLibrary.Function runtimeFunction;
public String getFunction() {
- return functionName;
+ return compilerFunction.getFunction();
}
- Function(String functionName) {
- this.functionName = functionName;
+ Function(
+ CelOptionalCompilerLibrary.Function compilerFunction,
+ CelOptionalRuntimeLibrary.Function runtimeFunction) {
+ this.compilerFunction = compilerFunction;
+ this.runtimeFunction = runtimeFunction;
}
}
- private static final class Types {
- private static final TypeParamType PARAM_TYPE_K = TypeParamType.create("K");
- private static final TypeParamType PARAM_TYPE_V = TypeParamType.create("V");
- private static final OptionalType OPTIONAL_TYPE_V = OptionalType.create(PARAM_TYPE_V);
- private static final ListType LIST_TYPE_V = ListType.create(PARAM_TYPE_V);
- private static final MapType MAP_TYPE_KV = MapType.create(PARAM_TYPE_K, PARAM_TYPE_V);
- }
-
/** Declarations for the optional extension library. */
public enum OptionalDeclaration implements CelFunctionDecl.Declarer {
- OPTIONAL_OF(
- CelFunctionDecl.newFunctionDeclaration(
- Function.OPTIONAL_OF.getFunction(),
- CelOverloadDecl.newGlobalOverload(
- "optional_of", Types.OPTIONAL_TYPE_V, Types.PARAM_TYPE_V))),
+ OPTIONAL_OF(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_OF),
OPTIONAL_OF_NON_ZERO_VALUE(
- CelFunctionDecl.newFunctionDeclaration(
- Function.OPTIONAL_OF_NON_ZERO_VALUE.getFunction(),
- CelOverloadDecl.newGlobalOverload(
- "optional_ofNonZeroValue", Types.OPTIONAL_TYPE_V, Types.PARAM_TYPE_V))),
- OPTIONAL_NONE(
- CelFunctionDecl.newFunctionDeclaration(
- Function.OPTIONAL_NONE.getFunction(),
- CelOverloadDecl.newGlobalOverload("optional_none", Types.OPTIONAL_TYPE_V))),
- OPTIONAL_VALUE(
- CelFunctionDecl.newFunctionDeclaration(
- Function.VALUE.getFunction(),
- CelOverloadDecl.newMemberOverload(
- "optional_value", Types.PARAM_TYPE_V, Types.OPTIONAL_TYPE_V))),
- OPTIONAL_HAS_VALUE(
- CelFunctionDecl.newFunctionDeclaration(
- Function.HAS_VALUE.getFunction(),
- CelOverloadDecl.newMemberOverload(
- "optional_hasValue", SimpleType.BOOL, Types.OPTIONAL_TYPE_V))),
- OPTIONAL_UNWRAP(
- CelFunctionDecl.newFunctionDeclaration(
- Function.OPTIONAL_UNWRAP.getFunction(),
- CelOverloadDecl.newGlobalOverload(
- "optional_unwrap_list",
- Types.LIST_TYPE_V,
- ListType.create(Types.OPTIONAL_TYPE_V)))),
- OPTIONAL_OR(
- CelFunctionDecl.newFunctionDeclaration(
- "or",
- CelOverloadDecl.newMemberOverload(
- "optional_or_optional",
- Types.OPTIONAL_TYPE_V,
- Types.OPTIONAL_TYPE_V,
- Types.OPTIONAL_TYPE_V))),
- OPTIONAL_OR_VALUE(
- CelFunctionDecl.newFunctionDeclaration(
- "orValue",
- CelOverloadDecl.newMemberOverload(
- "optional_orValue_value",
- Types.PARAM_TYPE_V,
- Types.OPTIONAL_TYPE_V,
- Types.PARAM_TYPE_V))),
- OPTIONAL_SELECT(
- CelFunctionDecl.newFunctionDeclaration(
- Operator.OPTIONAL_SELECT.getFunction(),
- CelOverloadDecl.newGlobalOverload(
- "select_optional_field",
- Types.OPTIONAL_TYPE_V,
- SimpleType.DYN,
- SimpleType.STRING))),
- OPTIONAL_INDEX(
- CelFunctionDecl.newFunctionDeclaration(
- Operator.OPTIONAL_INDEX.getFunction(),
- CelOverloadDecl.newGlobalOverload(
- "list_optindex_optional_int",
- Types.OPTIONAL_TYPE_V,
- Types.LIST_TYPE_V,
- SimpleType.INT),
- CelOverloadDecl.newGlobalOverload(
- "optional_list_optindex_optional_int",
- Types.OPTIONAL_TYPE_V,
- OptionalType.create(Types.LIST_TYPE_V),
- SimpleType.INT),
- CelOverloadDecl.newGlobalOverload(
- "map_optindex_optional_value",
- Types.OPTIONAL_TYPE_V,
- Types.MAP_TYPE_KV,
- Types.PARAM_TYPE_K),
- CelOverloadDecl.newGlobalOverload(
- "optional_map_optindex_optional_value",
- Types.OPTIONAL_TYPE_V,
- OptionalType.create(Types.MAP_TYPE_KV),
- Types.PARAM_TYPE_K))),
- OPTIONAL_INDEX_OPERAND(
- CelFunctionDecl.newFunctionDeclaration(
- Operator.INDEX.getFunction(),
- CelOverloadDecl.newGlobalOverload(
- "optional_list_index_int",
- Types.OPTIONAL_TYPE_V,
- OptionalType.create(Types.LIST_TYPE_V),
- SimpleType.INT),
- CelOverloadDecl.newGlobalOverload(
- "optional_map_index_value",
- Types.OPTIONAL_TYPE_V,
- OptionalType.create(Types.MAP_TYPE_KV),
- Types.PARAM_TYPE_K)));
+ CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_OF_NON_ZERO_VALUE),
+ OPTIONAL_NONE(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_NONE),
+ OPTIONAL_VALUE(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_VALUE),
+ OPTIONAL_HAS_VALUE(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_HAS_VALUE),
+ OPTIONAL_UNWRAP(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_UNWRAP),
+ OPTIONAL_OR(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_OR),
+ OPTIONAL_OR_VALUE(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_OR_VALUE),
+ OPTIONAL_SELECT(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_SELECT),
+ OPTIONAL_INDEX(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_INDEX),
+ OPTIONAL_INDEX_OPERAND(CelOptionalCompilerLibrary.OptionalDeclaration.OPTIONAL_INDEX_OPERAND);
- private final CelFunctionDecl celFunctionDecl;
+ private final CelOptionalCompilerLibrary.OptionalDeclaration declaration;
- OptionalDeclaration(CelFunctionDecl celFunctionDecl) {
- this.celFunctionDecl = celFunctionDecl;
+ OptionalDeclaration(CelOptionalCompilerLibrary.OptionalDeclaration declaration) {
+ this.declaration = declaration;
}
@Override
public CelFunctionDecl functionDecl() {
- return celFunctionDecl;
+ return declaration.functionDecl();
}
}
- private static final CelExtensionLibrary LIBRARY =
- new CelExtensionLibrary() {
- private final CelOptionalLibrary version0 =
- new CelOptionalLibrary(
- 0,
- ImmutableSet.of(
- OptionalDeclaration.OPTIONAL_OF.functionDecl(),
- OptionalDeclaration.OPTIONAL_OF_NON_ZERO_VALUE.functionDecl(),
- OptionalDeclaration.OPTIONAL_NONE.functionDecl(),
- OptionalDeclaration.OPTIONAL_VALUE.functionDecl(),
- OptionalDeclaration.OPTIONAL_HAS_VALUE.functionDecl(),
- OptionalDeclaration.OPTIONAL_UNWRAP.functionDecl(),
- // Note: Implementation of "or" and "orValue" are special-cased inside the
- // interpreter. Hence, their bindings are not provided here.
- OptionalDeclaration.OPTIONAL_OR.functionDecl(),
- OptionalDeclaration.OPTIONAL_OR_VALUE.functionDecl(),
- // Note: Function bindings for optional field selection and indexer is defined
- // in {@code StandardFunctions}.
- OptionalDeclaration.OPTIONAL_SELECT.functionDecl(),
- OptionalDeclaration.OPTIONAL_INDEX.functionDecl(),
- // Index overloads to accommodate using an optional value as the operand
- OptionalDeclaration.OPTIONAL_INDEX_OPERAND.functionDecl()),
- ImmutableSet.of(
- CelMacro.newReceiverMacro("optMap", 2, CelOptionalLibrary::expandOptMap)),
- ImmutableSet.of(
- // Type declaration for optional_type -> type(optional_type(V))
- CelVarDecl.newVarDeclaration(
- OptionalType.NAME, TypeType.create(Types.OPTIONAL_TYPE_V))));
+ private static final class Library implements CelExtensionLibrary {
+ private final ImmutableSet versions;
- private final CelOptionalLibrary version1 =
- new CelOptionalLibrary(
- 1,
- version0.functions,
- ImmutableSet.builder()
- .addAll(version0.macros)
- .add(
- CelMacro.newReceiverMacro(
- "optFlatMap", 2, CelOptionalLibrary::expandOptFlatMap))
- .build(),
- version0.variables);
+ Library() {
+ versions =
+ CelOptionalCompilerLibrary.library().versions().stream()
+ .map(CelOptionalLibrary::new)
+ .collect(toImmutableSet());
+ }
- private final CelOptionalLibrary version2 =
- new CelOptionalLibrary(
- 2,
- ImmutableSet.builder()
- .addAll(version1.functions)
- .add(
- CelFunctionDecl.newFunctionDeclaration(
- FIRST.functionName,
- CelOverloadDecl.newMemberOverload(
- "optional_list_first",
- "Return the first value in a list if present, otherwise"
- + " optional.none()",
- Types.OPTIONAL_TYPE_V,
- Types.LIST_TYPE_V)),
- CelFunctionDecl.newFunctionDeclaration(
- LAST.functionName,
- CelOverloadDecl.newMemberOverload(
- "optional_list_last",
- "Return the last value in a list if present, otherwise"
- + " optional.none()",
- Types.OPTIONAL_TYPE_V,
- Types.LIST_TYPE_V)))
- .build(),
- version1.macros,
- version1.variables);
+ @Override
+ public String name() {
+ return CelOptionalCompilerLibrary.library().name();
+ }
- @Override
- public String name() {
- return "optional";
- }
+ @Override
+ public ImmutableSet versions() {
+ return versions;
+ }
+ }
- @Override
- public ImmutableSet versions() {
- return ImmutableSet.of(version0, version1, version2);
- }
- };
+ private static final Library LIBRARY = new Library();
static CelExtensionLibrary library() {
return LIBRARY;
@@ -296,60 +131,55 @@ static CelExtensionLibrary library() {
// TODO migrate from this constant to the CelExtensions.optional()
public static final CelOptionalLibrary INSTANCE = CelOptionalLibrary.library().latest();
- private static final String UNUSED_ITER_VAR = "#unused";
- private static final String OPTIONAL_MAP_VAR = "@target";
+ private final CelOptionalCompilerLibrary compilerLibrary;
+ private final CelOptionalRuntimeLibrary runtimeLibrary;
- private final int version;
- private final ImmutableSet functions;
- private final ImmutableSet macros;
- private final ImmutableSet variables;
+ CelOptionalLibrary() {
+ this(CelOptionalCompilerLibrary.optional());
+ }
- CelOptionalLibrary(
- int version,
- ImmutableSet functions,
- ImmutableSet macros,
- ImmutableSet variables) {
- this.version = version;
- this.functions = functions;
- this.macros = macros;
- this.variables = variables;
+ CelOptionalLibrary(Set functions) {
+ this.compilerLibrary =
+ new CelOptionalCompilerLibrary(
+ functions.stream().map(f -> f.compilerFunction).collect(toImmutableSet()));
+ this.runtimeLibrary =
+ new CelOptionalRuntimeLibrary(
+ functions.stream().map(f -> f.runtimeFunction).collect(toImmutableSet()));
+ }
+
+ private CelOptionalLibrary(CelOptionalCompilerLibrary compilerLibrary) {
+ this.compilerLibrary = compilerLibrary;
+ this.runtimeLibrary = CelOptionalRuntimeLibrary.optional(compilerLibrary.version());
}
@Override
public int version() {
- return version;
+ return compilerLibrary.version();
}
@Override
public ImmutableSet functions() {
- return functions;
+ return compilerLibrary.functions();
}
@Override
public ImmutableSet variables() {
- return variables;
+ return compilerLibrary.variables();
}
@Override
public ImmutableSet macros() {
- return macros;
+ return compilerLibrary.macros();
}
@Override
public void setParserOptions(CelParserBuilder parserBuilder) {
- if (!parserBuilder.getOptions().enableOptionalSyntax()) {
- // Enable optional syntax by default if the optional library has been added to the
- // environment
- parserBuilder.setOptions(
- parserBuilder.getOptions().toBuilder().enableOptionalSyntax(true).build());
- }
- parserBuilder.addMacros(macros());
+ compilerLibrary.setParserOptions(parserBuilder);
}
@Override
public void setCheckerOptions(CelCheckerBuilder checkerBuilder) {
- checkerBuilder.addVarDeclarations(variables());
- checkerBuilder.addFunctionDeclarations(functions());
+ compilerLibrary.setCheckerOptions(checkerBuilder);
}
@Override
@@ -357,316 +187,9 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) {
throw new UnsupportedOperationException("Unsupported");
}
- @SuppressWarnings({"unchecked", "rawtypes"})
@Override
public void setRuntimeOptions(
CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions) {
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- OPTIONAL_OF.getFunction(),
- CelFunctionBinding.from("optional_of", Object.class, Optional::of)));
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- OPTIONAL_OF_NON_ZERO_VALUE.getFunction(),
- CelFunctionBinding.from(
- "optional_ofNonZeroValue",
- Object.class,
- val -> {
- if (isZeroValue(val)) {
- return Optional.empty();
- }
- return Optional.of(val);
- })));
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- OPTIONAL_UNWRAP.getFunction(),
- CelFunctionBinding.from(
- "optional_unwrap_list",
- Collection.class,
- CelOptionalLibrary::elideOptionalCollection)));
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- OPTIONAL_NONE.getFunction(),
- CelFunctionBinding.from("optional_none", ImmutableList.of(), val -> Optional.empty())));
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- VALUE.getFunction(),
- CelFunctionBinding.from(
- "optional_value", Object.class, val -> ((Optional>) val).get())));
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- HAS_VALUE.getFunction(),
- CelFunctionBinding.from(
- "optional_hasValue", Object.class, val -> ((Optional>) val).isPresent())));
-
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- OPTIONAL_SELECT.getFunction(),
- CelFunctionBinding.from(
- "select_optional_field", // This only handles map selection. Proto selection is
- // special cased inside the interpreter.
- Map.class,
- String.class,
- runtimeEquality::findInMap)));
-
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- OPTIONAL_INDEX.getFunction(),
- CelFunctionBinding.from(
- "list_optindex_optional_int",
- List.class,
- Long.class,
- (List list, Long index) -> {
- int castIndex = Ints.checkedCast(index);
- if (castIndex < 0 || castIndex >= list.size()) {
- return Optional.empty();
- }
- return Optional.of(list.get(castIndex));
- }),
- CelFunctionBinding.from(
- "optional_list_optindex_optional_int",
- Optional.class,
- Long.class,
- CelOptionalLibrary::indexOptionalList),
- CelFunctionBinding.from(
- "map_optindex_optional_value", Map.class, Object.class, runtimeEquality::findInMap),
- CelFunctionBinding.from(
- "optional_map_optindex_optional_value",
- Optional.class,
- Object.class,
- (Optional optionalMap, Object key) ->
- indexOptionalMap(optionalMap, key, runtimeEquality))));
-
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- INDEX.getFunction(),
- CelFunctionBinding.from(
- "optional_list_index_int",
- Optional.class,
- Long.class,
- CelOptionalLibrary::indexOptionalList),
- CelFunctionBinding.from(
- "optional_map_index_value",
- Optional.class,
- Object.class,
- (Optional optionalMap, Object key) ->
- indexOptionalMap(optionalMap, key, runtimeEquality))));
-
- if (version >= 2) {
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- "first",
- CelFunctionBinding.from(
- "optional_list_first", Collection.class, CelOptionalLibrary::listOptionalFirst)));
- runtimeBuilder.addFunctionBindings(
- fromOverloads(
- "last",
- CelFunctionBinding.from(
- "optional_list_last", Collection.class, CelOptionalLibrary::listOptionalLast)));
- }
- }
-
- private static ImmutableList