From a22588899392af5a375592fa17301f214b80df0e Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 21 Oct 2025 17:15:12 -0700 Subject: [PATCH 001/100] No public description PiperOrigin-RevId: 822336866 --- .../main/java/dev/cel/common/internal/CelCodePointArray.java | 1 - common/src/main/java/dev/cel/common/internal/Constants.java | 1 - common/src/main/java/dev/cel/common/internal/Converter.java | 1 - common/src/main/java/dev/cel/common/types/EnumType.java | 2 -- common/src/main/java/dev/cel/common/types/StructType.java | 1 - .../src/main/java/dev/cel/runtime/CelEvaluationListener.java | 1 - runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java | 1 - runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java | 2 -- runtime/src/main/java/dev/cel/runtime/FunctionOverload.java | 1 - runtime/src/main/java/dev/cel/runtime/GlobalResolver.java | 1 - runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java | 2 -- .../main/java/dev/cel/runtime/standard/CelStandardOverload.java | 1 - .../main/java/dev/cel/runtime/standard/TimestampFunction.java | 1 - 13 files changed, 16 deletions(-) diff --git a/common/src/main/java/dev/cel/common/internal/CelCodePointArray.java b/common/src/main/java/dev/cel/common/internal/CelCodePointArray.java index 2bc606981..1f3124c93 100644 --- a/common/src/main/java/dev/cel/common/internal/CelCodePointArray.java +++ b/common/src/main/java/dev/cel/common/internal/CelCodePointArray.java @@ -57,7 +57,6 @@ public boolean isEmpty() { @Override public abstract String toString(); - @SuppressWarnings("AndroidJdkLibsChecker") // PrimitiveIterator added in 23 public static CelCodePointArray fromString(String text) { if (isNullOrEmpty(text)) { return EmptyCodePointArray.INSTANCE; diff --git a/common/src/main/java/dev/cel/common/internal/Constants.java b/common/src/main/java/dev/cel/common/internal/Constants.java index 7a9ffa1ec..d2c0719ec 100644 --- a/common/src/main/java/dev/cel/common/internal/Constants.java +++ b/common/src/main/java/dev/cel/common/internal/Constants.java @@ -32,7 +32,6 @@ *

CEL Library Internals. Do Not Use. */ @Internal -@SuppressWarnings("AndroidJdkLibsChecker") // PrimitiveIterator added in 23 public final class Constants { private static final String DOUBLE_QUOTE = "\""; diff --git a/common/src/main/java/dev/cel/common/internal/Converter.java b/common/src/main/java/dev/cel/common/internal/Converter.java index 2437d3093..2c52d0219 100644 --- a/common/src/main/java/dev/cel/common/internal/Converter.java +++ b/common/src/main/java/dev/cel/common/internal/Converter.java @@ -21,7 +21,6 @@ * *

CEL Library Internals. Do Not Use. */ -@SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @FunctionalInterface @Internal public interface Converter { diff --git a/common/src/main/java/dev/cel/common/types/EnumType.java b/common/src/main/java/dev/cel/common/types/EnumType.java index 076ebe527..836b68a6c 100644 --- a/common/src/main/java/dev/cel/common/types/EnumType.java +++ b/common/src/main/java/dev/cel/common/types/EnumType.java @@ -62,7 +62,6 @@ public Optional findNameByNumber(Integer enumNumber) { /** Functional interface for lookup up an enum number by its local or fully qualified name. */ @Immutable @FunctionalInterface - @SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 public interface EnumNumberResolver { Optional findNumber(String enumName); } @@ -70,7 +69,6 @@ public interface EnumNumberResolver { /** Functional interface for looking up an enum name by its number. */ @Immutable @FunctionalInterface - @SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 public interface EnumNameResolver { Optional findName(Integer enumNumber); } diff --git a/common/src/main/java/dev/cel/common/types/StructType.java b/common/src/main/java/dev/cel/common/types/StructType.java index ba4251559..b91b6fef0 100644 --- a/common/src/main/java/dev/cel/common/types/StructType.java +++ b/common/src/main/java/dev/cel/common/types/StructType.java @@ -99,7 +99,6 @@ public static StructType create( */ @Immutable @FunctionalInterface - @SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 public interface FieldResolver { /** Find the {@code CelType} for the given {@code fieldName} if the field is defined. */ Optional findField(String fieldName); diff --git a/runtime/src/main/java/dev/cel/runtime/CelEvaluationListener.java b/runtime/src/main/java/dev/cel/runtime/CelEvaluationListener.java index 1cb9e8d97..0afc1f5e2 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelEvaluationListener.java +++ b/runtime/src/main/java/dev/cel/runtime/CelEvaluationListener.java @@ -21,7 +21,6 @@ * Functional interface for a callback method invoked by the runtime. Implementations must ensure * that its instances are unconditionally thread-safe. */ -@SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @FunctionalInterface @ThreadSafe public interface CelEvaluationListener { diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java index f9b98df5b..f924c7d62 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java @@ -17,7 +17,6 @@ import com.google.errorprone.annotations.Immutable; /** Interface describing the general signature of all CEL custom function implementations. */ -@SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @Immutable @FunctionalInterface public interface CelFunctionOverload extends FunctionOverload { diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 578f428ee..299dd7a89 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -846,7 +846,6 @@ public CelStandardFunctions build() { * Functional interface for filtering standard functions. Returning true in the callback will * include the function in the environment. */ - @SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @FunctionalInterface public interface FunctionFilter { boolean include(StandardFunction standardFunction, StandardOverload standardOverload); @@ -858,7 +857,6 @@ public static Builder newBuilder() { return new Builder(); } - @SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @FunctionalInterface @Immutable private interface FunctionBindingCreator { diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java index bd9a8054c..bf825f004 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java @@ -17,7 +17,6 @@ import com.google.errorprone.annotations.Immutable; /** Interface describing the general signature of all CEL custom function implementations. */ -@SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @FunctionalInterface @Immutable interface FunctionOverload { diff --git a/runtime/src/main/java/dev/cel/runtime/GlobalResolver.java b/runtime/src/main/java/dev/cel/runtime/GlobalResolver.java index ce8dc8b3f..ffc1933b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/GlobalResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/GlobalResolver.java @@ -23,7 +23,6 @@ * *

CEL Library Internals. Do Not Use. */ -@SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @FunctionalInterface @Internal public interface GlobalResolver { diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 0e02af273..25f01ef59 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -56,14 +56,12 @@ public static RuntimeHelpers create() { // ========= /** Convert a string to a Protobuf Duration. */ - @SuppressWarnings("AndroidJdkLibsChecker") // DateTimeParseException added in 26 public static Duration createDurationFromString(String d) { java.time.Duration dv = createJavaDurationFromString(d); return Duration.newBuilder().setSeconds(dv.getSeconds()).setNanos(dv.getNano()).build(); } /** Convert a string to a native Java Duration. */ - @SuppressWarnings("AndroidJdkLibsChecker") // DateTimeParseException added in 26 public static java.time.Duration createJavaDurationFromString(String d) { try { java.time.Duration dv = AmountFormats.parseUnitBasedDuration(d); diff --git a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java index 25555c13e..2b4e385db 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java @@ -29,7 +29,6 @@ interface CelStandardOverload { CelFunctionBinding newFunctionBinding(CelOptions celOptions, RuntimeEquality runtimeEquality); - @SuppressWarnings("AndroidJdkLibsChecker") // FunctionalInterface added in 24 @FunctionalInterface @Immutable interface FunctionBindingCreator { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 8ed250143..76d2e5b84 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -45,7 +45,6 @@ public static TimestampFunction create(Iterable From 4aeba253fb71f56c695e3a0343c1ec2205ff7e40 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 22 Oct 2025 15:04:40 -0700 Subject: [PATCH 002/100] Expose LiteralValidator PiperOrigin-RevId: 822754074 --- .../src/main/java/dev/cel/validator/validators/BUILD.bazel | 3 ++- .../java/dev/cel/validator/validators/LiteralValidator.java | 2 +- validator/validators/BUILD.bazel | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel b/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel index 67e791317..d24cbd20c 100644 --- a/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel @@ -106,7 +106,8 @@ java_library( srcs = [ "LiteralValidator.java", ], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ "//bundle:cel", "//common:cel_ast", diff --git a/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java b/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java index 2f23dab4c..e16b9369e 100644 --- a/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java +++ b/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java @@ -32,7 +32,7 @@ * call by evaluating it and ensuring that no errors are thrown (example: duration / timestamp * literals). */ -abstract class LiteralValidator implements CelAstValidator { +public abstract class LiteralValidator implements CelAstValidator { private final String functionName; private final Class expectedResultType; diff --git a/validator/validators/BUILD.bazel b/validator/validators/BUILD.bazel index ab0919f20..8054ae092 100644 --- a/validator/validators/BUILD.bazel +++ b/validator/validators/BUILD.bazel @@ -34,3 +34,8 @@ java_library( name = "comprehension_nesting_limit_validator", exports = ["//validator/src/main/java/dev/cel/validator/validators:comprehension_nesting_limit_validator"], ) + +java_library( + name = "literal_validator", + exports = ["//validator/src/main/java/dev/cel/validator/validators:literal_validator"], +) From 2e6da1e4d1603900d4693648d4729bf7c7685145 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 28 Oct 2025 17:07:18 -0700 Subject: [PATCH 003/100] Copy dev.cel.parser.Operator enum into dev.cel.common package This enum is being promoted into common package, as it's already in use across multiple packages. PiperOrigin-RevId: 825268198 --- .../src/main/java/dev/cel/checker/BUILD.bazel | 4 +- .../cel/checker/CelStandardDeclarations.java | 2 +- .../java/dev/cel/checker/ExprChecker.java | 2 +- .../src/test/java/dev/cel/checker/BUILD.bazel | 2 +- .../cel/checker/CelProtoExprVisitorTest.java | 2 +- common/BUILD.bazel | 5 + .../src/main/java/dev/cel/common/BUILD.bazel | 11 + .../main/java/dev/cel/common/Operator.java | 218 ++++++++++++++++++ .../test/java/dev/cel/common/ast/BUILD.bazel | 2 +- .../cel/common/ast/CelExprVisitorTest.java | 2 +- .../dev/cel/common/navigation/BUILD.bazel | 2 +- .../CelNavigableExprVisitorTest.java | 2 +- .../main/java/dev/cel/extensions/BUILD.bazel | 6 +- .../CelComprehensionsExtensions.java | 2 +- .../cel/extensions/CelListsExtensions.java | 68 +++--- .../cel/extensions/CelOptionalLibrary.java | 2 +- .../dev/cel/optimizer/optimizers/BUILD.bazel | 4 +- .../optimizers/ConstantFoldingOptimizer.java | 2 +- .../optimizers/DefaultOptimizerConstants.java | 2 +- .../test/java/dev/cel/optimizer/BUILD.bazel | 2 - .../dev/cel/optimizer/optimizers/BUILD.bazel | 1 - .../main/java/dev/cel/parser/Operator.java | 3 + .../src/main/java/dev/cel/policy/BUILD.bazel | 2 +- .../java/dev/cel/policy/RuleComposer.java | 2 +- .../src/main/java/dev/cel/runtime/BUILD.bazel | 2 +- .../dev/cel/runtime/CelAttributeParser.java | 2 +- 26 files changed, 292 insertions(+), 62 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/Operator.java diff --git a/checker/src/main/java/dev/cel/checker/BUILD.bazel b/checker/src/main/java/dev/cel/checker/BUILD.bazel index 5479934c4..6c486bd92 100644 --- a/checker/src/main/java/dev/cel/checker/BUILD.bazel +++ b/checker/src/main/java/dev/cel/checker/BUILD.bazel @@ -179,6 +179,7 @@ java_library( "//common:cel_ast", "//common:compiler_common", "//common:container", + "//common:operator", "//common:options", "//common:proto_ast", "//common/annotations", @@ -191,7 +192,6 @@ java_library( "//common/types:cel_types", "//common/types:type_providers", "//parser:macro", - "//parser:operator", "@cel_spec//proto/cel/expr:checked_java_proto", "@cel_spec//proto/cel/expr:syntax_java_proto", "@maven//:com_google_errorprone_error_prone_annotations", @@ -239,10 +239,10 @@ java_library( deps = [ ":cel_ident_decl", "//common:compiler_common", + "//common:operator", "//common/types", "//common/types:cel_types", "//common/types:type_providers", - "//parser:operator", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], diff --git a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java index 94b7ad313..12ad47c62 100644 --- a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java +++ b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java @@ -23,6 +23,7 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOverloadDecl; +import dev.cel.common.Operator; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypes; import dev.cel.common.types.ListType; @@ -30,7 +31,6 @@ import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeParamType; import dev.cel.common.types.TypeType; -import dev.cel.parser.Operator; /** * Standard declarations for CEL. diff --git a/checker/src/main/java/dev/cel/checker/ExprChecker.java b/checker/src/main/java/dev/cel/checker/ExprChecker.java index d4dbf7994..9ed121a20 100644 --- a/checker/src/main/java/dev/cel/checker/ExprChecker.java +++ b/checker/src/main/java/dev/cel/checker/ExprChecker.java @@ -31,6 +31,7 @@ import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelProtoAbstractSyntaxTree; +import dev.cel.common.Operator; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; @@ -44,7 +45,6 @@ import dev.cel.common.types.OptionalType; import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeType; -import dev.cel.parser.Operator; import java.util.ArrayList; import java.util.HashSet; import java.util.List; diff --git a/checker/src/test/java/dev/cel/checker/BUILD.bazel b/checker/src/test/java/dev/cel/checker/BUILD.bazel index 1b8ee9557..1821a5d85 100644 --- a/checker/src/test/java/dev/cel/checker/BUILD.bazel +++ b/checker/src/test/java/dev/cel/checker/BUILD.bazel @@ -27,6 +27,7 @@ java_library( "//common:compiler_common", "//common:container", "//common:mutable_ast", + "//common:operator", "//common:options", "//common:proto_ast", "//common:source_location", @@ -42,7 +43,6 @@ java_library( "//compiler", "//compiler:compiler_builder", "//parser:macro", - "//parser:operator", "//testing:adorner", "//testing:cel_baseline_test_case", "@maven//:junit_junit", diff --git a/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java b/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java index d9e311ca2..3cab304ac 100644 --- a/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java +++ b/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java @@ -22,12 +22,12 @@ import com.google.auto.value.AutoValue; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; +import dev.cel.common.Operator; import dev.cel.common.types.SimpleType; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.parser.CelStandardMacro; -import dev.cel.parser.Operator; import java.util.Optional; import org.junit.Before; import org.junit.Test; diff --git a/common/BUILD.bazel b/common/BUILD.bazel index f86ea04f9..815d8a1da 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -121,3 +121,8 @@ java_library( ], exports = ["//common/src/main/java/dev/cel/common:cel_descriptor_util"], ) + +java_library( + name = "operator", + exports = ["//common/src/main/java/dev/cel/common:operator"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index ce6963206..7cd10f392 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -354,3 +354,14 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_library( + name = "operator", + srcs = ["Operator.java"], + tags = [ + ], + deps = [ + "//common/ast", + "@maven//:com_google_guava_guava", + ], +) diff --git a/common/src/main/java/dev/cel/common/Operator.java b/common/src/main/java/dev/cel/common/Operator.java new file mode 100644 index 000000000..eaa414277 --- /dev/null +++ b/common/src/main/java/dev/cel/common/Operator.java @@ -0,0 +1,218 @@ +// Copyright 2025 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.common; + +import com.google.common.collect.ImmutableMap; +import dev.cel.common.ast.CelExpr; +import java.util.Objects; +import java.util.Optional; + +/** + * Package-private enumeration of Common Expression Language operators. + * + *

Equivalent to https://pkg.go.dev/github.com/google/cel-go/common/operators. + */ +public enum Operator { + CONDITIONAL("_?_:_"), + LOGICAL_AND("_&&_", "&&"), + LOGICAL_OR("_||_", "||"), + LOGICAL_NOT("!_", "!"), + EQUALS("_==_", "=="), + NOT_EQUALS("_!=_", "!="), + LESS("_<_", "<"), + LESS_EQUALS("_<=_", "<="), + GREATER("_>_", ">"), + GREATER_EQUALS("_>=_", ">="), + ADD("_+_", "+"), + SUBTRACT("_-_", "-"), + MULTIPLY("_*_", "*"), + DIVIDE("_/_", "/"), + MODULO("_%_", "%"), + NEGATE("-_", "-"), + INDEX("_[_]"), + HAS("has"), + ALL("all"), + EXISTS("exists"), + EXISTS_ONE("exists_one"), + MAP("map"), + FILTER("filter"), + NOT_STRICTLY_FALSE("@not_strictly_false"), + IN("@in", "in"), + OPTIONAL_INDEX("_[?_]"), + OPTIONAL_SELECT("_?._"), + @Deprecated // Prefer NOT_STRICTLY_FALSE. + OLD_NOT_STRICTLY_FALSE("__not_strictly_false__"), + @Deprecated // Prefer IN. + OLD_IN("_in_"); + + private final String functionName; + private final String displayName; + + Operator(String functionName) { + this(functionName, ""); + } + + Operator(String functionName, String displayName) { + this.functionName = functionName; + this.displayName = displayName; + } + + /** Returns the mangled operator name, as used within the AST. */ + public String getFunction() { + return functionName; + } + + /** Returns the unmangled operator name, as used within the source text of an expression. */ + String getSymbol() { + return displayName; + } + + private static final ImmutableMap OPERATORS = + ImmutableMap.builder() + .put(ADD.getSymbol(), ADD) + .put(DIVIDE.getSymbol(), DIVIDE) + .put(EQUALS.getSymbol(), EQUALS) + .put(GREATER.getSymbol(), GREATER) + .put(GREATER_EQUALS.getSymbol(), GREATER_EQUALS) + .put(IN.getSymbol(), IN) + .put(LESS.getSymbol(), LESS) + .put(LESS_EQUALS.getSymbol(), LESS_EQUALS) + .put(MODULO.getSymbol(), MODULO) + .put(MULTIPLY.getSymbol(), MULTIPLY) + .put(NOT_EQUALS.getSymbol(), NOT_EQUALS) + .put(SUBTRACT.getSymbol(), SUBTRACT) + .buildOrThrow(); + + /** Lookup an operator by its unmangled name, as used with the source text of an expression. */ + static Optional find(String text) { + return Optional.ofNullable(OPERATORS.get(text)); + } + + private static final ImmutableMap REVERSE_OPERATORS = + ImmutableMap.builder() + .put(ADD.getFunction(), ADD) + .put(ALL.getFunction(), ALL) + .put(CONDITIONAL.getFunction(), CONDITIONAL) + .put(DIVIDE.getFunction(), DIVIDE) + .put(EQUALS.getFunction(), EQUALS) + .put(EXISTS.getFunction(), EXISTS) + .put(EXISTS_ONE.getFunction(), EXISTS_ONE) + .put(FILTER.getFunction(), FILTER) + .put(GREATER.getFunction(), GREATER) + .put(GREATER_EQUALS.getFunction(), GREATER_EQUALS) + .put(HAS.getFunction(), HAS) + .put(IN.getFunction(), IN) + .put(INDEX.getFunction(), INDEX) + .put(LESS.getFunction(), LESS) + .put(LESS_EQUALS.getFunction(), LESS_EQUALS) + .put(LOGICAL_AND.getFunction(), LOGICAL_AND) + .put(LOGICAL_NOT.getFunction(), LOGICAL_NOT) + .put(LOGICAL_OR.getFunction(), LOGICAL_OR) + .put(MAP.getFunction(), MAP) + .put(MODULO.getFunction(), MODULO) + .put(MULTIPLY.getFunction(), MULTIPLY) + .put(NEGATE.getFunction(), NEGATE) + .put(NOT_EQUALS.getFunction(), NOT_EQUALS) + .put(NOT_STRICTLY_FALSE.getFunction(), NOT_STRICTLY_FALSE) + .put(OLD_IN.getFunction(), OLD_IN) + .put(OLD_NOT_STRICTLY_FALSE.getFunction(), OLD_NOT_STRICTLY_FALSE) + .put(OPTIONAL_INDEX.getFunction(), OPTIONAL_INDEX) + .put(OPTIONAL_SELECT.getFunction(), OPTIONAL_SELECT) + .put(SUBTRACT.getFunction(), SUBTRACT) + .buildOrThrow(); + + // precedence of the operator, where the higher value means higher. + private static final ImmutableMap PRECEDENCES = + ImmutableMap.builder() + .put(CONDITIONAL.getFunction(), 8) + .put(LOGICAL_OR.getFunction(), 7) + .put(LOGICAL_AND.getFunction(), 6) + .put(EQUALS.getFunction(), 5) + .put(GREATER.getFunction(), 5) + .put(GREATER_EQUALS.getFunction(), 5) + .put(IN.getFunction(), 5) + .put(LESS.getFunction(), 5) + .put(LESS_EQUALS.getFunction(), 5) + .put(NOT_EQUALS.getFunction(), 5) + .put(ADD.getFunction(), 4) + .put(SUBTRACT.getFunction(), 4) + .put(DIVIDE.getFunction(), 3) + .put(MODULO.getFunction(), 3) + .put(MULTIPLY.getFunction(), 3) + .put(LOGICAL_NOT.getFunction(), 2) + .put(NEGATE.getFunction(), 2) + .put(INDEX.getFunction(), 1) + .buildOrThrow(); + + private static final ImmutableMap UNARY_OPERATORS = + ImmutableMap.builder() + .put(NEGATE.getFunction(), "-") + .put(LOGICAL_NOT.getFunction(), "!") + .buildOrThrow(); + + private static final ImmutableMap BINARY_OPERATORS = + ImmutableMap.builder() + .put(LOGICAL_OR.getFunction(), "||") + .put(LOGICAL_AND.getFunction(), "&&") + .put(LESS_EQUALS.getFunction(), "<=") + .put(LESS.getFunction(), "<") + .put(GREATER_EQUALS.getFunction(), ">=") + .put(GREATER.getFunction(), ">") + .put(EQUALS.getFunction(), "==") + .put(NOT_EQUALS.getFunction(), "!=") + .put(IN.getFunction(), "in") + .put(ADD.getFunction(), "+") + .put(SUBTRACT.getFunction(), "-") + .put(MULTIPLY.getFunction(), "*") + .put(DIVIDE.getFunction(), "/") + .put(MODULO.getFunction(), "%") + .buildOrThrow(); + + /** Lookup an operator by its mangled name (ex: _&&_), as used within the AST. */ + public static Optional findReverse(String op) { + return Optional.ofNullable(REVERSE_OPERATORS.get(op)); + } + + /** Lookup a binary operator by its mangled name, as used within the AST. */ + static Optional findReverseBinaryOperator(String op) { + if (Objects.equals(op, LOGICAL_NOT.getFunction()) || Objects.equals(op, NEGATE.getFunction())) { + return Optional.empty(); + } + return Optional.ofNullable(REVERSE_OPERATORS.get(op)); + } + + static int lookupPrecedence(String op) { + return PRECEDENCES.getOrDefault(op, 0); + } + + static Optional lookupUnaryOperator(String op) { + return Optional.ofNullable(UNARY_OPERATORS.get(op)); + } + + static Optional lookupBinaryOperator(String op) { + return Optional.ofNullable(BINARY_OPERATORS.get(op)); + } + + static boolean isOperatorLowerPrecedence(String op, CelExpr expr) { + if (!expr.exprKind().getKind().equals(CelExpr.ExprKind.Kind.CALL)) { + return false; + } + return lookupPrecedence(op) < lookupPrecedence(expr.call().function()); + } + + static boolean isOperatorLeftRecursive(String op) { + return !op.equals(LOGICAL_AND.getFunction()) && !op.equals(LOGICAL_OR.getFunction()); + } +} diff --git a/common/src/test/java/dev/cel/common/ast/BUILD.bazel b/common/src/test/java/dev/cel/common/ast/BUILD.bazel index a2bb62719..19726aa21 100644 --- a/common/src/test/java/dev/cel/common/ast/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/ast/BUILD.bazel @@ -17,6 +17,7 @@ java_library( "//common:container", "//common:mutable_ast", "//common:mutable_source", + "//common:operator", "//common:options", "//common/ast", "//common/ast:cel_expr_visitor", @@ -31,7 +32,6 @@ java_library( "//compiler:compiler_builder", "//extensions:optional_library", "//parser:macro", - "//parser:operator", "@cel_spec//proto/cel/expr:checked_java_proto", "@cel_spec//proto/cel/expr:syntax_java_proto", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", diff --git a/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java b/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java index 7e75c9610..97f1a4a1b 100644 --- a/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java @@ -22,6 +22,7 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; +import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr.CelCall; import dev.cel.common.ast.CelExpr.CelComprehension; import dev.cel.common.ast.CelExpr.CelIdent; @@ -35,7 +36,6 @@ import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.parser.CelStandardMacro; -import dev.cel.parser.Operator; import java.util.Optional; import org.junit.Before; import org.junit.Test; diff --git a/common/src/test/java/dev/cel/common/navigation/BUILD.bazel b/common/src/test/java/dev/cel/common/navigation/BUILD.bazel index 172e8f5ea..74ec3e080 100644 --- a/common/src/test/java/dev/cel/common/navigation/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/navigation/BUILD.bazel @@ -13,6 +13,7 @@ java_library( "//common:compiler_common", "//common:container", "//common:mutable_ast", + "//common:operator", "//common:options", "//common/ast", "//common/ast:mutable_expr", @@ -23,7 +24,6 @@ java_library( "//compiler", "//compiler:compiler_builder", "//parser:macro", - "//parser:operator", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_testparameterinjector_test_parameter_injector", diff --git a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java index ed58a93cc..15c6ac620 100644 --- a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java +++ b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java @@ -28,6 +28,7 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; +import dev.cel.common.Operator; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; @@ -39,7 +40,6 @@ import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.parser.CelStandardMacro; -import dev.cel.parser.Operator; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel index c39ff2358..9b897cf84 100644 --- a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel @@ -177,6 +177,7 @@ java_library( deps = [ "//checker:checker_builder", "//common:compiler_common", + "//common:operator", "//common:options", "//common/ast", "//common/types", @@ -185,7 +186,6 @@ java_library( "//compiler:compiler_builder", "//extensions:extension_library", "//parser:macro", - "//parser:operator", "//parser:parser_builder", "//runtime", "//runtime:function_binding", @@ -262,6 +262,7 @@ java_library( deps = [ "//checker:checker_builder", "//common:compiler_common", + "//common:operator", "//common:options", "//common/ast", "//common/internal:comparison_functions", @@ -269,7 +270,6 @@ java_library( "//compiler:compiler_builder", "//extensions:extension_library", "//parser:macro", - "//parser:operator", "//parser:parser_builder", "//runtime", "//runtime:function_binding", @@ -301,13 +301,13 @@ java_library( deps = [ "//checker:checker_builder", "//common:compiler_common", + "//common:operator", "//common:options", "//common/ast", "//common/types", "//compiler:compiler_builder", "//extensions:extension_library", "//parser:macro", - "//parser:operator", "//parser:parser_builder", "//runtime", "//runtime:function_binding", diff --git a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java index 72f180db5..ddea801b6 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java @@ -25,6 +25,7 @@ import dev.cel.common.CelIssue; import dev.cel.common.CelOptions; import dev.cel.common.CelOverloadDecl; +import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; import dev.cel.common.types.MapType; import dev.cel.common.types.TypeParamType; @@ -32,7 +33,6 @@ import dev.cel.parser.CelMacro; import dev.cel.parser.CelMacroExprFactory; import dev.cel.parser.CelParserBuilder; -import dev.cel.parser.Operator; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelInternalRuntimeLibrary; import dev.cel.runtime.CelRuntimeBuilder; diff --git a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java index 56e0fb97b..385023da7 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java @@ -27,6 +27,7 @@ import dev.cel.common.CelIssue; import dev.cel.common.CelOptions; import dev.cel.common.CelOverloadDecl; +import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; import dev.cel.common.internal.ComparisonFunctions; import dev.cel.common.types.ListType; @@ -36,7 +37,6 @@ import dev.cel.parser.CelMacro; import dev.cel.parser.CelMacroExprFactory; import dev.cel.parser.CelParserBuilder; -import dev.cel.parser.Operator; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelInternalRuntimeLibrary; import dev.cel.runtime.CelRuntimeBuilder; @@ -118,27 +118,23 @@ public enum Function { "Returns the elements of a list in reverse order", ListType.create(TypeParamType.create("T")), ListType.create(TypeParamType.create("T")))), - CelFunctionBinding.from( - "list_reverse", - Collection.class, - CelListsExtensions::reverse)), - SORT( - CelFunctionDecl.newFunctionDeclaration( - "sort", - CelOverloadDecl.newMemberOverload( - "list_sort", - "Sorts a list with comparable elements.", - ListType.create(TypeParamType.create("T")), - ListType.create(TypeParamType.create("T"))))), - SORT_BY( - CelFunctionDecl.newFunctionDeclaration( - "lists.@sortByAssociatedKeys", - CelOverloadDecl.newGlobalOverload( - "list_sortByAssociatedKeys", - "Sorts a list by a key value. Used by the 'sortBy' macro", - ListType.create(TypeParamType.create("T")), - ListType.create(TypeParamType.create("T"))))) - ; + CelFunctionBinding.from("list_reverse", Collection.class, CelListsExtensions::reverse)), + SORT( + CelFunctionDecl.newFunctionDeclaration( + "sort", + CelOverloadDecl.newMemberOverload( + "list_sort", + "Sorts a list with comparable elements.", + ListType.create(TypeParamType.create("T")), + ListType.create(TypeParamType.create("T"))))), + SORT_BY( + CelFunctionDecl.newFunctionDeclaration( + "lists.@sortByAssociatedKeys", + CelOverloadDecl.newGlobalOverload( + "list_sortByAssociatedKeys", + "Sorts a list by a key value. Used by the 'sortBy' macro", + ListType.create(TypeParamType.create("T")), + ListType.create(TypeParamType.create("T"))))); private final CelFunctionDecl functionDecl; private final ImmutableSet functionBindings; @@ -240,8 +236,8 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { @SuppressWarnings("unchecked") @Override - public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, - CelOptions celOptions) { + public void setRuntimeOptions( + CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions) { for (Function function : functions) { runtimeBuilder.addFunctionBindings(function.functionBindings); for (CelOverloadDecl overload : function.functionDecl.overloads()) { @@ -412,23 +408,23 @@ private static Optional sortByMacro( // Wrap the pair in another list in order to be able to use the `list+list` operator step = exprFactory.newList(step); // Append the key-value pair to the i - step = exprFactory.newGlobalCall( + step = + exprFactory.newGlobalCall( Operator.ADD.getFunction(), exprFactory.newIdentifier(exprFactory.getAccumulatorVarName()), step); // Create an intermediate list and populate it with key-value pairs - step = exprFactory.fold( - varName, - target, - exprFactory.getAccumulatorVarName(), - exprFactory.newList(), - exprFactory.newBoolLiteral(true), // Include all elements - step, - exprFactory.newIdentifier(exprFactory.getAccumulatorVarName())); + step = + exprFactory.fold( + varName, + target, + exprFactory.getAccumulatorVarName(), + exprFactory.newList(), + exprFactory.newBoolLiteral(true), // Include all elements + step, + exprFactory.newIdentifier(exprFactory.getAccumulatorVarName())); // Finally, sort the list of key-value pairs and map it to a list of values - step = exprFactory.newGlobalCall( - Function.SORT_BY.getFunction(), - step); + step = exprFactory.newGlobalCall(Function.SORT_BY.getFunction(), step); return Optional.of(step); } diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index 6ed4d1491..bcce0453b 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -32,6 +32,7 @@ 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; @@ -45,7 +46,6 @@ import dev.cel.parser.CelMacro; import dev.cel.parser.CelMacroExprFactory; import dev.cel.parser.CelParserBuilder; -import dev.cel.parser.Operator; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelInternalRuntimeLibrary; import dev.cel.runtime.CelRuntimeBuilder; 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 6057b3105..d68842315 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -24,6 +24,7 @@ java_library( "//common:cel_source", "//common:compiler_common", "//common:mutable_ast", + "//common:operator", "//common/ast", "//common/ast:mutable_expr", "//common/internal:date_time_helpers", @@ -33,7 +34,6 @@ java_library( "//optimizer:ast_optimizer", "//optimizer:mutable_ast", "//optimizer:optimization_exception", - "//parser:operator", "//runtime", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -79,9 +79,9 @@ java_library( visibility = ["//visibility:private"], deps = [ "//checker:standard_decl", + "//common:operator", "//extensions", "//extensions:optional_library", - "//parser:operator", "@maven//:com_google_guava_guava", ], ) 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 294589af2..9d61fb19c 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -28,6 +28,7 @@ import dev.cel.common.CelMutableAst; import dev.cel.common.CelSource; import dev.cel.common.CelValidationException; +import dev.cel.common.Operator; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.ExprKind.Kind; @@ -46,7 +47,6 @@ import dev.cel.optimizer.AstMutator; import dev.cel.optimizer.CelAstOptimizer; import dev.cel.optimizer.CelOptimizationException; -import dev.cel.parser.Operator; import dev.cel.runtime.CelEvaluationException; import java.time.Duration; import java.time.Instant; diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/DefaultOptimizerConstants.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/DefaultOptimizerConstants.java index 35608a776..a9db48391 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/DefaultOptimizerConstants.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/DefaultOptimizerConstants.java @@ -20,10 +20,10 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Streams; import dev.cel.checker.CelStandardDeclarations; +import dev.cel.common.Operator; import dev.cel.extensions.CelExtensions; import dev.cel.extensions.CelOptionalLibrary; import dev.cel.extensions.CelOptionalLibrary.Function; -import dev.cel.parser.Operator; /** * Package-private class that holds constants that's generally applicable across canonical diff --git a/optimizer/src/test/java/dev/cel/optimizer/BUILD.bazel b/optimizer/src/test/java/dev/cel/optimizer/BUILD.bazel index d81fba29f..748e7ee89 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/BUILD.bazel +++ b/optimizer/src/test/java/dev/cel/optimizer/BUILD.bazel @@ -19,7 +19,6 @@ java_library( "//common/ast", "//common/ast:mutable_expr", "//common/navigation", - "//common/navigation:mutable_navigation", "//common/types", "//compiler", "//extensions", @@ -31,7 +30,6 @@ java_library( "//optimizer:optimizer_builder", "//optimizer:optimizer_impl", "//parser:macro", - "//parser:operator", "//parser:parser_factory", "//parser:unparser", "//runtime", diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel index 8def5de7a..acc8a4c3f 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -28,7 +28,6 @@ java_library( "//optimizer/optimizers:common_subexpression_elimination", "//optimizer/optimizers:constant_folding", "//parser:macro", - "//parser:operator", "//parser:unparser", "//runtime", "//runtime:function_binding", diff --git a/parser/src/main/java/dev/cel/parser/Operator.java b/parser/src/main/java/dev/cel/parser/Operator.java index 25bd2ca5b..6135350e3 100644 --- a/parser/src/main/java/dev/cel/parser/Operator.java +++ b/parser/src/main/java/dev/cel/parser/Operator.java @@ -23,7 +23,10 @@ * Package-private enumeration of Common Expression Language operators. * *

Equivalent to https://pkg.go.dev/github.com/google/cel-go/common/operators. + * + * @deprecated Use {@code dev.cel.common.Operator} instead. */ +@Deprecated public enum Operator { CONDITIONAL("_?_:_"), LOGICAL_AND("_&&_", "&&"), diff --git a/policy/src/main/java/dev/cel/policy/BUILD.bazel b/policy/src/main/java/dev/cel/policy/BUILD.bazel index ee35e8271..4fa1baa78 100644 --- a/policy/src/main/java/dev/cel/policy/BUILD.bazel +++ b/policy/src/main/java/dev/cel/policy/BUILD.bazel @@ -249,13 +249,13 @@ java_library( "//common:cel_ast", "//common:compiler_common", "//common:mutable_ast", + "//common:operator", "//common/ast", "//common/formats:value_string", "//common/navigation:mutable_navigation", "//extensions:optional_library", "//optimizer:ast_optimizer", "//optimizer:mutable_ast", - "//parser:operator", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], diff --git a/policy/src/main/java/dev/cel/policy/RuleComposer.java b/policy/src/main/java/dev/cel/policy/RuleComposer.java index e3b9e7057..7bbde7685 100644 --- a/policy/src/main/java/dev/cel/policy/RuleComposer.java +++ b/policy/src/main/java/dev/cel/policy/RuleComposer.java @@ -25,6 +25,7 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelMutableAst; import dev.cel.common.CelValidationException; +import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr.ExprKind.Kind; import dev.cel.common.formats.ValueString; import dev.cel.common.navigation.CelNavigableMutableAst; @@ -32,7 +33,6 @@ import dev.cel.extensions.CelOptionalLibrary.Function; import dev.cel.optimizer.AstMutator; import dev.cel.optimizer.CelAstOptimizer; -import dev.cel.parser.Operator; import dev.cel.policy.CelCompiledRule.CelCompiledMatch; import dev.cel.policy.CelCompiledRule.CelCompiledMatch.OutputValue; import dev.cel.policy.CelCompiledRule.CelCompiledVariable; diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 184a01534..3df163f79 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -992,8 +992,8 @@ java_library( ":unknown_attributes", "//common:cel_ast", "//common:compiler_common", + "//common:operator", "//common/ast", - "//parser:operator", "//parser:parser_builder", "//parser:parser_factory", "@maven//:com_google_guava_guava", diff --git a/runtime/src/main/java/dev/cel/runtime/CelAttributeParser.java b/runtime/src/main/java/dev/cel/runtime/CelAttributeParser.java index a14d8bfd5..c64250e4c 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelAttributeParser.java +++ b/runtime/src/main/java/dev/cel/runtime/CelAttributeParser.java @@ -21,13 +21,13 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelValidationException; import dev.cel.common.CelValidationResult; +import dev.cel.common.Operator; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; import dev.cel.common.ast.CelExpr.ExprKind.Kind; import dev.cel.parser.CelParser; import dev.cel.parser.CelParserFactory; -import dev.cel.parser.Operator; import java.util.ArrayDeque; /** From 0fb05e522085cd4ad503bbb4f528ebb54f87d120 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 30 Oct 2025 11:04:28 -0700 Subject: [PATCH 004/100] Remove dev.cel.parser.Operator, migrate parser package to dev.cel.common.Operator PiperOrigin-RevId: 826106837 --- .../main/java/dev/cel/common/Operator.java | 31 ++- .../src/test/java/dev/cel/common/BUILD.bazel | 1 + .../java/dev/cel/common}/OperatorTest.java | 72 +----- parser/BUILD.bazel | 5 - .../src/main/java/dev/cel/parser/BUILD.bazel | 17 +- .../java/dev/cel/parser/CelStandardMacro.java | 1 + .../dev/cel/parser/CelUnparserVisitor.java | 22 +- .../main/java/dev/cel/parser/Operator.java | 221 ------------------ .../src/main/java/dev/cel/parser/Parser.java | 1 + .../src/test/java/dev/cel/parser/BUILD.bazel | 3 +- .../dev/cel/parser/CelStandardMacroTest.java | 1 + .../dev/cel/parser/CelUnparserImplTest.java | 71 ++++++ 12 files changed, 115 insertions(+), 331 deletions(-) rename {parser/src/test/java/dev/cel/parser => common/src/test/java/dev/cel/common}/OperatorTest.java (68%) delete mode 100644 parser/src/main/java/dev/cel/parser/Operator.java diff --git a/common/src/main/java/dev/cel/common/Operator.java b/common/src/main/java/dev/cel/common/Operator.java index eaa414277..0c059d989 100644 --- a/common/src/main/java/dev/cel/common/Operator.java +++ b/common/src/main/java/dev/cel/common/Operator.java @@ -15,14 +15,14 @@ package dev.cel.common; import com.google.common.collect.ImmutableMap; -import dev.cel.common.ast.CelExpr; import java.util.Objects; import java.util.Optional; /** * Package-private enumeration of Common Expression Language operators. * - *

Equivalent to https://pkg.go.dev/github.com/google/cel-go/common/operators. + *

Equivalent to operators. */ public enum Operator { CONDITIONAL("_?_:_"), @@ -96,7 +96,7 @@ String getSymbol() { .buildOrThrow(); /** Lookup an operator by its unmangled name, as used with the source text of an expression. */ - static Optional find(String text) { + public static Optional find(String text) { return Optional.ofNullable(OPERATORS.get(text)); } @@ -193,26 +193,23 @@ static Optional findReverseBinaryOperator(String op) { return Optional.ofNullable(REVERSE_OPERATORS.get(op)); } - static int lookupPrecedence(String op) { + /** + * Returns the precedence of the operator. + * + * @return Integer value describing precedence. Higher value means higher precedence. Returns 0 if + * the operator is not found. + */ + public static int lookupPrecedence(String op) { return PRECEDENCES.getOrDefault(op, 0); } - static Optional lookupUnaryOperator(String op) { + /** Looks up the associated unary operator based on its function name (Ex: !_ -> !) */ + public static Optional lookupUnaryOperator(String op) { return Optional.ofNullable(UNARY_OPERATORS.get(op)); } - static Optional lookupBinaryOperator(String op) { + /** Looks up the associated binary operator based on its function name (Ex: _||_ -> ||) */ + public static Optional lookupBinaryOperator(String op) { return Optional.ofNullable(BINARY_OPERATORS.get(op)); } - - static boolean isOperatorLowerPrecedence(String op, CelExpr expr) { - if (!expr.exprKind().getKind().equals(CelExpr.ExprKind.Kind.CALL)) { - return false; - } - return lookupPrecedence(op) < lookupPrecedence(expr.call().function()); - } - - static boolean isOperatorLeftRecursive(String op) { - return !op.equals(LOGICAL_AND.getFunction()) && !op.equals(LOGICAL_OR.getFunction()); - } } diff --git a/common/src/test/java/dev/cel/common/BUILD.bazel b/common/src/test/java/dev/cel/common/BUILD.bazel index 1ff6deb3b..8dcb60b92 100644 --- a/common/src/test/java/dev/cel/common/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/BUILD.bazel @@ -17,6 +17,7 @@ java_library( "//common:cel_source", "//common:compiler_common", "//common:container", + "//common:operator", "//common:options", "//common:proto_ast", "//common:proto_json_adapter", diff --git a/parser/src/test/java/dev/cel/parser/OperatorTest.java b/common/src/test/java/dev/cel/common/OperatorTest.java similarity index 68% rename from parser/src/test/java/dev/cel/parser/OperatorTest.java rename to common/src/test/java/dev/cel/common/OperatorTest.java index 9bed3c466..28d941c6e 100644 --- a/parser/src/test/java/dev/cel/parser/OperatorTest.java +++ b/common/src/test/java/dev/cel/common/OperatorTest.java @@ -12,18 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dev.cel.parser; +package dev.cel.common; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; -import dev.cel.common.ast.CelExpr; -import dev.cel.common.ast.CelExpr.CelCall; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -150,70 +146,4 @@ public void lookupBinaryOperator_nonEmpty(String operator, String value) { public void lookupBinaryOperator_empty(String operator) { assertEquals(Operator.lookupBinaryOperator(operator), Optional.empty()); } - - @Test - @TestParameters({ - "{operator1: '_[_]', operator2: '_&&_'}", - "{operator1: '_&&_', operator2: '_||_'}", - "{operator1: '_||_', operator2: '_?_:_'}", - "{operator1: '!_', operator2: '_*_'}", - "{operator1: '_==_', operator2: '_&&_'}", - "{operator1: '_!=_', operator2: '_?_:_'}", - }) - public void operatorLowerPrecedence(String operator1, String operator2) { - CelExpr expr = - CelExpr.newBuilder().setCall(CelCall.newBuilder().setFunction(operator2).build()).build(); - - assertTrue(Operator.isOperatorLowerPrecedence(operator1, expr)); - } - - @Test - @TestParameters({ - "{operator1: '_?_:_', operator2: '_&&_'}", - "{operator1: '_&&_', operator2: '_[_]'}", - "{operator1: '_||_', operator2: '!_'}", - "{operator1: '!_', operator2: '-_'}", - "{operator1: '_==_', operator2: '_!=_'}", - "{operator1: '_!=_', operator2: '_-_'}", - }) - public void operatorNotLowerPrecedence(String operator1, String operator2) { - CelExpr expr = - CelExpr.newBuilder().setCall(CelCall.newBuilder().setFunction(operator2).build()).build(); - - assertFalse(Operator.isOperatorLowerPrecedence(operator1, expr)); - } - - @Test - @TestParameters({ - "{operator: '_[_]'}", - "{operator: '!_'}", - "{operator: '_==_'}", - "{operator: '_?_:_'}", - "{operator: '_!=_'}", - "{operator: '_<_'}", - "{operator: '_<=_'}", - "{operator: '_>_'}", - "{operator: '_>=_'}", - "{operator: '_+_'}", - "{operator: '_-_'}", - "{operator: '_*_'}", - "{operator: '_/_'}", - "{operator: '_%_'}", - "{operator: '-_'}", - "{operator: 'has'}", - "{operator: '_[?_]'}", - "{operator: '@not_strictly_false'}", - }) - public void operatorLeftRecursive(String operator) { - assertTrue(Operator.isOperatorLeftRecursive(operator)); - } - - @Test - @TestParameters({ - "{operator: '_&&_'}", - "{operator: '_||_'}", - }) - public void operatorNotLeftRecursive(String operator) { - assertFalse(Operator.isOperatorLeftRecursive(operator)); - } } diff --git a/parser/BUILD.bazel b/parser/BUILD.bazel index f05a5cf93..1e662c3c5 100644 --- a/parser/BUILD.bazel +++ b/parser/BUILD.bazel @@ -31,11 +31,6 @@ java_library( exports = ["//parser/src/main/java/dev/cel/parser:macro"], ) -java_library( - name = "operator", - exports = ["//parser/src/main/java/dev/cel/parser:operator"], -) - java_library( name = "cel_g4_visitors", visibility = ["//:internal"], diff --git a/parser/src/main/java/dev/cel/parser/BUILD.bazel b/parser/src/main/java/dev/cel/parser/BUILD.bazel index 2bbc8c0e2..84b9e5af6 100644 --- a/parser/src/main/java/dev/cel/parser/BUILD.bazel +++ b/parser/src/main/java/dev/cel/parser/BUILD.bazel @@ -56,11 +56,11 @@ java_library( ], deps = [ ":macro", - ":operator", ":parser_builder", "//common:cel_ast", "//common:cel_source", "//common:compiler_common", + "//common:operator", "//common:options", "//common:source_location", "//common/annotations", @@ -94,9 +94,9 @@ java_library( tags = [ ], deps = [ - ":operator", "//:auto_value", "//common:compiler_common", + "//common:operator", "//common:source_location", "//common/ast", "//common/ast:expr_factory", @@ -105,17 +105,6 @@ java_library( ], ) -java_library( - name = "operator", - srcs = ["Operator.java"], - tags = [ - ], - deps = [ - "//common/ast", - "@maven//:com_google_guava_guava", - ], -) - java_library( name = "unparser", srcs = UNPARSER_SOURCES, @@ -134,9 +123,9 @@ java_library( tags = [ ], deps = [ - ":operator", "//common:cel_ast", "//common:cel_source", + "//common:operator", "//common/ast", "//common/ast:cel_expr_visitor", "//common/values:cel_byte_string", diff --git a/parser/src/main/java/dev/cel/parser/CelStandardMacro.java b/parser/src/main/java/dev/cel/parser/CelStandardMacro.java index 0dce8b295..a90acce72 100644 --- a/parser/src/main/java/dev/cel/parser/CelStandardMacro.java +++ b/parser/src/main/java/dev/cel/parser/CelStandardMacro.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import dev.cel.common.CelIssue; +import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; import java.util.Optional; diff --git a/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java b/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java index a743c0439..61c8b10e9 100644 --- a/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java +++ b/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java @@ -13,10 +13,15 @@ // limitations under the License. package dev.cel.parser; +import static dev.cel.common.Operator.LOGICAL_AND; +import static dev.cel.common.Operator.LOGICAL_OR; + +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import com.google.re2j.Pattern; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelSource; +import dev.cel.common.Operator; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; @@ -273,7 +278,7 @@ private void visitBinary(CelCall expr, String op) { // add parens if the current operator is lower precedence than the rhs expr // operator, or the same precedence and the operator is left recursive. boolean rhsParen = isComplexOperatorWithRespectTo(rhs, fun); - if (!rhsParen && Operator.isOperatorLeftRecursive(fun)) { + if (!rhsParen && isOperatorLeftRecursive(fun)) { rhsParen = isOperatorSamePrecedence(fun, rhs); } @@ -366,7 +371,7 @@ private boolean isComplexOperatorWithRespectTo(CelExpr expr, String op) { return false; } // Otherwise, return whether the given op has lower precedence than expr - return Operator.isOperatorLowerPrecedence(op, expr); + return isOperatorLowerPrecedence(op, expr); } // bytesToOctets converts byte sequences to a string using a three digit octal encoded value @@ -378,4 +383,17 @@ private String bytesToOctets(CelByteString bytes) { } return sb.toString(); } + + @VisibleForTesting + static boolean isOperatorLeftRecursive(String op) { + return !op.equals(LOGICAL_AND.getFunction()) && !op.equals(LOGICAL_OR.getFunction()); + } + + @VisibleForTesting + static boolean isOperatorLowerPrecedence(String op, CelExpr expr) { + if (!expr.exprKind().getKind().equals(CelExpr.ExprKind.Kind.CALL)) { + return false; + } + return Operator.lookupPrecedence(op) < Operator.lookupPrecedence(expr.call().function()); + } } diff --git a/parser/src/main/java/dev/cel/parser/Operator.java b/parser/src/main/java/dev/cel/parser/Operator.java deleted file mode 100644 index 6135350e3..000000000 --- a/parser/src/main/java/dev/cel/parser/Operator.java +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2022 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.parser; - -import com.google.common.collect.ImmutableMap; -import dev.cel.common.ast.CelExpr; -import java.util.Objects; -import java.util.Optional; - -/** - * Package-private enumeration of Common Expression Language operators. - * - *

Equivalent to https://pkg.go.dev/github.com/google/cel-go/common/operators. - * - * @deprecated Use {@code dev.cel.common.Operator} instead. - */ -@Deprecated -public enum Operator { - CONDITIONAL("_?_:_"), - LOGICAL_AND("_&&_", "&&"), - LOGICAL_OR("_||_", "||"), - LOGICAL_NOT("!_", "!"), - EQUALS("_==_", "=="), - NOT_EQUALS("_!=_", "!="), - LESS("_<_", "<"), - LESS_EQUALS("_<=_", "<="), - GREATER("_>_", ">"), - GREATER_EQUALS("_>=_", ">="), - ADD("_+_", "+"), - SUBTRACT("_-_", "-"), - MULTIPLY("_*_", "*"), - DIVIDE("_/_", "/"), - MODULO("_%_", "%"), - NEGATE("-_", "-"), - INDEX("_[_]"), - HAS("has"), - ALL("all"), - EXISTS("exists"), - EXISTS_ONE("exists_one"), - MAP("map"), - FILTER("filter"), - NOT_STRICTLY_FALSE("@not_strictly_false"), - IN("@in", "in"), - OPTIONAL_INDEX("_[?_]"), - OPTIONAL_SELECT("_?._"), - @Deprecated // Prefer NOT_STRICTLY_FALSE. - OLD_NOT_STRICTLY_FALSE("__not_strictly_false__"), - @Deprecated // Prefer IN. - OLD_IN("_in_"); - - private final String functionName; - private final String displayName; - - Operator(String functionName) { - this(functionName, ""); - } - - Operator(String functionName, String displayName) { - this.functionName = functionName; - this.displayName = displayName; - } - - /** Returns the mangled operator name, as used within the AST. */ - public String getFunction() { - return functionName; - } - - /** Returns the unmangled operator name, as used within the source text of an expression. */ - String getSymbol() { - return displayName; - } - - private static final ImmutableMap OPERATORS = - ImmutableMap.builder() - .put(ADD.getSymbol(), ADD) - .put(DIVIDE.getSymbol(), DIVIDE) - .put(EQUALS.getSymbol(), EQUALS) - .put(GREATER.getSymbol(), GREATER) - .put(GREATER_EQUALS.getSymbol(), GREATER_EQUALS) - .put(IN.getSymbol(), IN) - .put(LESS.getSymbol(), LESS) - .put(LESS_EQUALS.getSymbol(), LESS_EQUALS) - .put(MODULO.getSymbol(), MODULO) - .put(MULTIPLY.getSymbol(), MULTIPLY) - .put(NOT_EQUALS.getSymbol(), NOT_EQUALS) - .put(SUBTRACT.getSymbol(), SUBTRACT) - .buildOrThrow(); - - /** Lookup an operator by its unmangled name, as used with the source text of an expression. */ - static Optional find(String text) { - return Optional.ofNullable(OPERATORS.get(text)); - } - - private static final ImmutableMap REVERSE_OPERATORS = - ImmutableMap.builder() - .put(ADD.getFunction(), ADD) - .put(ALL.getFunction(), ALL) - .put(CONDITIONAL.getFunction(), CONDITIONAL) - .put(DIVIDE.getFunction(), DIVIDE) - .put(EQUALS.getFunction(), EQUALS) - .put(EXISTS.getFunction(), EXISTS) - .put(EXISTS_ONE.getFunction(), EXISTS_ONE) - .put(FILTER.getFunction(), FILTER) - .put(GREATER.getFunction(), GREATER) - .put(GREATER_EQUALS.getFunction(), GREATER_EQUALS) - .put(HAS.getFunction(), HAS) - .put(IN.getFunction(), IN) - .put(INDEX.getFunction(), INDEX) - .put(LESS.getFunction(), LESS) - .put(LESS_EQUALS.getFunction(), LESS_EQUALS) - .put(LOGICAL_AND.getFunction(), LOGICAL_AND) - .put(LOGICAL_NOT.getFunction(), LOGICAL_NOT) - .put(LOGICAL_OR.getFunction(), LOGICAL_OR) - .put(MAP.getFunction(), MAP) - .put(MODULO.getFunction(), MODULO) - .put(MULTIPLY.getFunction(), MULTIPLY) - .put(NEGATE.getFunction(), NEGATE) - .put(NOT_EQUALS.getFunction(), NOT_EQUALS) - .put(NOT_STRICTLY_FALSE.getFunction(), NOT_STRICTLY_FALSE) - .put(OLD_IN.getFunction(), OLD_IN) - .put(OLD_NOT_STRICTLY_FALSE.getFunction(), OLD_NOT_STRICTLY_FALSE) - .put(OPTIONAL_INDEX.getFunction(), OPTIONAL_INDEX) - .put(OPTIONAL_SELECT.getFunction(), OPTIONAL_SELECT) - .put(SUBTRACT.getFunction(), SUBTRACT) - .buildOrThrow(); - - // precedence of the operator, where the higher value means higher. - private static final ImmutableMap PRECEDENCES = - ImmutableMap.builder() - .put(CONDITIONAL.getFunction(), 8) - .put(LOGICAL_OR.getFunction(), 7) - .put(LOGICAL_AND.getFunction(), 6) - .put(EQUALS.getFunction(), 5) - .put(GREATER.getFunction(), 5) - .put(GREATER_EQUALS.getFunction(), 5) - .put(IN.getFunction(), 5) - .put(LESS.getFunction(), 5) - .put(LESS_EQUALS.getFunction(), 5) - .put(NOT_EQUALS.getFunction(), 5) - .put(ADD.getFunction(), 4) - .put(SUBTRACT.getFunction(), 4) - .put(DIVIDE.getFunction(), 3) - .put(MODULO.getFunction(), 3) - .put(MULTIPLY.getFunction(), 3) - .put(LOGICAL_NOT.getFunction(), 2) - .put(NEGATE.getFunction(), 2) - .put(INDEX.getFunction(), 1) - .buildOrThrow(); - - private static final ImmutableMap UNARY_OPERATORS = - ImmutableMap.builder() - .put(NEGATE.getFunction(), "-") - .put(LOGICAL_NOT.getFunction(), "!") - .buildOrThrow(); - - private static final ImmutableMap BINARY_OPERATORS = - ImmutableMap.builder() - .put(LOGICAL_OR.getFunction(), "||") - .put(LOGICAL_AND.getFunction(), "&&") - .put(LESS_EQUALS.getFunction(), "<=") - .put(LESS.getFunction(), "<") - .put(GREATER_EQUALS.getFunction(), ">=") - .put(GREATER.getFunction(), ">") - .put(EQUALS.getFunction(), "==") - .put(NOT_EQUALS.getFunction(), "!=") - .put(IN.getFunction(), "in") - .put(ADD.getFunction(), "+") - .put(SUBTRACT.getFunction(), "-") - .put(MULTIPLY.getFunction(), "*") - .put(DIVIDE.getFunction(), "/") - .put(MODULO.getFunction(), "%") - .buildOrThrow(); - - /** Lookup an operator by its mangled name (ex: _&&_), as used within the AST. */ - public static Optional findReverse(String op) { - return Optional.ofNullable(REVERSE_OPERATORS.get(op)); - } - - /** Lookup a binary operator by its mangled name, as used within the AST. */ - static Optional findReverseBinaryOperator(String op) { - if (Objects.equals(op, LOGICAL_NOT.getFunction()) || Objects.equals(op, NEGATE.getFunction())) { - return Optional.empty(); - } - return Optional.ofNullable(REVERSE_OPERATORS.get(op)); - } - - static int lookupPrecedence(String op) { - return PRECEDENCES.getOrDefault(op, 0); - } - - static Optional lookupUnaryOperator(String op) { - return Optional.ofNullable(UNARY_OPERATORS.get(op)); - } - - static Optional lookupBinaryOperator(String op) { - return Optional.ofNullable(BINARY_OPERATORS.get(op)); - } - - static boolean isOperatorLowerPrecedence(String op, CelExpr expr) { - if (!expr.exprKind().getKind().equals(CelExpr.ExprKind.Kind.CALL)) { - return false; - } - return lookupPrecedence(op) < lookupPrecedence(expr.call().function()); - } - - static boolean isOperatorLeftRecursive(String op) { - return !op.equals(LOGICAL_AND.getFunction()) && !op.equals(LOGICAL_OR.getFunction()); - } -} diff --git a/parser/src/main/java/dev/cel/parser/Parser.java b/parser/src/main/java/dev/cel/parser/Parser.java index 2e7b08dc0..a0bef54bf 100644 --- a/parser/src/main/java/dev/cel/parser/Parser.java +++ b/parser/src/main/java/dev/cel/parser/Parser.java @@ -70,6 +70,7 @@ import dev.cel.common.CelSource; import dev.cel.common.CelSourceLocation; import dev.cel.common.CelValidationResult; +import dev.cel.common.Operator; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.internal.CodePointStream; diff --git a/parser/src/test/java/dev/cel/parser/BUILD.bazel b/parser/src/test/java/dev/cel/parser/BUILD.bazel index b6a89ff66..1b1668ce3 100644 --- a/parser/src/test/java/dev/cel/parser/BUILD.bazel +++ b/parser/src/test/java/dev/cel/parser/BUILD.bazel @@ -16,6 +16,7 @@ java_library( "//common:cel_ast", "//common:cel_source", "//common:compiler_common", + "//common:operator", "//common:options", "//common:proto_ast", "//common:source_location", @@ -25,10 +26,10 @@ java_library( "//extensions:optional_library", "//parser", "//parser:macro", - "//parser:operator", "//parser:parser_builder", "//parser:parser_factory", "//parser:unparser", + "//parser:unparser_visitor", "//testing:adorner", "//testing:baseline_test_case", "@cel_spec//proto/cel/expr:syntax_java_proto", diff --git a/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java b/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java index c06be2688..80538930a 100644 --- a/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java +++ b/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java @@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import com.google.common.testing.EqualsTester; +import dev.cel.common.Operator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/parser/src/test/java/dev/cel/parser/CelUnparserImplTest.java b/parser/src/test/java/dev/cel/parser/CelUnparserImplTest.java index 0f2c00022..6789b5621 100644 --- a/parser/src/test/java/dev/cel/parser/CelUnparserImplTest.java +++ b/parser/src/test/java/dev/cel/parser/CelUnparserImplTest.java @@ -15,11 +15,16 @@ package dev.cel.parser; import static com.google.common.truth.Truth.assertThat; +import static dev.cel.parser.CelUnparserVisitor.isOperatorLeftRecursive; +import static dev.cel.parser.CelUnparserVisitor.isOperatorLowerPrecedence; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider; +import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelOptions; import dev.cel.common.CelProtoAbstractSyntaxTree; @@ -301,4 +306,70 @@ public void unparse_macroWithReceiverStyleArg() throws Exception { assertThat(unparser.unparse(ast)) .isEqualTo("[\"a\"].all(x, x.trim().lowerAscii().contains(\"b\"))"); } + + @Test + @TestParameters({ + "{operator: '_[_]'}", + "{operator: '!_'}", + "{operator: '_==_'}", + "{operator: '_?_:_'}", + "{operator: '_!=_'}", + "{operator: '_<_'}", + "{operator: '_<=_'}", + "{operator: '_>_'}", + "{operator: '_>=_'}", + "{operator: '_+_'}", + "{operator: '_-_'}", + "{operator: '_*_'}", + "{operator: '_/_'}", + "{operator: '_%_'}", + "{operator: '-_'}", + "{operator: 'has'}", + "{operator: '_[?_]'}", + "{operator: '@not_strictly_false'}", + }) + public void operatorLeftRecursive(String operator) { + assertTrue(isOperatorLeftRecursive(operator)); + } + + @Test + @TestParameters({ + "{operator: '_&&_'}", + "{operator: '_||_'}", + }) + public void operatorNotLeftRecursive(String operator) { + assertFalse(isOperatorLeftRecursive(operator)); + } + + @Test + @TestParameters({ + "{operator1: '_[_]', operator2: '_&&_'}", + "{operator1: '_&&_', operator2: '_||_'}", + "{operator1: '_||_', operator2: '_?_:_'}", + "{operator1: '!_', operator2: '_*_'}", + "{operator1: '_==_', operator2: '_&&_'}", + "{operator1: '_!=_', operator2: '_?_:_'}", + }) + public void operatorLowerPrecedence(String operator1, String operator2) { + CelExpr expr = + CelExpr.newBuilder().setCall(CelCall.newBuilder().setFunction(operator2).build()).build(); + + assertTrue(isOperatorLowerPrecedence(operator1, expr)); + } + + @Test + @TestParameters({ + "{operator1: '_?_:_', operator2: '_&&_'}", + "{operator1: '_&&_', operator2: '_[_]'}", + "{operator1: '_||_', operator2: '!_'}", + "{operator1: '!_', operator2: '-_'}", + "{operator1: '_==_', operator2: '_!=_'}", + "{operator1: '_!=_', operator2: '_-_'}", + }) + public void operatorNotLowerPrecedence(String operator1, String operator2) { + CelExpr expr = + CelExpr.newBuilder().setCall(CelCall.newBuilder().setFunction(operator2).build()).build(); + + assertFalse(isOperatorLowerPrecedence(operator1, expr)); + } } From cd05924fb69c74fcd2fdaf523a7dba6a6dc36c7a Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 31 Oct 2025 14:35:17 -0700 Subject: [PATCH 005/100] Consolidate function overload and resolver interfaces PiperOrigin-RevId: 826632322 --- runtime/BUILD.bazel | 24 +++-- .../src/main/java/dev/cel/runtime/BUILD.bazel | 94 ++++++++++++------- .../dev/cel/runtime/CelFunctionOverload.java | 13 ++- .../dev/cel/runtime/CelFunctionResolver.java | 18 +++- .../cel/runtime/CelLateFunctionBindings.java | 4 +- .../dev/cel/runtime/CelResolvedOverload.java | 12 ++- .../dev/cel/runtime/CelStandardFunctions.java | 62 +++++------- .../dev/cel/runtime/DefaultDispatcher.java | 42 +-------- .../dev/cel/runtime/DefaultInterpreter.java | 12 +-- .../main/java/dev/cel/runtime/Dispatcher.java | 2 +- .../dev/cel/runtime/FunctionOverload.java | 46 --------- .../dev/cel/runtime/FunctionResolver.java | 44 --------- .../java/dev/cel/runtime/Interpretable.java | 4 +- .../main/java/dev/cel/runtime/Registrar.java | 6 +- .../dev/cel/runtime/ResolvedOverload.java | 2 +- .../runtime/UnknownTrackingInterpretable.java | 2 +- .../java/dev/cel/runtime/standard/BUILD.bazel | 6 +- .../runtime/standard/CelStandardOverload.java | 4 +- .../src/test/java/dev/cel/runtime/BUILD.bazel | 4 +- .../cel/runtime/CelResolvedOverloadTest.java | 9 +- .../cel/runtime/CelStandardFunctionsTest.java | 4 +- .../cel/runtime/DefaultDispatcherTest.java | 8 +- runtime/standard/BUILD.bazel | 10 ++ 23 files changed, 187 insertions(+), 245 deletions(-) delete mode 100644 runtime/src/main/java/dev/cel/runtime/FunctionOverload.java delete mode 100644 runtime/src/main/java/dev/cel/runtime/FunctionResolver.java diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index b7d59ce96..3bcb86766 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -76,12 +76,6 @@ cel_android_library( exports = ["//runtime/src/main/java/dev/cel/runtime:late_function_binding_android"], ) -java_library( - name = "function_overload_impl", - visibility = ["//:internal"], - exports = ["//runtime/src/main/java/dev/cel/runtime:function_overload_impl"], -) - java_library( name = "evaluation_exception_builder", exports = ["//runtime/src/main/java/dev/cel/runtime:evaluation_exception_builder"], @@ -220,3 +214,21 @@ cel_android_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:lite_runtime_impl_android"], ) + +java_library( + name = "resolved_overload", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload"], +) + +cel_android_library( + name = "resolved_overload_android", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_android"], +) + +java_library( + name = "resolved_overload_internal", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_internal"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 3df163f79..e5df8250b 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -51,13 +51,6 @@ FUNCTION_BINDING_SOURCES = [ "FunctionBindingImpl.java", ] -# keep sorted -FUNCTION_OVERLOAD_IMPL_SOURCES = [ - "FunctionOverload.java", - "FunctionResolver.java", - "ResolvedOverload.java", -] - # keep sorted INTERPRABLE_SOURCES = [ "GlobalResolver.java", @@ -128,10 +121,12 @@ java_library( ":base", ":evaluation_exception", ":evaluation_exception_builder", - ":function_overload_impl", + ":resolved_overload", + ":resolved_overload_internal", "//:auto_value", "//common:error_codes", "//common/annotations", + "//runtime:function_resolver", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -146,7 +141,9 @@ cel_android_library( ":base_android", ":evaluation_exception", ":evaluation_exception_builder", - ":function_overload_impl_android", + ":function_resolver_android", + ":resolved_overload_android", + ":resolved_overload_internal_android", "//:auto_value", "//common:error_codes", "//common/annotations", @@ -250,7 +247,7 @@ java_library( tags = [ ], deps = [ - ":function_overload_impl", + ":function_overload", ":metadata", "//common:cel_ast", "//common/annotations", @@ -264,11 +261,10 @@ cel_android_library( srcs = BASE_SOURCES, visibility = ["//visibility:private"], deps = [ - ":function_overload_impl_android", + ":function_overload_android", ":metadata", "//common:cel_ast_android", "//common/annotations", - "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -288,10 +284,10 @@ java_library( ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener", - ":function_overload_impl", ":interpretable", ":interpreter_util", ":metadata", + ":resolved_overload_internal", ":runtime_helpers", ":runtime_type_provider", ":type_resolver", @@ -306,6 +302,7 @@ java_library( "//common/types", "//common/types:type_providers", "//common/values:cel_byte_string", + "//runtime:function_resolver", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -326,10 +323,11 @@ cel_android_library( ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener_android", - ":function_overload_impl_android", + ":function_resolver_android", ":interpretable_android", ":interpreter_util_android", ":metadata", + ":resolved_overload_internal_android", ":runtime_helpers_android", ":runtime_type_provider_android", ":type_resolver_android", @@ -486,7 +484,6 @@ RUNTIME_SOURCES = [ LATE_FUNCTION_BINDING_SOURCES = [ "CelLateFunctionBindings.java", - "CelResolvedOverload.java", ] java_library( @@ -498,9 +495,9 @@ java_library( ":dispatcher", ":evaluation_exception", ":function_binding", - ":function_overload", - ":function_overload_impl", ":function_resolver", + ":resolved_overload", + ":resolved_overload_internal", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -516,9 +513,9 @@ cel_android_library( ":dispatcher_android", ":evaluation_exception", ":function_binding_android", - ":function_overload_android", - ":function_overload_impl_android", ":function_resolver_android", + ":resolved_overload_android", + ":resolved_overload_internal_android", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -586,8 +583,8 @@ java_library( deps = [ ":evaluation_exception", ":evaluation_listener", - ":function_overload_impl", "//common/annotations", + "//runtime:function_resolver", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", ], @@ -600,7 +597,7 @@ cel_android_library( deps = [ ":evaluation_exception", ":evaluation_listener_android", - ":function_overload_impl_android", + ":function_resolver_android", "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", @@ -652,6 +649,7 @@ java_library( "//runtime/standard:not_equals", "//runtime/standard:size", "//runtime/standard:standard_function", + "//runtime/standard:standard_overload", "//runtime/standard:starts_with", "//runtime/standard:string", "//runtime/standard:subtract", @@ -707,6 +705,7 @@ cel_android_library( "//runtime/standard:not_equals_android", "//runtime/standard:size_android", "//runtime/standard:standard_function_android", + "//runtime/standard:standard_overload_android", "//runtime/standard:starts_with_android", "//runtime/standard:string_android", "//runtime/standard:subtract_android", @@ -751,7 +750,8 @@ java_library( tags = [ ], deps = [ - ":function_overload_impl", + ":evaluation_exception", + ":resolved_overload_internal", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -761,7 +761,8 @@ cel_android_library( name = "function_resolver_android", srcs = ["CelFunctionResolver.java"], deps = [ - ":function_overload_impl_android", + ":evaluation_exception", + ":resolved_overload_internal_android", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -775,7 +776,7 @@ java_library( tags = [ ], deps = [ - ":function_overload_impl", + ":evaluation_exception", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -786,19 +787,18 @@ cel_android_library( "CelFunctionOverload.java", ], deps = [ - ":function_overload_impl_android", + ":evaluation_exception", "@maven//:com_google_errorprone_error_prone_annotations", ], ) java_library( - name = "function_overload_impl", - srcs = FUNCTION_OVERLOAD_IMPL_SOURCES, + name = "resolved_overload_internal", + srcs = ["ResolvedOverload.java"], tags = [ ], deps = [ - ":evaluation_exception", - "//common/annotations", + ":function_overload", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_protobuf_protobuf_java", @@ -806,11 +806,11 @@ java_library( ) cel_android_library( - name = "function_overload_impl_android", - srcs = FUNCTION_OVERLOAD_IMPL_SOURCES, + name = "resolved_overload_internal_android", + srcs = ["ResolvedOverload.java"], + visibility = ["//visibility:private"], deps = [ - ":evaluation_exception", - "//common/annotations", + ":function_overload_android", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_protobuf_protobuf_javalite", @@ -1167,3 +1167,31 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) + +java_library( + name = "resolved_overload", + srcs = ["CelResolvedOverload.java"], + tags = [ + ], + deps = [ + ":function_overload", + ":resolved_overload_internal", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "resolved_overload_android", + srcs = ["CelResolvedOverload.java"], + tags = [ + ], + deps = [ + ":function_overload_android", + ":resolved_overload_internal_android", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java index f924c7d62..a1341cb21 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java @@ -19,7 +19,10 @@ /** Interface describing the general signature of all CEL custom function implementations. */ @Immutable @FunctionalInterface -public interface CelFunctionOverload extends FunctionOverload { +public interface CelFunctionOverload { + + /** Evaluate a set of arguments throwing a {@code CelException} on error. */ + Object apply(Object[] args) throws CelEvaluationException; /** * Helper interface for describing unary functions where the type-parameter is used to improve @@ -27,7 +30,9 @@ public interface CelFunctionOverload extends FunctionOverload { */ @Immutable @FunctionalInterface - interface Unary extends FunctionOverload.Unary {} + interface Unary { + Object apply(T arg) throws CelEvaluationException; + } /** * Helper interface for describing binary functions where the type parameters are used to improve @@ -35,5 +40,7 @@ interface Unary extends FunctionOverload.Unary {} */ @Immutable @FunctionalInterface - interface Binary extends FunctionOverload.Binary {} + interface Binary { + Object apply(T1 arg1, T2 arg2) throws CelEvaluationException; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java index d769ff238..8df7fd0dc 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java @@ -15,10 +15,26 @@ package dev.cel.runtime; import javax.annotation.concurrent.ThreadSafe; +import java.util.List; +import java.util.Optional; /** * Interface to a resolver for CEL functions based on the function name, overload ids, and * arguments. */ @ThreadSafe -public interface CelFunctionResolver extends FunctionResolver {} +public interface CelFunctionResolver { + + /** + * Finds a specific function overload to invoke based on given parameters. + * + * @param functionName the logical name of the function being invoked. + * @param overloadIds A list of function overload ids. The dispatcher selects the unique overload + * from this list with matching arguments. + * @param args The arguments to pass to the function. + * @return an optional value of the resolved overload. + * @throws CelEvaluationException if the overload resolution is ambiguous, + */ + Optional findOverload( + String functionName, List overloadIds, Object[] args) throws CelEvaluationException; +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index 7f83e38fd..282ef1eb9 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -57,7 +57,7 @@ public static CelLateFunctionBindings from(List functions) { private static ResolvedOverload createResolvedOverload(CelFunctionBinding binding) { return CelResolvedOverload.of( binding.getOverloadId(), - binding.getArgTypes(), - (args) -> binding.getDefinition().apply(args)); + (args) -> binding.getDefinition().apply(args), + binding.getArgTypes()); } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java index e23749f15..9725315cc 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java @@ -17,6 +17,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import java.util.List; /** * Representation of a function overload which has been resolved to a specific set of argument types @@ -24,7 +25,7 @@ */ @AutoValue @Immutable -public abstract class CelResolvedOverload implements ResolvedOverload { +abstract class CelResolvedOverload implements ResolvedOverload { /** The overload id of the function. */ @Override @@ -42,15 +43,16 @@ public abstract class CelResolvedOverload implements ResolvedOverload { * Creates a new resolved overload from the given overload id, parameter types, and definition. */ public static CelResolvedOverload of( - String overloadId, Class[] parameterTypes, CelFunctionOverload definition) { - return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); + String overloadId, CelFunctionOverload definition, Class... parameterTypes) { + return of(overloadId, definition, ImmutableList.copyOf(parameterTypes)); } /** * Creates a new resolved overload from the given overload id, parameter types, and definition. */ public static CelResolvedOverload of( - String overloadId, ImmutableList> parameterTypes, CelFunctionOverload definition) { - return new AutoValue_CelResolvedOverload(overloadId, parameterTypes, definition); + String overloadId, CelFunctionOverload definition, List> parameterTypes) { + return new AutoValue_CelResolvedOverload( + overloadId, ImmutableList.copyOf(parameterTypes), definition); } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 299dd7a89..7d781d4af 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -39,6 +39,7 @@ import dev.cel.runtime.standard.BytesFunction; import dev.cel.runtime.standard.BytesFunction.BytesOverload; import dev.cel.runtime.standard.CelStandardFunction; +import dev.cel.runtime.standard.CelStandardOverload; import dev.cel.runtime.standard.ContainsFunction; import dev.cel.runtime.standard.ContainsFunction.ContainsOverload; import dev.cel.runtime.standard.DivideOperator; @@ -116,7 +117,7 @@ @Immutable public final class CelStandardFunctions { - private final ImmutableSet standardOverloads; + private final ImmutableSet standardOverloads; public static final ImmutableSet ALL_STANDARD_FUNCTIONS = ImmutableSet.of( @@ -163,8 +164,8 @@ public final class CelStandardFunctions { /** * Enumeration of Standard Function bindings. * - *

Note: The conditional, logical_or, logical_and, not_strictly_false, and type functions are - * currently special-cased, and does not appear in this enum. + *

Note: The conditional, logical_or, logical_and, and type functions are currently + * special-cased, and does not appear in this enum. */ public enum StandardFunction { LOGICAL_NOT(BooleanOperator.LOGICAL_NOT), @@ -331,7 +332,7 @@ public enum StandardFunction { public static final class Overload { /** Overloads for internal functions that may have been rewritten by macros (ex: @in) */ - public enum InternalOperator implements StandardOverload { + public enum InternalOperator implements CelStandardOverload { IN_LIST(InOverload.IN_LIST::newFunctionBinding), IN_MAP(InOverload.IN_MAP::newFunctionBinding); @@ -349,7 +350,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for functions that test relations. */ - public enum Relation implements StandardOverload { + public enum Relation implements CelStandardOverload { EQUALS(EqualsOverload.EQUALS::newFunctionBinding), NOT_EQUALS(NotEqualsOverload.NOT_EQUALS::newFunctionBinding); @@ -367,7 +368,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for performing arithmetic operations. */ - public enum Arithmetic implements StandardOverload { + public enum Arithmetic implements CelStandardOverload { ADD_INT64(AddOverload.ADD_INT64::newFunctionBinding), ADD_UINT64(AddOverload.ADD_UINT64::newFunctionBinding), ADD_BYTES(AddOverload.ADD_BYTES::newFunctionBinding), @@ -415,7 +416,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for indexing a list or a map. */ - public enum Index implements StandardOverload { + public enum Index implements CelStandardOverload { INDEX_LIST(IndexOverload.INDEX_LIST::newFunctionBinding), INDEX_MAP(IndexOverload.INDEX_MAP::newFunctionBinding); @@ -433,7 +434,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for retrieving the size of a literal or a collection. */ - public enum Size implements StandardOverload { + public enum Size implements CelStandardOverload { SIZE_BYTES(SizeOverload.SIZE_BYTES::newFunctionBinding), BYTES_SIZE(SizeOverload.BYTES_SIZE::newFunctionBinding), SIZE_LIST(SizeOverload.SIZE_LIST::newFunctionBinding), @@ -457,7 +458,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for performing type conversions. */ - public enum Conversions implements StandardOverload { + public enum Conversions implements CelStandardOverload { BOOL_TO_BOOL(BoolOverload.BOOL_TO_BOOL::newFunctionBinding), STRING_TO_BOOL(BoolOverload.STRING_TO_BOOL::newFunctionBinding), INT64_TO_INT64(IntOverload.INT64_TO_INT64::newFunctionBinding), @@ -514,7 +515,7 @@ public CelFunctionBinding newFunctionBinding( * Overloads for functions performing string matching, such as regular expressions or contains * check. */ - public enum StringMatchers implements StandardOverload { + public enum StringMatchers implements CelStandardOverload { MATCHES(MatchesOverload.MATCHES::newFunctionBinding), MATCHES_STRING(MatchesOverload.MATCHES_STRING::newFunctionBinding), CONTAINS_STRING(ContainsOverload.CONTAINS_STRING::newFunctionBinding), @@ -535,7 +536,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for logical operators that return a bool as a result. */ - public enum BooleanOperator implements StandardOverload { + public enum BooleanOperator implements CelStandardOverload { LOGICAL_NOT(LogicalNotOverload.LOGICAL_NOT::newFunctionBinding); private final FunctionBindingCreator bindingCreator; @@ -552,7 +553,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for functions performing date/time operations. */ - public enum DateTime implements StandardOverload { + public enum DateTime implements CelStandardOverload { TIMESTAMP_TO_YEAR(GetFullYearOverload.TIMESTAMP_TO_YEAR::newFunctionBinding), TIMESTAMP_TO_YEAR_WITH_TZ( GetFullYearOverload.TIMESTAMP_TO_YEAR_WITH_TZ::newFunctionBinding), @@ -605,7 +606,7 @@ public CelFunctionBinding newFunctionBinding( } /** Overloads for performing numeric comparisons. */ - public enum Comparison implements StandardOverload { + public enum Comparison implements CelStandardOverload { LESS_BOOL(LessOverload.LESS_BOOL::newFunctionBinding, false), LESS_INT64(LessOverload.LESS_INT64::newFunctionBinding, false), LESS_UINT64(LessOverload.LESS_UINT64::newFunctionBinding, false), @@ -702,20 +703,20 @@ public boolean isHeterogeneousComparison() { private Overload() {} } - private final ImmutableSet standardOverloads; + private final ImmutableSet standardOverloads; - StandardFunction(StandardOverload... overloads) { + StandardFunction(CelStandardOverload... overloads) { this.standardOverloads = ImmutableSet.copyOf(overloads); } @VisibleForTesting - ImmutableSet getOverloads() { + ImmutableSet getOverloads() { return standardOverloads; } } @VisibleForTesting - ImmutableSet getOverloads() { + ImmutableSet getOverloads() { return standardOverloads; } @@ -723,19 +724,13 @@ ImmutableSet getOverloads() { public ImmutableSet newFunctionBindings( RuntimeEquality runtimeEquality, CelOptions celOptions) { ImmutableSet.Builder builder = ImmutableSet.builder(); - for (StandardOverload overload : standardOverloads) { + for (CelStandardOverload overload : standardOverloads) { builder.add(overload.newFunctionBinding(celOptions, runtimeEquality)); } return builder.build(); } - /** General interface for defining a standard function overload. */ - @Immutable - public interface StandardOverload { - CelFunctionBinding newFunctionBinding(CelOptions celOptions, RuntimeEquality runtimeEquality); - } - /** Builder for constructing the set of standard function/identifiers. */ public static final class Builder { private ImmutableSet includeFunctions; @@ -805,7 +800,7 @@ public CelStandardFunctions build() { "You may only populate one of the following builder methods: includeFunctions," + " excludeFunctions or filterFunctions"); - ImmutableSet.Builder standardOverloadBuilder = ImmutableSet.builder(); + ImmutableSet.Builder standardOverloadBuilder = ImmutableSet.builder(); for (StandardFunction standardFunction : StandardFunction.values()) { if (hasIncludeFunctions) { if (this.includeFunctions.contains(standardFunction)) { @@ -820,15 +815,16 @@ public CelStandardFunctions build() { continue; } if (hasFilterFunction) { - ImmutableSet.Builder filteredOverloadsBuilder = ImmutableSet.builder(); - for (StandardOverload standardOverload : standardFunction.standardOverloads) { + ImmutableSet.Builder filteredOverloadsBuilder = + ImmutableSet.builder(); + for (CelStandardOverload standardOverload : standardFunction.standardOverloads) { boolean includeOverload = functionFilter.include(standardFunction, standardOverload); if (includeOverload) { standardOverloadBuilder.add(standardOverload); } } - ImmutableSet filteredOverloads = filteredOverloadsBuilder.build(); + ImmutableSet filteredOverloads = filteredOverloadsBuilder.build(); if (!filteredOverloads.isEmpty()) { standardOverloadBuilder.addAll(filteredOverloads); } @@ -848,7 +844,7 @@ public CelStandardFunctions build() { */ @FunctionalInterface public interface FunctionFilter { - boolean include(StandardFunction standardFunction, StandardOverload standardOverload); + boolean include(StandardFunction standardFunction, CelStandardOverload standardOverload); } } @@ -857,13 +853,7 @@ public static Builder newBuilder() { return new Builder(); } - @FunctionalInterface - @Immutable - private interface FunctionBindingCreator { - CelFunctionBinding create(CelOptions celOptions, RuntimeEquality runtimeEquality); - } - - private CelStandardFunctions(ImmutableSet standardOverloads) { + private CelStandardFunctions(ImmutableSet standardOverloads) { this.standardOverloads = standardOverloads; } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 1b89939ea..c4c27005e 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -14,9 +14,7 @@ package dev.cel.runtime; -import com.google.auto.value.AutoValue; import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.Immutable; import javax.annotation.concurrent.ThreadSafe; @@ -48,8 +46,7 @@ public synchronized void add( String overloadId, Class argType, final Registrar.UnaryFunction function) { overloads.put( overloadId, - ResolvedOverloadImpl.of( - overloadId, new Class[] {argType}, args -> function.apply((T) args[0]))); + CelResolvedOverload.of(overloadId, args -> function.apply((T) args[0]), argType)); } @Override @@ -61,18 +58,14 @@ public synchronized void add( final Registrar.BinaryFunction function) { overloads.put( overloadId, - ResolvedOverloadImpl.of( - overloadId, - new Class[] {argType1, argType2}, - args -> function.apply((T1) args[0], (T2) args[1]))); + CelResolvedOverload.of( + overloadId, args -> function.apply((T1) args[0], (T2) args[1]), argType1, argType2)); } @Override public synchronized void add( String overloadId, List> argTypes, Registrar.Function function) { - overloads.put( - overloadId, - ResolvedOverloadImpl.of(overloadId, argTypes.toArray(new Class[0]), function)); + overloads.put(overloadId, CelResolvedOverload.of(overloadId, function, argTypes)); } @Override @@ -144,31 +137,4 @@ public Dispatcher.ImmutableCopy immutableCopy() { } private DefaultDispatcher() {} - - @AutoValue - @Immutable - abstract static class ResolvedOverloadImpl implements ResolvedOverload { - /** The overload id of the function. */ - @Override - public abstract String getOverloadId(); - - /** The types of the function parameters. */ - @Override - public abstract ImmutableList> getParameterTypes(); - - /** The function definition. */ - @Override - public abstract FunctionOverload getDefinition(); - - static ResolvedOverload of( - String overloadId, Class[] parameterTypes, FunctionOverload definition) { - return of(overloadId, ImmutableList.copyOf(parameterTypes), definition); - } - - static ResolvedOverload of( - String overloadId, ImmutableList> parameterTypes, FunctionOverload definition) { - return new AutoValue_DefaultDispatcher_ResolvedOverloadImpl( - overloadId, parameterTypes, definition); - } - } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index afdca0e3e..b471d972e 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -154,7 +154,7 @@ public Object eval(GlobalResolver resolver, CelEvaluationListener listener) } @Override - public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { return evalTrackingUnknowns( RuntimeUnknownResolver.fromResolver(resolver), @@ -165,7 +165,7 @@ public Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionRe @Override public Object eval( GlobalResolver resolver, - FunctionResolver lateBoundFunctionResolver, + CelFunctionResolver lateBoundFunctionResolver, CelEvaluationListener listener) throws CelEvaluationException { return evalTrackingUnknowns( @@ -177,7 +177,7 @@ public Object eval( @Override public Object evalTrackingUnknowns( RuntimeUnknownResolver resolver, - Optional functionResolver, + Optional functionResolver, Optional listener) throws CelEvaluationException { ExecutionFrame frame = newExecutionFrame(resolver, functionResolver, listener); @@ -223,7 +223,7 @@ ExecutionFrame newTestExecutionFrame(GlobalResolver resolver) { private ExecutionFrame newExecutionFrame( RuntimeUnknownResolver resolver, - Optional functionResolver, + Optional functionResolver, Optional listener) { int comprehensionMaxIterations = celOptions.enableComprehension() ? celOptions.comprehensionMaxIterations() : 0; @@ -1105,7 +1105,7 @@ static class ExecutionFrame { private final Optional evaluationListener; private final int maxIterations; private final ArrayDeque resolvers; - private final Optional lateBoundFunctionResolver; + private final Optional lateBoundFunctionResolver; private RuntimeUnknownResolver currentResolver; private int iterations; @VisibleForTesting int scopeLevel; @@ -1113,7 +1113,7 @@ static class ExecutionFrame { private ExecutionFrame( Optional evaluationListener, RuntimeUnknownResolver resolver, - Optional lateBoundFunctionResolver, + Optional lateBoundFunctionResolver, int maxIterations) { this.evaluationListener = evaluationListener; this.resolvers = new ArrayDeque<>(); diff --git a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java index 017e76685..e7bc6163f 100644 --- a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java @@ -25,7 +25,7 @@ */ @ThreadSafe @Internal -interface Dispatcher extends FunctionResolver { +interface Dispatcher extends CelFunctionResolver { /** * Returns an {@link ImmutableCopy} from current instance. diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java deleted file mode 100644 index bf825f004..000000000 --- a/runtime/src/main/java/dev/cel/runtime/FunctionOverload.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2024 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.runtime; - -import com.google.errorprone.annotations.Immutable; - -/** Interface describing the general signature of all CEL custom function implementations. */ -@FunctionalInterface -@Immutable -interface FunctionOverload { - - /** Evaluate a set of arguments throwing a {@code CelException} on error. */ - Object apply(Object[] args) throws CelEvaluationException; - - /** - * Helper interface for describing unary functions where the type-parameter is used to improve - * compile-time correctness of function bindings. - */ - @Immutable - @FunctionalInterface - interface Unary { - Object apply(T arg) throws CelEvaluationException; - } - - /** - * Helper interface for describing binary functions where the type parameters are used to improve - * compile-time correctness of function bindings. - */ - @Immutable - @FunctionalInterface - interface Binary { - Object apply(T1 arg1, T2 arg2) throws CelEvaluationException; - } -} diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionResolver.java b/runtime/src/main/java/dev/cel/runtime/FunctionResolver.java deleted file mode 100644 index 888780901..000000000 --- a/runtime/src/main/java/dev/cel/runtime/FunctionResolver.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2024 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.runtime; - -import javax.annotation.concurrent.ThreadSafe; -import dev.cel.common.annotations.Internal; -import java.util.List; -import java.util.Optional; - -/** - * Interface to a resolver for CEL functions based on the function name, overload ids, and - * arguments. - * - *

CEL Library Internals. Do Not Use. - */ -@ThreadSafe -@Internal -interface FunctionResolver { - - /** - * Finds a specific function overload to invoke based on given parameters. - * - * @param functionName the logical name of the function being invoked. - * @param overloadIds A list of function overload ids. The dispatcher selects the unique overload - * from this list with matching arguments. - * @param args The arguments to pass to the function. - * @return an optional value of the resolved overload. - * @throws CelEvaluationException if the overload resolution is ambiguous, - */ - Optional findOverload( - String functionName, List overloadIds, Object[] args) throws CelEvaluationException; -} diff --git a/runtime/src/main/java/dev/cel/runtime/Interpretable.java b/runtime/src/main/java/dev/cel/runtime/Interpretable.java index 21e95921d..ece90cb4b 100644 --- a/runtime/src/main/java/dev/cel/runtime/Interpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/Interpretable.java @@ -45,7 +45,7 @@ Object eval(GlobalResolver resolver, CelEvaluationListener listener) * directly such as recording telemetry or evaluation state in a more granular fashion than a more * general evaluation listener might permit. */ - Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) + Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException; /** @@ -58,7 +58,7 @@ Object eval(GlobalResolver resolver, FunctionResolver lateBoundFunctionResolver) */ Object eval( GlobalResolver resolver, - FunctionResolver lateBoundFunctionResolver, + CelFunctionResolver lateBoundFunctionResolver, CelEvaluationListener listener) throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/Registrar.java b/runtime/src/main/java/dev/cel/runtime/Registrar.java index 3a3afa1d0..12dbe6cb3 100644 --- a/runtime/src/main/java/dev/cel/runtime/Registrar.java +++ b/runtime/src/main/java/dev/cel/runtime/Registrar.java @@ -28,21 +28,21 @@ public interface Registrar { /** Interface describing the general signature of all CEL custom function implementations. */ @Immutable - interface Function extends FunctionOverload {} + interface Function extends CelFunctionOverload {} /** * Helper interface for describing unary functions where the type-parameter is used to improve * compile-time correctness of function bindings. */ @Immutable - interface UnaryFunction extends FunctionOverload.Unary {} + interface UnaryFunction extends CelFunctionOverload.Unary {} /** * Helper interface for describing binary functions where the type parameters are used to improve * compile-time correctness of function bindings. */ @Immutable - interface BinaryFunction extends FunctionOverload.Binary {} + interface BinaryFunction extends CelFunctionOverload.Binary {} /** Adds a unary function to the dispatcher. */ void add(String overloadId, Class argType, UnaryFunction function); diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index f71748814..bc7544199 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -33,7 +33,7 @@ interface ResolvedOverload { List> getParameterTypes(); /** The function definition. */ - FunctionOverload getDefinition(); + CelFunctionOverload getDefinition(); /** * Returns true if the overload's expected argument types match the types of the given arguments. diff --git a/runtime/src/main/java/dev/cel/runtime/UnknownTrackingInterpretable.java b/runtime/src/main/java/dev/cel/runtime/UnknownTrackingInterpretable.java index 9a1a4964a..8422910a1 100644 --- a/runtime/src/main/java/dev/cel/runtime/UnknownTrackingInterpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/UnknownTrackingInterpretable.java @@ -35,7 +35,7 @@ public interface UnknownTrackingInterpretable { */ Object evalTrackingUnknowns( RuntimeUnknownResolver resolver, - Optional lateBoundFunctionResolver, + Optional lateBoundFunctionResolver, Optional listener) throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index 08177a474..851f375a4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -1422,7 +1422,8 @@ cel_android_library( java_library( name = "standard_overload", srcs = ["CelStandardOverload.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ "//common:options", "//runtime:function_binding", @@ -1434,7 +1435,8 @@ java_library( cel_android_library( name = "standard_overload_android", srcs = ["CelStandardOverload.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ "//common:options", "//runtime:function_binding_android", diff --git a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java index 2b4e385db..7c6599737 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java @@ -25,10 +25,12 @@ * overload. */ @Immutable -interface CelStandardOverload { +public interface CelStandardOverload { + /** Constructs a new {@link CelFunctionBinding} for this CEL standard overload. */ CelFunctionBinding newFunctionBinding(CelOptions celOptions, RuntimeEquality runtimeEquality); + /** TODO: To be removed in the upcoming CL */ @FunctionalInterface @Immutable interface FunctionBindingCreator { diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 5137cad39..81e206fdb 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -65,7 +65,6 @@ java_library( "//runtime:evaluation_exception_builder", "//runtime:evaluation_listener", "//runtime:function_binding", - "//runtime:function_overload_impl", "//runtime:interpretable", "//runtime:interpreter", "//runtime:interpreter_util", @@ -75,12 +74,15 @@ java_library( "//runtime:proto_message_activation_factory", "//runtime:proto_message_runtime_equality", "//runtime:proto_message_runtime_helpers", + "//runtime:resolved_overload", + "//runtime:resolved_overload_internal", "//runtime:runtime_equality", "//runtime:runtime_helpers", "//runtime:standard_functions", "//runtime:type_resolver", "//runtime:unknown_attributes", "//runtime:unknown_options", + "//runtime/standard:standard_overload", "//testing/protos:message_with_enum_cel_java_proto", "//testing/protos:message_with_enum_java_proto", "//testing/protos:multi_file_cel_java_proto", diff --git a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java index 8b0fd7193..2c81b89d4 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java @@ -16,7 +16,6 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.common.collect.ImmutableList; import dev.cel.expr.conformance.proto3.TestAllTypes; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,11 +28,11 @@ public final class CelResolvedOverloadTest { CelResolvedOverload getIncrementIntOverload() { return CelResolvedOverload.of( "increment_int", - ImmutableList.of(Long.class), (args) -> { Long arg = (Long) args[0]; return arg + 1; - }); + }, + Long.class); } @Test @@ -44,14 +43,14 @@ public void canHandle_matchingTypes_returnsTrue() { @Test public void canHandle_nullMessageType_returnsTrue() { CelResolvedOverload overload = - CelResolvedOverload.of("identity", ImmutableList.of(TestAllTypes.class), (args) -> args[0]); + CelResolvedOverload.of("identity", (args) -> args[0], TestAllTypes.class); assertThat(overload.canHandle(new Object[] {null})).isTrue(); } @Test public void canHandle_nullPrimitive_returnsFalse() { CelResolvedOverload overload = - CelResolvedOverload.of("identity", ImmutableList.of(Long.class), (args) -> args[0]); + CelResolvedOverload.of("identity", (args) -> args[0], Long.class); assertThat(overload.canHandle(new Object[] {null})).isFalse(); } diff --git a/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java b/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java index 09da78615..eac68fb63 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java @@ -25,7 +25,7 @@ import dev.cel.compiler.CelCompilerFactory; import dev.cel.runtime.CelStandardFunctions.StandardFunction; import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Arithmetic; -import dev.cel.runtime.CelStandardFunctions.StandardOverload; +import dev.cel.runtime.standard.CelStandardOverload; import org.junit.Test; import org.junit.runner.RunWith; @@ -90,7 +90,7 @@ public void standardFunctions_includeFunctions() { assertThat(celStandardFunctions.getOverloads()) .containsExactlyElementsIn( - ImmutableSet.builder() + ImmutableSet.builder() .addAll(CelStandardFunctions.StandardFunction.ADD.getOverloads()) .addAll(CelStandardFunctions.StandardFunction.SUBTRACT.getOverloads()) .build()); diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java index 556d6945c..03eba51ce 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java @@ -35,13 +35,9 @@ public final class DefaultDispatcherTest { public void setup() { overloads = new HashMap<>(); overloads.put( - "overload_1", - CelResolvedOverload.of( - "overload_1", new Class[] {Long.class}, args -> (Long) args[0] + 1)); + "overload_1", CelResolvedOverload.of("overload_1", args -> (Long) args[0] + 1, Long.class)); overloads.put( - "overload_2", - CelResolvedOverload.of( - "overload_2", new Class[] {Long.class}, args -> (Long) args[0] + 2)); + "overload_2", CelResolvedOverload.of("overload_2", args -> (Long) args[0] + 2, Long.class)); } @Test diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index 5f84c105f..a69f69887 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -405,3 +405,13 @@ cel_android_library( name = "uint_android", exports = ["//runtime/src/main/java/dev/cel/runtime/standard:uint_android"], ) + +java_library( + name = "standard_overload", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:standard_overload"], +) + +cel_android_library( + name = "standard_overload_android", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:standard_overload_android"], +) From 741e5d0df4585e39a8baad1d019ad93d09b3bb89 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 31 Oct 2025 15:34:05 -0700 Subject: [PATCH 006/100] Remove redundant FunctionBindingCreator interface, consolidate with CelStandardOverload PiperOrigin-RevId: 826652181 --- .../dev/cel/runtime/CelStandardFunctions.java | 453 ++++++++---------- .../dev/cel/runtime/standard/AddOperator.java | 8 +- .../cel/runtime/standard/BoolFunction.java | 8 +- .../cel/runtime/standard/BytesFunction.java | 8 +- .../runtime/standard/CelStandardOverload.java | 8 +- .../runtime/standard/ContainsFunction.java | 8 +- .../cel/runtime/standard/DivideOperator.java | 8 +- .../cel/runtime/standard/DoubleFunction.java | 8 +- .../runtime/standard/DurationFunction.java | 8 +- .../dev/cel/runtime/standard/DynFunction.java | 8 +- .../runtime/standard/EndsWithFunction.java | 8 +- .../cel/runtime/standard/EqualsOperator.java | 8 +- .../cel/runtime/standard/GetDateFunction.java | 8 +- .../standard/GetDayOfMonthFunction.java | 8 +- .../standard/GetDayOfWeekFunction.java | 8 +- .../standard/GetDayOfYearFunction.java | 8 +- .../runtime/standard/GetFullYearFunction.java | 8 +- .../runtime/standard/GetHoursFunction.java | 8 +- .../standard/GetMillisecondsFunction.java | 8 +- .../runtime/standard/GetMinutesFunction.java | 8 +- .../runtime/standard/GetMonthFunction.java | 8 +- .../runtime/standard/GetSecondsFunction.java | 8 +- .../standard/GreaterEqualsOperator.java | 8 +- .../cel/runtime/standard/GreaterOperator.java | 8 +- .../dev/cel/runtime/standard/InOperator.java | 8 +- .../cel/runtime/standard/IndexOperator.java | 8 +- .../dev/cel/runtime/standard/IntFunction.java | 8 +- .../runtime/standard/LessEqualsOperator.java | 8 +- .../cel/runtime/standard/LessOperator.java | 8 +- .../runtime/standard/LogicalNotOperator.java | 8 +- .../cel/runtime/standard/MatchesFunction.java | 8 +- .../cel/runtime/standard/ModuloOperator.java | 8 +- .../runtime/standard/MultiplyOperator.java | 8 +- .../cel/runtime/standard/NegateOperator.java | 8 +- .../runtime/standard/NotEqualsOperator.java | 8 +- .../cel/runtime/standard/SizeFunction.java | 8 +- .../runtime/standard/StartsWithFunction.java | 8 +- .../cel/runtime/standard/StringFunction.java | 8 +- .../runtime/standard/SubtractOperator.java | 8 +- .../runtime/standard/TimestampFunction.java | 8 +- .../cel/runtime/standard/UintFunction.java | 8 +- 41 files changed, 369 insertions(+), 404 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 7d781d4af..6742dc42f 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -333,181 +333,179 @@ public static final class Overload { /** Overloads for internal functions that may have been rewritten by macros (ex: @in) */ public enum InternalOperator implements CelStandardOverload { - IN_LIST(InOverload.IN_LIST::newFunctionBinding), - IN_MAP(InOverload.IN_MAP::newFunctionBinding); + IN_LIST(InOverload.IN_LIST), + IN_MAP(InOverload.IN_MAP); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - InternalOperator(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + InternalOperator(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for functions that test relations. */ public enum Relation implements CelStandardOverload { - EQUALS(EqualsOverload.EQUALS::newFunctionBinding), - NOT_EQUALS(NotEqualsOverload.NOT_EQUALS::newFunctionBinding); + EQUALS(EqualsOverload.EQUALS), + NOT_EQUALS(NotEqualsOverload.NOT_EQUALS); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - Relation(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + Relation(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for performing arithmetic operations. */ public enum Arithmetic implements CelStandardOverload { - ADD_INT64(AddOverload.ADD_INT64::newFunctionBinding), - ADD_UINT64(AddOverload.ADD_UINT64::newFunctionBinding), - ADD_BYTES(AddOverload.ADD_BYTES::newFunctionBinding), - ADD_DOUBLE(AddOverload.ADD_DOUBLE::newFunctionBinding), - ADD_DURATION_DURATION(AddOverload.ADD_DURATION_DURATION::newFunctionBinding), - ADD_TIMESTAMP_DURATION(AddOverload.ADD_TIMESTAMP_DURATION::newFunctionBinding), - ADD_STRING(AddOverload.ADD_STRING::newFunctionBinding), - ADD_DURATION_TIMESTAMP(AddOverload.ADD_DURATION_TIMESTAMP::newFunctionBinding), - ADD_LIST(AddOverload.ADD_LIST::newFunctionBinding), - - SUBTRACT_INT64(SubtractOverload.SUBTRACT_INT64::newFunctionBinding), - SUBTRACT_TIMESTAMP_TIMESTAMP( - SubtractOverload.SUBTRACT_TIMESTAMP_TIMESTAMP::newFunctionBinding), - SUBTRACT_TIMESTAMP_DURATION( - SubtractOverload.SUBTRACT_TIMESTAMP_DURATION::newFunctionBinding), - SUBTRACT_UINT64(SubtractOverload.SUBTRACT_UINT64::newFunctionBinding), - SUBTRACT_DOUBLE(SubtractOverload.SUBTRACT_DOUBLE::newFunctionBinding), - SUBTRACT_DURATION_DURATION(SubtractOverload.SUBTRACT_DURATION_DURATION::newFunctionBinding), - - MULTIPLY_INT64(MultiplyOverload.MULTIPLY_INT64::newFunctionBinding), - MULTIPLY_DOUBLE(MultiplyOverload.MULTIPLY_DOUBLE::newFunctionBinding), - MULTIPLY_UINT64(MultiplyOverload.MULTIPLY_UINT64::newFunctionBinding), - - DIVIDE_DOUBLE(DivideOverload.DIVIDE_DOUBLE::newFunctionBinding), - DIVIDE_INT64(DivideOverload.DIVIDE_INT64::newFunctionBinding), - DIVIDE_UINT64(DivideOverload.DIVIDE_UINT64::newFunctionBinding), - - MODULO_INT64(ModuloOverload.MODULO_INT64::newFunctionBinding), - MODULO_UINT64(ModuloOverload.MODULO_UINT64::newFunctionBinding), - - NEGATE_INT64(NegateOverload.NEGATE_INT64::newFunctionBinding), - NEGATE_DOUBLE(NegateOverload.NEGATE_DOUBLE::newFunctionBinding); - - private final FunctionBindingCreator bindingCreator; + ADD_INT64(AddOverload.ADD_INT64), + ADD_UINT64(AddOverload.ADD_UINT64), + ADD_BYTES(AddOverload.ADD_BYTES), + ADD_DOUBLE(AddOverload.ADD_DOUBLE), + ADD_DURATION_DURATION(AddOverload.ADD_DURATION_DURATION), + ADD_TIMESTAMP_DURATION(AddOverload.ADD_TIMESTAMP_DURATION), + ADD_STRING(AddOverload.ADD_STRING), + ADD_DURATION_TIMESTAMP(AddOverload.ADD_DURATION_TIMESTAMP), + ADD_LIST(AddOverload.ADD_LIST), + + SUBTRACT_INT64(SubtractOverload.SUBTRACT_INT64), + SUBTRACT_TIMESTAMP_TIMESTAMP(SubtractOverload.SUBTRACT_TIMESTAMP_TIMESTAMP), + SUBTRACT_TIMESTAMP_DURATION(SubtractOverload.SUBTRACT_TIMESTAMP_DURATION), + SUBTRACT_UINT64(SubtractOverload.SUBTRACT_UINT64), + SUBTRACT_DOUBLE(SubtractOverload.SUBTRACT_DOUBLE), + SUBTRACT_DURATION_DURATION(SubtractOverload.SUBTRACT_DURATION_DURATION), + + MULTIPLY_INT64(MultiplyOverload.MULTIPLY_INT64), + MULTIPLY_DOUBLE(MultiplyOverload.MULTIPLY_DOUBLE), + MULTIPLY_UINT64(MultiplyOverload.MULTIPLY_UINT64), + + DIVIDE_DOUBLE(DivideOverload.DIVIDE_DOUBLE), + DIVIDE_INT64(DivideOverload.DIVIDE_INT64), + DIVIDE_UINT64(DivideOverload.DIVIDE_UINT64), + + MODULO_INT64(ModuloOverload.MODULO_INT64), + MODULO_UINT64(ModuloOverload.MODULO_UINT64), + + NEGATE_INT64(NegateOverload.NEGATE_INT64), + NEGATE_DOUBLE(NegateOverload.NEGATE_DOUBLE); + + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - Arithmetic(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + Arithmetic(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for indexing a list or a map. */ public enum Index implements CelStandardOverload { - INDEX_LIST(IndexOverload.INDEX_LIST::newFunctionBinding), - INDEX_MAP(IndexOverload.INDEX_MAP::newFunctionBinding); + INDEX_LIST(IndexOverload.INDEX_LIST), + INDEX_MAP(IndexOverload.INDEX_MAP); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - Index(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + Index(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for retrieving the size of a literal or a collection. */ public enum Size implements CelStandardOverload { - SIZE_BYTES(SizeOverload.SIZE_BYTES::newFunctionBinding), - BYTES_SIZE(SizeOverload.BYTES_SIZE::newFunctionBinding), - SIZE_LIST(SizeOverload.SIZE_LIST::newFunctionBinding), - LIST_SIZE(SizeOverload.LIST_SIZE::newFunctionBinding), - SIZE_STRING(SizeOverload.SIZE_STRING::newFunctionBinding), - STRING_SIZE(SizeOverload.STRING_SIZE::newFunctionBinding), - SIZE_MAP(SizeOverload.SIZE_MAP::newFunctionBinding), - MAP_SIZE(SizeOverload.MAP_SIZE::newFunctionBinding); + SIZE_BYTES(SizeOverload.SIZE_BYTES), + BYTES_SIZE(SizeOverload.BYTES_SIZE), + SIZE_LIST(SizeOverload.SIZE_LIST), + LIST_SIZE(SizeOverload.LIST_SIZE), + SIZE_STRING(SizeOverload.SIZE_STRING), + STRING_SIZE(SizeOverload.STRING_SIZE), + SIZE_MAP(SizeOverload.SIZE_MAP), + MAP_SIZE(SizeOverload.MAP_SIZE); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - Size(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + Size(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for performing type conversions. */ public enum Conversions implements CelStandardOverload { - BOOL_TO_BOOL(BoolOverload.BOOL_TO_BOOL::newFunctionBinding), - STRING_TO_BOOL(BoolOverload.STRING_TO_BOOL::newFunctionBinding), - INT64_TO_INT64(IntOverload.INT64_TO_INT64::newFunctionBinding), - DOUBLE_TO_INT64(IntOverload.DOUBLE_TO_INT64::newFunctionBinding), - STRING_TO_INT64(IntOverload.STRING_TO_INT64::newFunctionBinding), - TIMESTAMP_TO_INT64(IntOverload.TIMESTAMP_TO_INT64::newFunctionBinding), - UINT64_TO_INT64(IntOverload.UINT64_TO_INT64::newFunctionBinding), - - UINT64_TO_UINT64(UintOverload.UINT64_TO_UINT64::newFunctionBinding), - INT64_TO_UINT64(UintOverload.INT64_TO_UINT64::newFunctionBinding), - DOUBLE_TO_UINT64(UintOverload.DOUBLE_TO_UINT64::newFunctionBinding), - STRING_TO_UINT64(UintOverload.STRING_TO_UINT64::newFunctionBinding), - - DOUBLE_TO_DOUBLE(DoubleOverload.DOUBLE_TO_DOUBLE::newFunctionBinding), - INT64_TO_DOUBLE(DoubleOverload.INT64_TO_DOUBLE::newFunctionBinding), - STRING_TO_DOUBLE(DoubleOverload.STRING_TO_DOUBLE::newFunctionBinding), - UINT64_TO_DOUBLE(DoubleOverload.UINT64_TO_DOUBLE::newFunctionBinding), - - STRING_TO_STRING(StringOverload.STRING_TO_STRING::newFunctionBinding), - INT64_TO_STRING(StringOverload.INT64_TO_STRING::newFunctionBinding), - DOUBLE_TO_STRING(StringOverload.DOUBLE_TO_STRING::newFunctionBinding), - BOOL_TO_STRING(StringOverload.BOOL_TO_STRING::newFunctionBinding), - BYTES_TO_STRING(StringOverload.BYTES_TO_STRING::newFunctionBinding), - TIMESTAMP_TO_STRING(StringOverload.TIMESTAMP_TO_STRING::newFunctionBinding), - DURATION_TO_STRING(StringOverload.DURATION_TO_STRING::newFunctionBinding), - UINT64_TO_STRING(StringOverload.UINT64_TO_STRING::newFunctionBinding), - - BYTES_TO_BYTES(BytesOverload.BYTES_TO_BYTES::newFunctionBinding), - STRING_TO_BYTES(BytesOverload.STRING_TO_BYTES::newFunctionBinding), - - DURATION_TO_DURATION(DurationOverload.DURATION_TO_DURATION::newFunctionBinding), - STRING_TO_DURATION(DurationOverload.STRING_TO_DURATION::newFunctionBinding), - - STRING_TO_TIMESTAMP(TimestampOverload.STRING_TO_TIMESTAMP::newFunctionBinding), - TIMESTAMP_TO_TIMESTAMP(TimestampOverload.TIMESTAMP_TO_TIMESTAMP::newFunctionBinding), - INT64_TO_TIMESTAMP(TimestampOverload.INT64_TO_TIMESTAMP::newFunctionBinding), - - TO_DYN(DynOverload.TO_DYN::newFunctionBinding); - - private final FunctionBindingCreator bindingCreator; + BOOL_TO_BOOL(BoolOverload.BOOL_TO_BOOL), + STRING_TO_BOOL(BoolOverload.STRING_TO_BOOL), + INT64_TO_INT64(IntOverload.INT64_TO_INT64), + DOUBLE_TO_INT64(IntOverload.DOUBLE_TO_INT64), + STRING_TO_INT64(IntOverload.STRING_TO_INT64), + TIMESTAMP_TO_INT64(IntOverload.TIMESTAMP_TO_INT64), + UINT64_TO_INT64(IntOverload.UINT64_TO_INT64), + + UINT64_TO_UINT64(UintOverload.UINT64_TO_UINT64), + INT64_TO_UINT64(UintOverload.INT64_TO_UINT64), + DOUBLE_TO_UINT64(UintOverload.DOUBLE_TO_UINT64), + STRING_TO_UINT64(UintOverload.STRING_TO_UINT64), + + DOUBLE_TO_DOUBLE(DoubleOverload.DOUBLE_TO_DOUBLE), + INT64_TO_DOUBLE(DoubleOverload.INT64_TO_DOUBLE), + STRING_TO_DOUBLE(DoubleOverload.STRING_TO_DOUBLE), + UINT64_TO_DOUBLE(DoubleOverload.UINT64_TO_DOUBLE), + + STRING_TO_STRING(StringOverload.STRING_TO_STRING), + INT64_TO_STRING(StringOverload.INT64_TO_STRING), + DOUBLE_TO_STRING(StringOverload.DOUBLE_TO_STRING), + BOOL_TO_STRING(StringOverload.BOOL_TO_STRING), + BYTES_TO_STRING(StringOverload.BYTES_TO_STRING), + TIMESTAMP_TO_STRING(StringOverload.TIMESTAMP_TO_STRING), + DURATION_TO_STRING(StringOverload.DURATION_TO_STRING), + UINT64_TO_STRING(StringOverload.UINT64_TO_STRING), + + BYTES_TO_BYTES(BytesOverload.BYTES_TO_BYTES), + STRING_TO_BYTES(BytesOverload.STRING_TO_BYTES), + + DURATION_TO_DURATION(DurationOverload.DURATION_TO_DURATION), + STRING_TO_DURATION(DurationOverload.STRING_TO_DURATION), + + STRING_TO_TIMESTAMP(TimestampOverload.STRING_TO_TIMESTAMP), + TIMESTAMP_TO_TIMESTAMP(TimestampOverload.TIMESTAMP_TO_TIMESTAMP), + INT64_TO_TIMESTAMP(TimestampOverload.INT64_TO_TIMESTAMP), + + TO_DYN(DynOverload.TO_DYN); + + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - Conversions(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + Conversions(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } @@ -516,182 +514,155 @@ public CelFunctionBinding newFunctionBinding( * check. */ public enum StringMatchers implements CelStandardOverload { - MATCHES(MatchesOverload.MATCHES::newFunctionBinding), - MATCHES_STRING(MatchesOverload.MATCHES_STRING::newFunctionBinding), - CONTAINS_STRING(ContainsOverload.CONTAINS_STRING::newFunctionBinding), - ENDS_WITH_STRING(EndsWithOverload.ENDS_WITH_STRING::newFunctionBinding), - STARTS_WITH_STRING(StartsWithOverload.STARTS_WITH_STRING::newFunctionBinding); + MATCHES(MatchesOverload.MATCHES), + MATCHES_STRING(MatchesOverload.MATCHES_STRING), + CONTAINS_STRING(ContainsOverload.CONTAINS_STRING), + ENDS_WITH_STRING(EndsWithOverload.ENDS_WITH_STRING), + STARTS_WITH_STRING(StartsWithOverload.STARTS_WITH_STRING); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - StringMatchers(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + StringMatchers(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for logical operators that return a bool as a result. */ public enum BooleanOperator implements CelStandardOverload { - LOGICAL_NOT(LogicalNotOverload.LOGICAL_NOT::newFunctionBinding); + LOGICAL_NOT(LogicalNotOverload.LOGICAL_NOT); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - BooleanOperator(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + BooleanOperator(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for functions performing date/time operations. */ public enum DateTime implements CelStandardOverload { - TIMESTAMP_TO_YEAR(GetFullYearOverload.TIMESTAMP_TO_YEAR::newFunctionBinding), - TIMESTAMP_TO_YEAR_WITH_TZ( - GetFullYearOverload.TIMESTAMP_TO_YEAR_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_MONTH(GetMonthOverload.TIMESTAMP_TO_MONTH::newFunctionBinding), - TIMESTAMP_TO_MONTH_WITH_TZ(GetMonthOverload.TIMESTAMP_TO_MONTH_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_DAY_OF_YEAR(GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR::newFunctionBinding), - TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ( - GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_DAY_OF_MONTH( - GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH::newFunctionBinding), - TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ( - GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_DAY_OF_MONTH_1_BASED( - GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED::newFunctionBinding), + TIMESTAMP_TO_YEAR(GetFullYearOverload.TIMESTAMP_TO_YEAR), + TIMESTAMP_TO_YEAR_WITH_TZ(GetFullYearOverload.TIMESTAMP_TO_YEAR_WITH_TZ), + TIMESTAMP_TO_MONTH(GetMonthOverload.TIMESTAMP_TO_MONTH), + TIMESTAMP_TO_MONTH_WITH_TZ(GetMonthOverload.TIMESTAMP_TO_MONTH_WITH_TZ), + TIMESTAMP_TO_DAY_OF_YEAR(GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR), + TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ(GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ), + TIMESTAMP_TO_DAY_OF_MONTH(GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH), + TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ(GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ), + TIMESTAMP_TO_DAY_OF_MONTH_1_BASED(GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED), TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ( - GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ::newFunctionBinding), - - TIMESTAMP_TO_DAY_OF_WEEK(GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK::newFunctionBinding), - TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ( - GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_HOURS(GetHoursOverload.TIMESTAMP_TO_HOURS::newFunctionBinding), - TIMESTAMP_TO_HOURS_WITH_TZ(GetHoursOverload.TIMESTAMP_TO_HOURS_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_MINUTES(GetMinutesOverload.TIMESTAMP_TO_MINUTES::newFunctionBinding), - TIMESTAMP_TO_MINUTES_WITH_TZ( - GetMinutesOverload.TIMESTAMP_TO_MINUTES_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_SECONDS(GetSecondsOverload.TIMESTAMP_TO_SECONDS::newFunctionBinding), - TIMESTAMP_TO_SECONDS_WITH_TZ( - GetSecondsOverload.TIMESTAMP_TO_SECONDS_WITH_TZ::newFunctionBinding), - TIMESTAMP_TO_MILLISECONDS( - GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS::newFunctionBinding), + GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ), + + TIMESTAMP_TO_DAY_OF_WEEK(GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK), + TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ(GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ), + TIMESTAMP_TO_HOURS(GetHoursOverload.TIMESTAMP_TO_HOURS), + TIMESTAMP_TO_HOURS_WITH_TZ(GetHoursOverload.TIMESTAMP_TO_HOURS_WITH_TZ), + TIMESTAMP_TO_MINUTES(GetMinutesOverload.TIMESTAMP_TO_MINUTES), + TIMESTAMP_TO_MINUTES_WITH_TZ(GetMinutesOverload.TIMESTAMP_TO_MINUTES_WITH_TZ), + TIMESTAMP_TO_SECONDS(GetSecondsOverload.TIMESTAMP_TO_SECONDS), + TIMESTAMP_TO_SECONDS_WITH_TZ(GetSecondsOverload.TIMESTAMP_TO_SECONDS_WITH_TZ), + TIMESTAMP_TO_MILLISECONDS(GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS), TIMESTAMP_TO_MILLISECONDS_WITH_TZ( - GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS_WITH_TZ::newFunctionBinding), - DURATION_TO_HOURS(GetHoursOverload.DURATION_TO_HOURS::newFunctionBinding), - DURATION_TO_MINUTES(GetMinutesOverload.DURATION_TO_MINUTES::newFunctionBinding), - DURATION_TO_SECONDS(GetSecondsOverload.DURATION_TO_SECONDS::newFunctionBinding), - DURATION_TO_MILLISECONDS( - GetMillisecondsOverload.DURATION_TO_MILLISECONDS::newFunctionBinding); + GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS_WITH_TZ), + DURATION_TO_HOURS(GetHoursOverload.DURATION_TO_HOURS), + DURATION_TO_MINUTES(GetMinutesOverload.DURATION_TO_MINUTES), + DURATION_TO_SECONDS(GetSecondsOverload.DURATION_TO_SECONDS), + DURATION_TO_MILLISECONDS(GetMillisecondsOverload.DURATION_TO_MILLISECONDS); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - DateTime(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + DateTime(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } /** Overloads for performing numeric comparisons. */ public enum Comparison implements CelStandardOverload { - LESS_BOOL(LessOverload.LESS_BOOL::newFunctionBinding, false), - LESS_INT64(LessOverload.LESS_INT64::newFunctionBinding, false), - LESS_UINT64(LessOverload.LESS_UINT64::newFunctionBinding, false), - LESS_BYTES(LessOverload.LESS_BYTES::newFunctionBinding, false), - LESS_DOUBLE(LessOverload.LESS_DOUBLE::newFunctionBinding, false), - LESS_DOUBLE_UINT64(LessOverload.LESS_DOUBLE_UINT64::newFunctionBinding, true), - LESS_INT64_UINT64(LessOverload.LESS_INT64_UINT64::newFunctionBinding, true), - LESS_UINT64_INT64(LessOverload.LESS_UINT64_INT64::newFunctionBinding, true), - LESS_INT64_DOUBLE(LessOverload.LESS_INT64_DOUBLE::newFunctionBinding, true), - LESS_DOUBLE_INT64(LessOverload.LESS_DOUBLE_INT64::newFunctionBinding, true), - LESS_UINT64_DOUBLE(LessOverload.LESS_UINT64_DOUBLE::newFunctionBinding, true), - LESS_DURATION(LessOverload.LESS_DURATION::newFunctionBinding, false), - LESS_STRING(LessOverload.LESS_STRING::newFunctionBinding, false), - LESS_TIMESTAMP(LessOverload.LESS_TIMESTAMP::newFunctionBinding, false), - LESS_EQUALS_BOOL(LessEqualsOverload.LESS_EQUALS_BOOL::newFunctionBinding, false), - LESS_EQUALS_BYTES(LessEqualsOverload.LESS_EQUALS_BYTES::newFunctionBinding, false), - LESS_EQUALS_DOUBLE(LessEqualsOverload.LESS_EQUALS_DOUBLE::newFunctionBinding, false), - LESS_EQUALS_DURATION(LessEqualsOverload.LESS_EQUALS_DURATION::newFunctionBinding, false), - LESS_EQUALS_INT64(LessEqualsOverload.LESS_EQUALS_INT64::newFunctionBinding, false), - LESS_EQUALS_STRING(LessEqualsOverload.LESS_EQUALS_STRING::newFunctionBinding, false), - LESS_EQUALS_TIMESTAMP(LessEqualsOverload.LESS_EQUALS_TIMESTAMP::newFunctionBinding, false), - LESS_EQUALS_UINT64(LessEqualsOverload.LESS_EQUALS_UINT64::newFunctionBinding, false), - LESS_EQUALS_INT64_UINT64( - LessEqualsOverload.LESS_EQUALS_INT64_UINT64::newFunctionBinding, true), - LESS_EQUALS_UINT64_INT64( - LessEqualsOverload.LESS_EQUALS_UINT64_INT64::newFunctionBinding, true), - LESS_EQUALS_INT64_DOUBLE( - LessEqualsOverload.LESS_EQUALS_INT64_DOUBLE::newFunctionBinding, true), - LESS_EQUALS_DOUBLE_INT64( - LessEqualsOverload.LESS_EQUALS_DOUBLE_INT64::newFunctionBinding, true), - LESS_EQUALS_UINT64_DOUBLE( - LessEqualsOverload.LESS_EQUALS_UINT64_DOUBLE::newFunctionBinding, true), - LESS_EQUALS_DOUBLE_UINT64( - LessEqualsOverload.LESS_EQUALS_DOUBLE_UINT64::newFunctionBinding, true), - GREATER_BOOL(GreaterOverload.GREATER_BOOL::newFunctionBinding, false), - GREATER_BYTES(GreaterOverload.GREATER_BYTES::newFunctionBinding, false), - GREATER_DOUBLE(GreaterOverload.GREATER_DOUBLE::newFunctionBinding, false), - GREATER_DURATION(GreaterOverload.GREATER_DURATION::newFunctionBinding, false), - GREATER_INT64(GreaterOverload.GREATER_INT64::newFunctionBinding, false), - GREATER_STRING(GreaterOverload.GREATER_STRING::newFunctionBinding, false), - GREATER_TIMESTAMP(GreaterOverload.GREATER_TIMESTAMP::newFunctionBinding, false), - GREATER_UINT64(GreaterOverload.GREATER_UINT64::newFunctionBinding, false), - GREATER_INT64_UINT64(GreaterOverload.GREATER_INT64_UINT64::newFunctionBinding, true), - GREATER_UINT64_INT64(GreaterOverload.GREATER_UINT64_INT64::newFunctionBinding, true), - GREATER_INT64_DOUBLE(GreaterOverload.GREATER_INT64_DOUBLE::newFunctionBinding, true), - GREATER_DOUBLE_INT64(GreaterOverload.GREATER_DOUBLE_INT64::newFunctionBinding, true), - GREATER_UINT64_DOUBLE(GreaterOverload.GREATER_UINT64_DOUBLE::newFunctionBinding, true), - GREATER_DOUBLE_UINT64(GreaterOverload.GREATER_DOUBLE_UINT64::newFunctionBinding, true), - GREATER_EQUALS_BOOL(GreaterEqualsOverload.GREATER_EQUALS_BOOL::newFunctionBinding, false), - GREATER_EQUALS_BYTES(GreaterEqualsOverload.GREATER_EQUALS_BYTES::newFunctionBinding, false), - GREATER_EQUALS_DOUBLE( - GreaterEqualsOverload.GREATER_EQUALS_DOUBLE::newFunctionBinding, false), - GREATER_EQUALS_DURATION( - GreaterEqualsOverload.GREATER_EQUALS_DURATION::newFunctionBinding, false), - GREATER_EQUALS_INT64(GreaterEqualsOverload.GREATER_EQUALS_INT64::newFunctionBinding, false), - GREATER_EQUALS_STRING( - GreaterEqualsOverload.GREATER_EQUALS_STRING::newFunctionBinding, false), - GREATER_EQUALS_TIMESTAMP( - GreaterEqualsOverload.GREATER_EQUALS_TIMESTAMP::newFunctionBinding, false), - GREATER_EQUALS_UINT64( - GreaterEqualsOverload.GREATER_EQUALS_UINT64::newFunctionBinding, false), - GREATER_EQUALS_INT64_UINT64( - GreaterEqualsOverload.GREATER_EQUALS_INT64_UINT64::newFunctionBinding, true), - GREATER_EQUALS_UINT64_INT64( - GreaterEqualsOverload.GREATER_EQUALS_UINT64_INT64::newFunctionBinding, true), - GREATER_EQUALS_INT64_DOUBLE( - GreaterEqualsOverload.GREATER_EQUALS_INT64_DOUBLE::newFunctionBinding, true), - GREATER_EQUALS_DOUBLE_INT64( - GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_INT64::newFunctionBinding, true), - GREATER_EQUALS_UINT64_DOUBLE( - GreaterEqualsOverload.GREATER_EQUALS_UINT64_DOUBLE::newFunctionBinding, true), - GREATER_EQUALS_DOUBLE_UINT64( - GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_UINT64::newFunctionBinding, true); - - private final FunctionBindingCreator bindingCreator; + LESS_BOOL(LessOverload.LESS_BOOL, false), + LESS_INT64(LessOverload.LESS_INT64, false), + LESS_UINT64(LessOverload.LESS_UINT64, false), + LESS_BYTES(LessOverload.LESS_BYTES, false), + LESS_DOUBLE(LessOverload.LESS_DOUBLE, false), + LESS_DOUBLE_UINT64(LessOverload.LESS_DOUBLE_UINT64, true), + LESS_INT64_UINT64(LessOverload.LESS_INT64_UINT64, true), + LESS_UINT64_INT64(LessOverload.LESS_UINT64_INT64, true), + LESS_INT64_DOUBLE(LessOverload.LESS_INT64_DOUBLE, true), + LESS_DOUBLE_INT64(LessOverload.LESS_DOUBLE_INT64, true), + LESS_UINT64_DOUBLE(LessOverload.LESS_UINT64_DOUBLE, true), + LESS_DURATION(LessOverload.LESS_DURATION, false), + LESS_STRING(LessOverload.LESS_STRING, false), + LESS_TIMESTAMP(LessOverload.LESS_TIMESTAMP, false), + LESS_EQUALS_BOOL(LessEqualsOverload.LESS_EQUALS_BOOL, false), + LESS_EQUALS_BYTES(LessEqualsOverload.LESS_EQUALS_BYTES, false), + LESS_EQUALS_DOUBLE(LessEqualsOverload.LESS_EQUALS_DOUBLE, false), + LESS_EQUALS_DURATION(LessEqualsOverload.LESS_EQUALS_DURATION, false), + LESS_EQUALS_INT64(LessEqualsOverload.LESS_EQUALS_INT64, false), + LESS_EQUALS_STRING(LessEqualsOverload.LESS_EQUALS_STRING, false), + LESS_EQUALS_TIMESTAMP(LessEqualsOverload.LESS_EQUALS_TIMESTAMP, false), + LESS_EQUALS_UINT64(LessEqualsOverload.LESS_EQUALS_UINT64, false), + LESS_EQUALS_INT64_UINT64(LessEqualsOverload.LESS_EQUALS_INT64_UINT64, true), + LESS_EQUALS_UINT64_INT64(LessEqualsOverload.LESS_EQUALS_UINT64_INT64, true), + LESS_EQUALS_INT64_DOUBLE(LessEqualsOverload.LESS_EQUALS_INT64_DOUBLE, true), + LESS_EQUALS_DOUBLE_INT64(LessEqualsOverload.LESS_EQUALS_DOUBLE_INT64, true), + LESS_EQUALS_UINT64_DOUBLE(LessEqualsOverload.LESS_EQUALS_UINT64_DOUBLE, true), + LESS_EQUALS_DOUBLE_UINT64(LessEqualsOverload.LESS_EQUALS_DOUBLE_UINT64, true), + GREATER_BOOL(GreaterOverload.GREATER_BOOL, false), + GREATER_BYTES(GreaterOverload.GREATER_BYTES, false), + GREATER_DOUBLE(GreaterOverload.GREATER_DOUBLE, false), + GREATER_DURATION(GreaterOverload.GREATER_DURATION, false), + GREATER_INT64(GreaterOverload.GREATER_INT64, false), + GREATER_STRING(GreaterOverload.GREATER_STRING, false), + GREATER_TIMESTAMP(GreaterOverload.GREATER_TIMESTAMP, false), + GREATER_UINT64(GreaterOverload.GREATER_UINT64, false), + GREATER_INT64_UINT64(GreaterOverload.GREATER_INT64_UINT64, true), + GREATER_UINT64_INT64(GreaterOverload.GREATER_UINT64_INT64, true), + GREATER_INT64_DOUBLE(GreaterOverload.GREATER_INT64_DOUBLE, true), + GREATER_DOUBLE_INT64(GreaterOverload.GREATER_DOUBLE_INT64, true), + GREATER_UINT64_DOUBLE(GreaterOverload.GREATER_UINT64_DOUBLE, true), + GREATER_DOUBLE_UINT64(GreaterOverload.GREATER_DOUBLE_UINT64, true), + GREATER_EQUALS_BOOL(GreaterEqualsOverload.GREATER_EQUALS_BOOL, false), + GREATER_EQUALS_BYTES(GreaterEqualsOverload.GREATER_EQUALS_BYTES, false), + GREATER_EQUALS_DOUBLE(GreaterEqualsOverload.GREATER_EQUALS_DOUBLE, false), + GREATER_EQUALS_DURATION(GreaterEqualsOverload.GREATER_EQUALS_DURATION, false), + GREATER_EQUALS_INT64(GreaterEqualsOverload.GREATER_EQUALS_INT64, false), + GREATER_EQUALS_STRING(GreaterEqualsOverload.GREATER_EQUALS_STRING, false), + GREATER_EQUALS_TIMESTAMP(GreaterEqualsOverload.GREATER_EQUALS_TIMESTAMP, false), + GREATER_EQUALS_UINT64(GreaterEqualsOverload.GREATER_EQUALS_UINT64, false), + GREATER_EQUALS_INT64_UINT64(GreaterEqualsOverload.GREATER_EQUALS_INT64_UINT64, true), + GREATER_EQUALS_UINT64_INT64(GreaterEqualsOverload.GREATER_EQUALS_UINT64_INT64, true), + GREATER_EQUALS_INT64_DOUBLE(GreaterEqualsOverload.GREATER_EQUALS_INT64_DOUBLE, true), + GREATER_EQUALS_DOUBLE_INT64(GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_INT64, true), + GREATER_EQUALS_UINT64_DOUBLE(GreaterEqualsOverload.GREATER_EQUALS_UINT64_DOUBLE, true), + GREATER_EQUALS_DOUBLE_UINT64(GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_UINT64, true); + + private final CelStandardOverload standardOverload; private final boolean isHeterogeneousComparison; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - Comparison(FunctionBindingCreator bindingCreator, boolean isHeterogeneousComparison) { - this.bindingCreator = bindingCreator; + Comparison(CelStandardOverload standardOverload, boolean isHeterogeneousComparison) { + this.standardOverload = standardOverload; this.isHeterogeneousComparison = isHeterogeneousComparison; } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java index 42386a942..783ddd81a 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -156,16 +156,16 @@ public enum AddOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("add_list", List.class, List.class, RuntimeHelpers::concat)); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - AddOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + AddOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java index 519755d18..5d9d3919c 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java @@ -72,17 +72,17 @@ public enum BoolOverload implements CelStandardOverload { } })); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; ; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - BoolOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + BoolOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java index d9b4babd5..e2a5230ce 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java @@ -61,17 +61,17 @@ public enum BytesOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; ; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - BytesOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + BytesOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java index 7c6599737..aad6e468b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardOverload.java @@ -25,15 +25,9 @@ * overload. */ @Immutable +@FunctionalInterface public interface CelStandardOverload { /** Constructs a new {@link CelFunctionBinding} for this CEL standard overload. */ CelFunctionBinding newFunctionBinding(CelOptions celOptions, RuntimeEquality runtimeEquality); - - /** TODO: To be removed in the upcoming CL */ - @FunctionalInterface - @Immutable - interface FunctionBindingCreator { - CelFunctionBinding create(CelOptions celOptions, RuntimeEquality runtimeEquality); - } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java index fb4aa37f1..21f8390b9 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java @@ -44,16 +44,16 @@ public enum ContainsOverload implements CelStandardOverload { "contains_string", String.class, String.class, String::contains)), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - ContainsOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + ContainsOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java index 23556d0f1..b9fdad33c 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java @@ -76,16 +76,16 @@ public enum DivideOverload implements CelStandardOverload { (Long x, Long y) -> RuntimeHelpers.uint64Divide(x, y, celOptions)); } }); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - DivideOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + DivideOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java index 1569a0c0b..508df1983 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java @@ -73,16 +73,16 @@ public enum DoubleOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - DoubleOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + DoubleOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java index b17a9871e..72f96f785 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -74,16 +74,16 @@ public enum DurationOverload implements CelStandardOverload { })), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - DurationOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + DurationOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java index bfaada85c..d3cb34de9 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java @@ -43,16 +43,16 @@ public enum DynOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("to_dyn", Object.class, (Object arg) -> arg)); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - DynOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + DynOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java index 048807a10..eeda546fe 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java @@ -44,17 +44,17 @@ public enum EndsWithOverload implements CelStandardOverload { "ends_with_string", String.class, String.class, String::endsWith)), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; ; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - EndsWithOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + EndsWithOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java index 192ffd749..ae78decbd 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java @@ -44,16 +44,16 @@ public enum EqualsOverload implements CelStandardOverload { "equals", Object.class, Object.class, runtimeEquality::objectEquals)), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - EqualsOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + EqualsOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java index 1c1b7ffd4..04e046062 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java @@ -75,16 +75,16 @@ public enum GetDateOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetDateOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetDateOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java index 1efa43620..b6f146aa9 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java @@ -76,16 +76,16 @@ public enum GetDayOfMonthOverload implements CelStandardOverload { } }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetDayOfMonthOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetDayOfMonthOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java index 4fa7000eb..cede6c1e1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java @@ -92,16 +92,16 @@ public enum GetDayOfWeekOverload implements CelStandardOverload { }); } }); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetDayOfWeekOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetDayOfWeekOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java index c74a85c1c..966c70bc6 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java @@ -77,16 +77,16 @@ public enum GetDayOfYearOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetDayOfYearOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetDayOfYearOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java index ca816eb80..a91fffb72 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java @@ -76,16 +76,16 @@ public enum GetFullYearOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetFullYearOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetFullYearOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java index 28d559248..94fb6391e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java @@ -87,16 +87,16 @@ public enum GetHoursOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetHoursOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetHoursOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java index 53ec92a82..672226eaf 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java @@ -98,16 +98,16 @@ public enum GetMillisecondsOverload implements CelStandardOverload { } }); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetMillisecondsOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetMillisecondsOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java index 62ee130b6..5f75d14ef 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java @@ -88,16 +88,16 @@ public enum GetMinutesOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetMinutesOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetMinutesOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java index 99770e69f..dc4e66889 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java @@ -75,16 +75,16 @@ public enum GetMonthOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetMonthOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetMonthOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java index f0357990b..70fe96ec4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java @@ -100,16 +100,16 @@ public enum GetSecondsOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GetSecondsOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GetSecondsOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java index 03be9c41b..be41e0f5f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java @@ -183,16 +183,16 @@ public enum GreaterEqualsOverload implements CelStandardOverload { UnsignedLong.class, (Double x, UnsignedLong y) -> ComparisonFunctions.compareDoubleUint(x, y) >= 0)); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GreaterEqualsOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GreaterEqualsOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java index f0db202a8..21b59eebb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java @@ -176,16 +176,16 @@ public enum GreaterOverload implements CelStandardOverload { (Double x, UnsignedLong y) -> ComparisonFunctions.compareDoubleUint(x, y) == 1)), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - GreaterOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + GreaterOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java index 9b4e3986c..9a2c99f43 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java @@ -56,16 +56,16 @@ public enum InOverload implements CelStandardOverload { Map.class, (Object key, Map map) -> runtimeEquality.inMap(map, key))); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - InOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + InOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java index f94fd2807..d1b88a88f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java @@ -51,16 +51,16 @@ public enum IndexOverload implements CelStandardOverload { CelFunctionBinding.from( "index_map", Map.class, Object.class, runtimeEquality::indexMap)); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - IndexOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + IndexOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java index 17ad835cb..fda307a9d 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -116,16 +116,16 @@ public enum IntOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - IntOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + IntOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java index 8ff6f5248..e09fcce5e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java @@ -180,16 +180,16 @@ public enum LessEqualsOverload implements CelStandardOverload { (Double x, UnsignedLong y) -> ComparisonFunctions.compareDoubleUint(x, y) <= 0)), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - LessEqualsOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + LessEqualsOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java index d348555b7..4afbc20a2 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java @@ -176,16 +176,16 @@ public enum LessOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - LessOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + LessOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java index c11539a81..a4be6385b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java @@ -43,16 +43,16 @@ public enum LogicalNotOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("logical_not", Boolean.class, (Boolean x) -> !x)); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - LogicalNotOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + LogicalNotOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java index 1b0891b1a..7c24f65a2 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java @@ -70,16 +70,16 @@ public enum MatchesOverload implements CelStandardOverload { })), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - MatchesOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + MatchesOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java index 716db43b7..e7246851c 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java @@ -71,16 +71,16 @@ public enum ModuloOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - ModuloOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + ModuloOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java index 46472fdd6..7e9e2f352 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java @@ -89,16 +89,16 @@ public enum MultiplyOverload implements CelStandardOverload { } }); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - MultiplyOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + MultiplyOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java index dd30c557e..2e3f094f5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java @@ -58,16 +58,16 @@ public enum NegateOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("negate_double", Double.class, (Double x) -> -x)); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - NegateOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + NegateOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java index 886047e98..5a0475f29 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java @@ -46,16 +46,16 @@ public enum NotEqualsOverload implements CelStandardOverload { Object.class, (Object x, Object y) -> !runtimeEquality.objectEquals(x, y))); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - NotEqualsOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + NotEqualsOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java index 206e4bdd4..9f79605ed 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java @@ -84,16 +84,16 @@ public enum SizeOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("map_size", Map.class, (Map map1) -> (long) map1.size())); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - SizeOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + SizeOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java index 3a73a9f35..6626d6912 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java @@ -44,16 +44,16 @@ public enum StartsWithOverload implements CelStandardOverload { CelFunctionBinding.from( "starts_with_string", String.class, String.class, String::startsWith)); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - StartsWithOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + StartsWithOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java index aa1935876..c491bfe76 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -120,16 +120,16 @@ public enum StringOverload implements CelStandardOverload { } }); - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - StringOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + StringOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java index a5d1359cc..4b0ecd3ae 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -143,16 +143,16 @@ public enum SubtractOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - SubtractOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + SubtractOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 76d2e5b84..34d3a1dc0 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -89,16 +89,16 @@ public enum TimestampOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - TimestampOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + TimestampOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java index a05258521..89424e7fc 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java @@ -144,16 +144,16 @@ public enum UintOverload implements CelStandardOverload { }), ; - private final FunctionBindingCreator bindingCreator; + private final CelStandardOverload standardOverload; @Override public CelFunctionBinding newFunctionBinding( CelOptions celOptions, RuntimeEquality runtimeEquality) { - return bindingCreator.create(celOptions, runtimeEquality); + return standardOverload.newFunctionBinding(celOptions, runtimeEquality); } - UintOverload(FunctionBindingCreator bindingCreator) { - this.bindingCreator = bindingCreator; + UintOverload(CelStandardOverload standardOverload) { + this.standardOverload = standardOverload; } } From 4180fc98dd7f6a1bf1b6205c9ffa15d6145ac02c Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 3 Nov 2025 13:42:24 -0800 Subject: [PATCH 007/100] Add isStrict flag to denote function overloads as strict PiperOrigin-RevId: 827633212 --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 6 ++-- .../dev/cel/runtime/CelFunctionBinding.java | 8 +++-- .../cel/runtime/CelLateFunctionBindings.java | 1 + .../dev/cel/runtime/CelResolvedOverload.java | 29 ++++++++++++++++--- .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 2 +- .../dev/cel/runtime/DefaultDispatcher.java | 29 ++----------------- .../dev/cel/runtime/FunctionBindingImpl.java | 13 ++++++++- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 5 +++- .../main/java/dev/cel/runtime/Registrar.java | 6 ++-- .../dev/cel/runtime/ResolvedOverload.java | 15 ++++++++++ .../cel/runtime/CelResolvedOverloadTest.java | 6 ++-- .../cel/runtime/DefaultDispatcherTest.java | 8 +++-- .../cel/runtime/DefaultInterpreterTest.java | 7 ++++- 13 files changed, 87 insertions(+), 48 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index e5df8250b..4a5f6dcc7 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -118,9 +118,9 @@ java_library( tags = [ ], deps = [ - ":base", ":evaluation_exception", ":evaluation_exception_builder", + ":function_overload", ":resolved_overload", ":resolved_overload_internal", "//:auto_value", @@ -138,9 +138,9 @@ cel_android_library( srcs = DISPATCHER_SOURCES, visibility = ["//visibility:private"], deps = [ - ":base_android", ":evaluation_exception", ":evaluation_exception_builder", + ":function_overload_android", ":function_resolver_android", ":resolved_overload_android", ":resolved_overload_internal_android", @@ -725,7 +725,6 @@ java_library( ], deps = [ ":function_overload", - "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -738,7 +737,6 @@ cel_android_library( ], deps = [ ":function_overload_android", - "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 8fe2b8a2e..79b0f3f54 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.annotations.Internal; /** * Binding consisting of an overload id, a Java-native argument signature, and an overload @@ -36,7 +35,7 @@ * *

Examples: string_startsWith_string, mathMax_list, lessThan_money_money */ -@Internal + @Immutable public interface CelFunctionBinding { String getOverloadId(); @@ -45,6 +44,8 @@ public interface CelFunctionBinding { CelFunctionOverload getDefinition(); + boolean isStrict(); + /** Create a unary function binding from the {@code overloadId}, {@code arg}, and {@code impl}. */ @SuppressWarnings("unchecked") static CelFunctionBinding from( @@ -66,6 +67,7 @@ static CelFunctionBinding from( /** Create a function binding from the {@code overloadId}, {@code argTypes}, and {@code impl}. */ static CelFunctionBinding from( String overloadId, Iterable> argTypes, CelFunctionOverload impl) { - return new FunctionBindingImpl(overloadId, ImmutableList.copyOf(argTypes), impl); + return new FunctionBindingImpl( + overloadId, ImmutableList.copyOf(argTypes), impl, /* isStrict= */ true); } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index 282ef1eb9..6762feec0 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -58,6 +58,7 @@ private static ResolvedOverload createResolvedOverload(CelFunctionBinding bindin return CelResolvedOverload.of( binding.getOverloadId(), (args) -> binding.getDefinition().apply(args), + binding.isStrict(), binding.getArgTypes()); } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java index 9725315cc..26f86a459 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java @@ -35,6 +35,21 @@ abstract class CelResolvedOverload implements ResolvedOverload { @Override public abstract ImmutableList> getParameterTypes(); + /* Denotes whether an overload is strict. + * + *

A strict function will not be invoked if any of its arguments are an error or unknown value. + * The runtime automatically propagates the error or unknown instead. + * + *

A non-strict function will be invoked even if its arguments contain errors or unknowns. The + * function's implementation is then responsible for handling these values. This is primarily used + * for short-circuiting logical operators (e.g., `||`, `&&`) and comprehension's + * internal @not_strictly_false function. + * + *

In a vast majority of cases, this should be set to true. + */ + @Override + public abstract boolean isStrict(); + /** The function definition. */ @Override public abstract CelFunctionOverload getDefinition(); @@ -43,16 +58,22 @@ abstract class CelResolvedOverload implements ResolvedOverload { * Creates a new resolved overload from the given overload id, parameter types, and definition. */ public static CelResolvedOverload of( - String overloadId, CelFunctionOverload definition, Class... parameterTypes) { - return of(overloadId, definition, ImmutableList.copyOf(parameterTypes)); + String overloadId, + CelFunctionOverload definition, + boolean isStrict, + Class... parameterTypes) { + return of(overloadId, definition, isStrict, ImmutableList.copyOf(parameterTypes)); } /** * Creates a new resolved overload from the given overload id, parameter types, and definition. */ public static CelResolvedOverload of( - String overloadId, CelFunctionOverload definition, List> parameterTypes) { + String overloadId, + CelFunctionOverload definition, + boolean isStrict, + List> parameterTypes) { return new AutoValue_CelResolvedOverload( - overloadId, ImmutableList.copyOf(parameterTypes), definition); + overloadId, ImmutableList.copyOf(parameterTypes), isStrict, definition); } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 2947445d1..4f58d44db 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -293,7 +293,7 @@ public CelRuntimeLegacyImpl build() { .forEach( (String overloadId, CelFunctionBinding func) -> dispatcher.add( - overloadId, func.getArgTypes(), (args) -> func.getDefinition().apply(args))); + overloadId, func.getArgTypes(), func.isStrict(), func.getDefinition())); RuntimeTypeProvider runtimeTypeProvider; diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index c4c27005e..6228dd253 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -32,7 +32,7 @@ *

Should be final, do not mock; mocking {@link Dispatcher} instead. */ @ThreadSafe -final class DefaultDispatcher implements Dispatcher, Registrar { +final class DefaultDispatcher implements Dispatcher { public static DefaultDispatcher create() { return new DefaultDispatcher(); } @@ -40,32 +40,9 @@ public static DefaultDispatcher create() { @GuardedBy("this") private final Map overloads = new HashMap<>(); - @Override - @SuppressWarnings("unchecked") - public synchronized void add( - String overloadId, Class argType, final Registrar.UnaryFunction function) { - overloads.put( - overloadId, - CelResolvedOverload.of(overloadId, args -> function.apply((T) args[0]), argType)); - } - - @Override - @SuppressWarnings("unchecked") - public synchronized void add( - String overloadId, - Class argType1, - Class argType2, - final Registrar.BinaryFunction function) { - overloads.put( - overloadId, - CelResolvedOverload.of( - overloadId, args -> function.apply((T1) args[0], (T2) args[1]), argType1, argType2)); - } - - @Override public synchronized void add( - String overloadId, List> argTypes, Registrar.Function function) { - overloads.put(overloadId, CelResolvedOverload.of(overloadId, function, argTypes)); + String overloadId, List> argTypes, boolean isStrict, CelFunctionOverload overload) { + overloads.put(overloadId, CelResolvedOverload.of(overloadId, overload, isStrict, argTypes)); } @Override diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java index 501608571..b554ce41a 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java @@ -26,6 +26,8 @@ final class FunctionBindingImpl implements CelFunctionBinding { private final CelFunctionOverload definition; + private final boolean isStrict; + @Override public String getOverloadId() { return overloadId; @@ -41,10 +43,19 @@ public CelFunctionOverload getDefinition() { return definition; } + @Override + public boolean isStrict() { + return isStrict; + } + FunctionBindingImpl( - String overloadId, ImmutableList> argTypes, CelFunctionOverload definition) { + String overloadId, + ImmutableList> argTypes, + CelFunctionOverload definition, + boolean isStrict) { this.overloadId = overloadId; this.argTypes = argTypes; this.definition = definition; + this.isStrict = isStrict; } } diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 152c96160..aba73aed4 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -182,7 +182,10 @@ public CelLiteRuntime build() { .forEach( (String overloadId, CelFunctionBinding func) -> dispatcher.add( - overloadId, func.getArgTypes(), (args) -> func.getDefinition().apply(args))); + overloadId, + func.getArgTypes(), + func.isStrict(), + (args) -> func.getDefinition().apply(args))); Interpreter interpreter = new DefaultInterpreter( diff --git a/runtime/src/main/java/dev/cel/runtime/Registrar.java b/runtime/src/main/java/dev/cel/runtime/Registrar.java index 12dbe6cb3..467a7c426 100644 --- a/runtime/src/main/java/dev/cel/runtime/Registrar.java +++ b/runtime/src/main/java/dev/cel/runtime/Registrar.java @@ -15,15 +15,15 @@ package dev.cel.runtime; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.annotations.Internal; import java.util.List; /** * An object which registers the functions that a {@link Dispatcher} calls. * - *

CEL Library Internals. Do Not Use. + * @deprecated Do not use. This interface exists solely for legacy async stack compatibility + * reasons. */ -@Internal +@Deprecated public interface Registrar { /** Interface describing the general signature of all CEL custom function implementations. */ diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index bc7544199..d0b4d2f77 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -35,6 +35,21 @@ interface ResolvedOverload { /** The function definition. */ CelFunctionOverload getDefinition(); + /** + * Denotes whether an overload is strict. + * + *

A strict function will not be invoked if any of its arguments are an error or unknown value. + * The runtime automatically propagates the error or unknown instead. + * + *

A non-strict function will be invoked even if its arguments contain errors or unknowns. The + * function's implementation is then responsible for handling these values. This is primarily used + * for short-circuiting logical operators (e.g., `||`, `&&`) and comprehension's + * internal @not_strictly_false function. + * + *

In a vast majority of cases, a function should be kept strict. + */ + boolean isStrict(); + /** * Returns true if the overload's expected argument types match the types of the given arguments. */ diff --git a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java index 2c81b89d4..0fdc4f65d 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java @@ -32,6 +32,7 @@ CelResolvedOverload getIncrementIntOverload() { Long arg = (Long) args[0]; return arg + 1; }, + /* isStrict= */ true, Long.class); } @@ -43,14 +44,15 @@ public void canHandle_matchingTypes_returnsTrue() { @Test public void canHandle_nullMessageType_returnsTrue() { CelResolvedOverload overload = - CelResolvedOverload.of("identity", (args) -> args[0], TestAllTypes.class); + CelResolvedOverload.of( + "identity", (args) -> args[0], /* isStrict= */ true, TestAllTypes.class); assertThat(overload.canHandle(new Object[] {null})).isTrue(); } @Test public void canHandle_nullPrimitive_returnsFalse() { CelResolvedOverload overload = - CelResolvedOverload.of("identity", (args) -> args[0], Long.class); + CelResolvedOverload.of("identity", (args) -> args[0], /* isStrict= */ true, Long.class); assertThat(overload.canHandle(new Object[] {null})).isFalse(); } diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java index 03eba51ce..fb6831201 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java @@ -35,9 +35,13 @@ public final class DefaultDispatcherTest { public void setup() { overloads = new HashMap<>(); overloads.put( - "overload_1", CelResolvedOverload.of("overload_1", args -> (Long) args[0] + 1, Long.class)); + "overload_1", + CelResolvedOverload.of( + "overload_1", args -> (Long) args[0] + 1, /* isStrict= */ true, Long.class)); overloads.put( - "overload_2", CelResolvedOverload.of("overload_2", args -> (Long) args[0] + 2, Long.class)); + "overload_2", + CelResolvedOverload.of( + "overload_2", args -> (Long) args[0] + 2, /* isStrict= */ true, Long.class)); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java index 4f3501d41..e9ac6b5a3 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java @@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableList; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelFunctionDecl; @@ -74,7 +75,11 @@ public Object adapt(String messageName, Object message) { }; CelAbstractSyntaxTree ast = celCompiler.compile("[1].all(x, [2].all(y, error()))").getAst(); DefaultDispatcher dispatcher = DefaultDispatcher.create(); - dispatcher.add("error", long.class, (args) -> new IllegalArgumentException("Always throws")); + dispatcher.add( + "error", + ImmutableList.of(long.class), + /* isStrict= */ true, + (args) -> new IllegalArgumentException("Always throws")); DefaultInterpreter defaultInterpreter = new DefaultInterpreter(new TypeResolver(), emptyProvider, dispatcher, CelOptions.DEFAULT); DefaultInterpretable interpretable = From 4a5e6593e4f1bb4b448a459bb01af9faeb25b6df Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 3 Nov 2025 14:36:20 -0800 Subject: [PATCH 008/100] Remove unnecessary synchronization on dispatcher by making it immutable PiperOrigin-RevId: 827654282 --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 3 - .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 6 +- .../dev/cel/runtime/DefaultDispatcher.java | 73 +++++++++---------- .../dev/cel/runtime/DefaultInterpreter.java | 10 +-- .../main/java/dev/cel/runtime/Dispatcher.java | 48 ------------ .../java/dev/cel/runtime/LiteRuntimeImpl.java | 11 +-- .../cel/runtime/DefaultInterpreterTest.java | 7 +- 7 files changed, 49 insertions(+), 109 deletions(-) delete mode 100644 runtime/src/main/java/dev/cel/runtime/Dispatcher.java diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 4a5f6dcc7..20d49c476 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -60,7 +60,6 @@ INTERPRABLE_SOURCES = [ # keep sorted DISPATCHER_SOURCES = [ "DefaultDispatcher.java", - "Dispatcher.java", ] java_library( @@ -125,7 +124,6 @@ java_library( ":resolved_overload_internal", "//:auto_value", "//common:error_codes", - "//common/annotations", "//runtime:function_resolver", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -146,7 +144,6 @@ cel_android_library( ":resolved_overload_internal_android", "//:auto_value", "//common:error_codes", - "//common/annotations", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 4f58d44db..1b7071505 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -287,12 +287,12 @@ public CelRuntimeLegacyImpl build() { functionBindingsBuilder.putAll(customFunctionBindings); - DefaultDispatcher dispatcher = DefaultDispatcher.create(); + DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); functionBindingsBuilder .buildOrThrow() .forEach( (String overloadId, CelFunctionBinding func) -> - dispatcher.add( + dispatcherBuilder.addOverload( overloadId, func.getArgTypes(), func.isStrict(), func.getDefinition())); RuntimeTypeProvider runtimeTypeProvider; @@ -313,7 +313,7 @@ public CelRuntimeLegacyImpl build() { new DefaultInterpreter( DescriptorTypeResolver.create(), runtimeTypeProvider, - dispatcher.immutableCopy(), + dispatcherBuilder.build(), options); return new CelRuntimeLegacyImpl( diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 6228dd253..2643ddf75 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -14,41 +14,29 @@ package dev.cel.runtime; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoBuilder; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; -import javax.annotation.concurrent.ThreadSafe; -import com.google.errorprone.annotations.concurrent.GuardedBy; import dev.cel.common.CelErrorCode; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -/** - * Default implementation of {@link Dispatcher}. - * - *

Should be final, do not mock; mocking {@link Dispatcher} instead. - */ -@ThreadSafe -final class DefaultDispatcher implements Dispatcher { - public static DefaultDispatcher create() { - return new DefaultDispatcher(); - } +/** Default implementation of dispatcher. */ +@Immutable +final class DefaultDispatcher implements CelFunctionResolver { - @GuardedBy("this") - private final Map overloads = new HashMap<>(); - - public synchronized void add( - String overloadId, List> argTypes, boolean isStrict, CelFunctionOverload overload) { - overloads.put(overloadId, CelResolvedOverload.of(overloadId, overload, isStrict, argTypes)); - } + private final ImmutableMap overloads; @Override - public synchronized Optional findOverload( + public Optional findOverload( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return DefaultDispatcher.findOverload(functionName, overloadIds, overloads, args); + return findOverload(functionName, overloadIds, overloads, args); } /** Finds the overload that matches the given function name, overload IDs, and arguments. */ @@ -87,31 +75,36 @@ public static Optional findOverload( return Optional.ofNullable(match); } - @Override - public synchronized Dispatcher.ImmutableCopy immutableCopy() { - return new ImmutableCopy(overloads); + static Builder newBuilder() { + return new AutoBuilder_DefaultDispatcher_Builder(); } - @Immutable - private static final class ImmutableCopy implements Dispatcher.ImmutableCopy { - private final ImmutableMap overloads; + @AutoBuilder(ofClass = DefaultDispatcher.class) + abstract static class Builder { - private ImmutableCopy(Map overloads) { - this.overloads = ImmutableMap.copyOf(overloads); - } + abstract ImmutableMap overloads(); - @Override - public Optional findOverload( - String functionName, List overloadIds, Object[] args) - throws CelEvaluationException { - return DefaultDispatcher.findOverload(functionName, overloadIds, overloads, args); - } + abstract ImmutableMap.Builder overloadsBuilder(); + + @CanIgnoreReturnValue + Builder addOverload( + String overloadId, + List> argTypes, + boolean isStrict, + CelFunctionOverload overload) { + checkNotNull(overloadId); + checkNotNull(argTypes); + checkNotNull(overload); - @Override - public Dispatcher.ImmutableCopy immutableCopy() { + overloadsBuilder() + .put(overloadId, CelResolvedOverload.of(overloadId, overload, isStrict, argTypes)); return this; } + + abstract DefaultDispatcher build(); } - private DefaultDispatcher() {} + DefaultDispatcher(ImmutableMap overloads) { + this.overloads = overloads; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index b471d972e..f40960e74 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -64,7 +64,7 @@ final class DefaultInterpreter implements Interpreter { private final TypeResolver typeResolver; private final RuntimeTypeProvider typeProvider; - private final Dispatcher dispatcher; + private final DefaultDispatcher dispatcher; private final CelOptions celOptions; /** @@ -102,7 +102,7 @@ static IntermediateResult create(Object value) { public DefaultInterpreter( TypeResolver typeResolver, RuntimeTypeProvider typeProvider, - Dispatcher dispatcher, + DefaultDispatcher dispatcher, CelOptions celOptions) { this.typeResolver = checkNotNull(typeResolver); this.typeProvider = checkNotNull(typeProvider); @@ -120,7 +120,7 @@ public Interpretable createInterpretable(CelAbstractSyntaxTree ast) { static final class DefaultInterpretable implements Interpretable, UnknownTrackingInterpretable { private final TypeResolver typeResolver; private final RuntimeTypeProvider typeProvider; - private final Dispatcher.ImmutableCopy dispatcher; + private final DefaultDispatcher dispatcher; private final Metadata metadata; private final CelAbstractSyntaxTree ast; private final CelOptions celOptions; @@ -128,12 +128,12 @@ static final class DefaultInterpretable implements Interpretable, UnknownTrackin DefaultInterpretable( TypeResolver typeResolver, RuntimeTypeProvider typeProvider, - Dispatcher dispatcher, + DefaultDispatcher dispatcher, CelAbstractSyntaxTree ast, CelOptions celOptions) { this.typeResolver = checkNotNull(typeResolver); this.typeProvider = checkNotNull(typeProvider); - this.dispatcher = checkNotNull(dispatcher).immutableCopy(); + this.dispatcher = checkNotNull(dispatcher); this.ast = checkNotNull(ast); this.metadata = new DefaultMetadata(ast); this.celOptions = checkNotNull(celOptions); diff --git a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java b/runtime/src/main/java/dev/cel/runtime/Dispatcher.java deleted file mode 100644 index e7bc6163f..000000000 --- a/runtime/src/main/java/dev/cel/runtime/Dispatcher.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 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.runtime; - -import com.google.errorprone.annotations.Immutable; -import javax.annotation.concurrent.ThreadSafe; -import dev.cel.common.annotations.Internal; - -/** - * An object which implements dispatching of function calls. - * - *

CEL Library Internals. Do Not Use. - */ -@ThreadSafe -@Internal -interface Dispatcher extends CelFunctionResolver { - - /** - * Returns an {@link ImmutableCopy} from current instance. - * - * @see ImmutableCopy - */ - ImmutableCopy immutableCopy(); - - /** - * An {@link Immutable} copy of a {@link Dispatcher}. Currently {@link DefaultDispatcher} - * implementation implements both {@link Dispatcher} and {@link Registrar} and cannot be annotated - * as {@link Immutable}. - * - *

Should consider to provide Registrar.dumpAsDispatcher and Registrar.dumpAsAsyncDispatcher - * instead of letting DefaultDispatcher or AsyncDispatcher to implement both Registrar and - * Dispatcher. But it requires a global refactoring. - */ - @Immutable - interface ImmutableCopy extends Dispatcher {} -} diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index aba73aed4..e1415eabd 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -176,22 +176,19 @@ public CelLiteRuntime build() { functionBindingsBuilder.putAll(customFunctionBindings); - DefaultDispatcher dispatcher = DefaultDispatcher.create(); + DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); functionBindingsBuilder .buildOrThrow() .forEach( (String overloadId, CelFunctionBinding func) -> - dispatcher.add( - overloadId, - func.getArgTypes(), - func.isStrict(), - (args) -> func.getDefinition().apply(args))); + dispatcherBuilder.addOverload( + overloadId, func.getArgTypes(), func.isStrict(), func.getDefinition())); Interpreter interpreter = new DefaultInterpreter( TypeResolver.create(), CelValueRuntimeTypeProvider.newInstance(celValueProvider), - dispatcher, + dispatcherBuilder.build(), celOptions); return new LiteRuntimeImpl( diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java index e9ac6b5a3..b70bffdb6 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java @@ -74,14 +74,15 @@ public Object adapt(String messageName, Object message) { } }; CelAbstractSyntaxTree ast = celCompiler.compile("[1].all(x, [2].all(y, error()))").getAst(); - DefaultDispatcher dispatcher = DefaultDispatcher.create(); - dispatcher.add( + DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); + dispatcherBuilder.addOverload( "error", ImmutableList.of(long.class), /* isStrict= */ true, (args) -> new IllegalArgumentException("Always throws")); DefaultInterpreter defaultInterpreter = - new DefaultInterpreter(new TypeResolver(), emptyProvider, dispatcher, CelOptions.DEFAULT); + new DefaultInterpreter( + new TypeResolver(), emptyProvider, dispatcherBuilder.build(), CelOptions.DEFAULT); DefaultInterpretable interpretable = (DefaultInterpretable) defaultInterpreter.createInterpretable(ast); From 93c9c1ebf29b799fcb85433286ddf686b0dfd8f8 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 3 Nov 2025 15:24:41 -0800 Subject: [PATCH 009/100] Internal Changes PiperOrigin-RevId: 827672471 --- .../java/dev/cel/protobuf/CelLiteDescriptorTest.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java b/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java index c26643291..1ceed29bb 100644 --- a/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java +++ b/protobuf/src/test/java/dev/cel/protobuf/CelLiteDescriptorTest.java @@ -53,16 +53,6 @@ public void testAllTypesMessageLiteDescriptor_fullyQualifiedNames() { .isEqualTo("cel.expr.conformance.proto3.TestAllTypes"); } - @Test - public void testAllTypesMessageLiteDescriptor_fieldInfoMap_containsAllEntries() { - MessageLiteDescriptor testAllTypesDescriptor = - TEST_ALL_TYPES_CEL_LITE_DESCRIPTOR - .getProtoTypeNamesToDescriptors() - .get("cel.expr.conformance.proto3.TestAllTypes"); - - assertThat(testAllTypesDescriptor.getFieldDescriptors()).hasSize(243); - } - @Test public void fieldDescriptor_getByFieldNumber() { MessageLiteDescriptor testAllTypesDescriptor = From c0dbe7ec262cf3730642703a9a6bdfe0232ea589 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 3 Nov 2025 16:02:39 -0800 Subject: [PATCH 010/100] Generally handle function strictness in the runtime, Add NotStrictlyFalse function to standard lib PiperOrigin-RevId: 827685375 --- runtime/BUILD.bazel | 12 ++++ .../src/main/java/dev/cel/runtime/BUILD.bazel | 30 ++++++++ .../dev/cel/runtime/CelFunctionResolver.java | 2 +- .../cel/runtime/CelLateFunctionBindings.java | 4 +- .../dev/cel/runtime/CelStandardFunctions.java | 14 +++- .../dev/cel/runtime/DefaultDispatcher.java | 32 ++++++++- .../dev/cel/runtime/DefaultInterpreter.java | 49 ++++++------- .../cel/runtime/InternalFunctionBinder.java | 49 +++++++++++++ .../dev/cel/runtime/ResolvedOverload.java | 8 +++ .../java/dev/cel/runtime/standard/BUILD.bazel | 32 +++++++++ .../standard/NotStrictlyFalseFunction.java | 68 +++++++++++++++++++ .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../runtime/CelLateFunctionBindingsTest.java | 16 +++-- .../cel/runtime/DefaultDispatcherTest.java | 2 +- .../cel/runtime/DefaultInterpreterTest.java | 10 +++ .../nonstrictQuantifierTests.baseline | 8 +++ runtime/standard/BUILD.bazel | 10 +++ .../dev/cel/testing/BaseInterpreterTest.java | 4 ++ 18 files changed, 311 insertions(+), 40 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java create mode 100644 runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 3bcb86766..22926832a 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -232,3 +232,15 @@ java_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_internal"], ) + +java_library( + name = "internal_function_binder", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder"], +) + +cel_android_library( + name = "internal_function_binder_android", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder_andriod"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 20d49c476..90808a8d0 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -644,6 +644,7 @@ java_library( "//runtime/standard:multiply", "//runtime/standard:negate", "//runtime/standard:not_equals", + "//runtime/standard:not_strictly_false", "//runtime/standard:size", "//runtime/standard:standard_function", "//runtime/standard:standard_overload", @@ -700,6 +701,7 @@ cel_android_library( "//runtime/standard:multiply_android", "//runtime/standard:negate_android", "//runtime/standard:not_equals_android", + "//runtime/standard:not_strictly_false_android", "//runtime/standard:size_android", "//runtime/standard:standard_function_android", "//runtime/standard:standard_overload_android", @@ -794,6 +796,7 @@ java_library( ], deps = [ ":function_overload", + ":unknown_attributes", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_protobuf_protobuf_java", @@ -806,6 +809,7 @@ cel_android_library( visibility = ["//visibility:private"], deps = [ ":function_overload_android", + ":unknown_attributes_android", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_protobuf_protobuf_javalite", @@ -1190,3 +1194,29 @@ cel_android_library( "@maven_android//:com_google_guava_guava", ], ) + +java_library( + name = "internal_function_binder", + srcs = ["InternalFunctionBinder.java"], + tags = [ + ], + deps = [ + ":function_overload", + "//common/annotations", + "//runtime:function_binding", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "internal_function_binder_andriod", + srcs = ["InternalFunctionBinder.java"], + tags = [ + ], + deps = [ + ":function_overload_android", + "//common/annotations", + "//runtime:function_binding_android", + "@maven_android//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java index 8df7fd0dc..8f00eebb3 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java @@ -35,6 +35,6 @@ public interface CelFunctionResolver { * @return an optional value of the resolved overload. * @throws CelEvaluationException if the overload resolution is ambiguous, */ - Optional findOverload( + Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index 6762feec0..75be39e92 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -36,9 +36,9 @@ private CelLateFunctionBindings(ImmutableMap functions } @Override - public Optional findOverload( + public Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return DefaultDispatcher.findOverload(functionName, overloadIds, functions, args); + return DefaultDispatcher.findOverloadMatchingArgs(functionName, overloadIds, functions, args); } public static CelLateFunctionBindings from(CelFunctionBinding... functions) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 6742dc42f..98e4c8636 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -100,6 +100,8 @@ import dev.cel.runtime.standard.NegateOperator.NegateOverload; import dev.cel.runtime.standard.NotEqualsOperator; import dev.cel.runtime.standard.NotEqualsOperator.NotEqualsOverload; +import dev.cel.runtime.standard.NotStrictlyFalseFunction; +import dev.cel.runtime.standard.NotStrictlyFalseFunction.NotStrictlyFalseOverload; import dev.cel.runtime.standard.SizeFunction; import dev.cel.runtime.standard.SizeFunction.SizeOverload; import dev.cel.runtime.standard.StartsWithFunction; @@ -159,7 +161,8 @@ public final class CelStandardFunctions { StringFunction.create(), SubtractOperator.create(), TimestampFunction.create(), - UintFunction.create()); + UintFunction.create(), + NotStrictlyFalseFunction.create()); /** * Enumeration of Standard Function bindings. @@ -170,6 +173,7 @@ public final class CelStandardFunctions { public enum StandardFunction { LOGICAL_NOT(BooleanOperator.LOGICAL_NOT), IN(InternalOperator.IN_LIST, InternalOperator.IN_MAP), + NOT_STRICTLY_FALSE(InternalOperator.NOT_STRICTLY_FALSE), EQUALS(Relation.EQUALS), NOT_EQUALS(Relation.NOT_EQUALS), BOOL(Conversions.BOOL_TO_BOOL, Conversions.STRING_TO_BOOL), @@ -331,10 +335,14 @@ public enum StandardFunction { /** Container class for CEL standard function overloads. */ public static final class Overload { - /** Overloads for internal functions that may have been rewritten by macros (ex: @in) */ + /** + * Overloads for internal functions that may have been rewritten by macros + * (ex: @in, @not_strictly_false) + */ public enum InternalOperator implements CelStandardOverload { IN_LIST(InOverload.IN_LIST), - IN_MAP(InOverload.IN_MAP); + IN_MAP(InOverload.IN_MAP), + NOT_STRICTLY_FALSE(NotStrictlyFalseOverload.NOT_STRICTLY_FALSE); private final CelStandardOverload standardOverload; diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 2643ddf75..35340abc5 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -34,13 +34,13 @@ final class DefaultDispatcher implements CelFunctionResolver { private final ImmutableMap overloads; @Override - public Optional findOverload( + public Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { - return findOverload(functionName, overloadIds, overloads, args); + return findOverloadMatchingArgs(functionName, overloadIds, overloads, args); } /** Finds the overload that matches the given function name, overload IDs, and arguments. */ - public static Optional findOverload( + static Optional findOverloadMatchingArgs( String functionName, List overloadIds, Map overloads, @@ -75,6 +75,32 @@ public static Optional findOverload( return Optional.ofNullable(match); } + /** + * Finds the single registered overload iff it's marked as a non-strict function. + * + *

The intent behind this function is to provide an at-parity behavior with existing + * DefaultInterpreter, where it historically special-cased locating a single overload for certain + * non-strict functions, such as not_strictly_false. This method should not be used outside of + * this specific context. + * + * @throws IllegalStateException if there are multiple overloads that are marked non-strict. + */ + Optional findSingleNonStrictOverload(List overloadIds) { + for (String overloadId : overloadIds) { + ResolvedOverload overload = overloads.get(overloadId); + if (overload != null && !overload.isStrict()) { + if (overloadIds.size() > 1) { + throw new IllegalStateException( + String.format( + "%d overloads found for a non-strict function. Expected 1.", overloadIds.size())); + } + return Optional.of(overload); + } + } + + return Optional.empty(); + } + static Builder newBuilder() { return new AutoBuilder_DefaultDispatcher_Builder(); } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index f40960e74..931194d6e 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -421,8 +421,6 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall return evalLogicalAnd(frame, callExpr); case "logical_or": return evalLogicalOr(frame, callExpr); - case "not_strictly_false": - return evalNotStrictlyFalse(frame, callExpr); case "type": return evalType(frame, callExpr); case "optional_or_optional": @@ -441,8 +439,18 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall break; } - // Delegate handling of call to dispatcher. + boolean isNonStrictCall = + dispatcher.findSingleNonStrictOverload(reference.overloadIds()).isPresent(); + return dispatchCall(frame, expr, callExpr, reference, isNonStrictCall); + } + private IntermediateResult dispatchCall( + ExecutionFrame frame, + CelExpr expr, + CelCall callExpr, + CelReference reference, + boolean isNonStrict) + throws CelEvaluationException { List callArgs = new ArrayList<>(); callExpr.target().ifPresent(callArgs::add); @@ -450,9 +458,16 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall IntermediateResult[] argResults = new IntermediateResult[callArgs.size()]; for (int i = 0; i < argResults.length; i++) { - // Default evaluation is strict so errors will propagate (via thrown Java exception) before - // unknowns. - argResults[i] = evalInternal(frame, callArgs.get(i)); + IntermediateResult result; + try { + result = evalInternal(frame, callArgs.get(i)); + } catch (Exception e) { + if (!isNonStrict) { + throw e; + } + result = IntermediateResult.create(e); + } + argResults[i] = result; } Optional indexAttr = @@ -469,7 +484,7 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall indexAttr.isPresent() ? CallArgumentChecker.createAcceptingPartial(frame.getResolver()) : CallArgumentChecker.create(frame.getResolver()); - for (DefaultInterpreter.IntermediateResult element : argResults) { + for (IntermediateResult element : argResults) { argChecker.checkArg(element); } Optional unknowns = argChecker.maybeUnknowns(); @@ -511,7 +526,7 @@ private ResolvedOverload findOverloadOrThrow( throws CelEvaluationException { try { Optional funcImpl = - dispatcher.findOverload(functionName, overloadIds, args); + dispatcher.findOverloadMatchingArgs(functionName, overloadIds, args); if (funcImpl.isPresent()) { return funcImpl.get(); } @@ -684,20 +699,6 @@ private IntermediateResult evalLogicalAnd(ExecutionFrame frame, CelCall callExpr return mergeBooleanUnknowns(left, right); } - // Returns true unless the expression evaluates to false, in which case it returns false. - // True is also returned if evaluation yields an error or an unknown set. - private IntermediateResult evalNotStrictlyFalse(ExecutionFrame frame, CelCall callExpr) { - try { - IntermediateResult value = evalBooleanStrict(frame, callExpr.args().get(0)); - if (value.value() instanceof Boolean) { - return value; - } - } catch (Exception e) { - /*nothing to do*/ - } - return IntermediateResult.create(true); - } - private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr) throws CelEvaluationException { CelExpr typeExprArg = callExpr.args().get(0); @@ -1134,7 +1135,9 @@ private RuntimeUnknownResolver getResolver() { private Optional findOverload( String function, List overloadIds, Object[] args) throws CelEvaluationException { if (lateBoundFunctionResolver.isPresent()) { - return lateBoundFunctionResolver.get().findOverload(function, overloadIds, args); + return lateBoundFunctionResolver + .get() + .findOverloadMatchingArgs(function, overloadIds, args); } return Optional.empty(); } diff --git a/runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java b/runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java new file mode 100644 index 000000000..5a063ee6c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/InternalFunctionBinder.java @@ -0,0 +1,49 @@ +// Copyright 2025 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.runtime; + +import com.google.common.collect.ImmutableList; +import dev.cel.common.annotations.Internal; + +/** + * A helper to create CelFunctionBinding instances with sensitive controls, such as to toggle the + * strictness of the function. + * + *

CEL Library Internals. Do Not Use. + */ +@Internal +public final class InternalFunctionBinder { + + /** + * Create a unary function binding from the {@code overloadId}, {@code arg}, {@code impl}, and + * {@code} isStrict. + */ + @SuppressWarnings("unchecked") + public static CelFunctionBinding from( + String overloadId, Class arg, CelFunctionOverload.Unary impl, boolean isStrict) { + return from(overloadId, ImmutableList.of(arg), (args) -> impl.apply((T) args[0]), isStrict); + } + + /** + * Create a function binding from the {@code overloadId}, {@code argTypes}, {@code impl} and + * {@code isStrict}. + */ + public static CelFunctionBinding from( + String overloadId, Iterable> argTypes, CelFunctionOverload impl, boolean isStrict) { + return new FunctionBindingImpl(overloadId, ImmutableList.copyOf(argTypes), impl, isStrict); + } + + private InternalFunctionBinder() {} +} diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index d0b4d2f77..a46a92fae 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -70,6 +70,14 @@ default boolean canHandle(Object[] arguments) { } continue; } + + if (arg instanceof Exception || arg instanceof CelUnknownSet) { + if (!isStrict()) { + // Only non-strict functions can accept errors/unknowns as arguments to a function + return true; + } + } + if (!paramType.isAssignableFrom(arg.getClass())) { return false; } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index 851f375a4..041d07dea 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -1419,6 +1419,38 @@ cel_android_library( ], ) +java_library( + name = "not_strictly_false", + srcs = ["NotStrictlyFalseFunction.java"], + tags = [ + ], + deps = [ + ":standard_overload", + "//common:options", + "//runtime:function_binding", + "//runtime:internal_function_binder", + "//runtime:runtime_equality", + "//runtime/standard:standard_function", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "not_strictly_false_android", + srcs = ["NotStrictlyFalseFunction.java"], + tags = [ + ], + deps = [ + ":standard_function_android", + ":standard_overload_android", + "//common:options", + "//runtime:function_binding_android", + "//runtime:internal_function_binder_android", + "//runtime:runtime_equality_android", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "standard_overload", srcs = ["CelStandardOverload.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java new file mode 100644 index 000000000..af7d1a3c2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java @@ -0,0 +1,68 @@ +// Copyright 2025 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.runtime.standard; + +import com.google.common.collect.ImmutableSet; +import dev.cel.common.CelOptions; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.InternalFunctionBinder; +import dev.cel.runtime.RuntimeEquality; + +/** + * Standard function for {@code @not_strictly_false}. This is an internal function used within + * comprehensions to coerce the result into true if an evaluation yields an error or an unknown set. + */ +public final class NotStrictlyFalseFunction extends CelStandardFunction { + private static final NotStrictlyFalseFunction ALL_OVERLOADS = + new NotStrictlyFalseFunction(ImmutableSet.copyOf(NotStrictlyFalseOverload.values())); + + public static NotStrictlyFalseFunction create() { + return ALL_OVERLOADS; + } + + /** Overloads for the standard function. */ + public enum NotStrictlyFalseOverload implements CelStandardOverload { + NOT_STRICTLY_FALSE( + (celOptions, runtimeEquality) -> + InternalFunctionBinder.from( + "not_strictly_false", + Object.class, + (Object value) -> { + if (value instanceof Boolean) { + return value; + } + + return true; + }, + /* isStrict= */ false)), + ; + + private final CelStandardOverload bindingCreator; + + @Override + public CelFunctionBinding newFunctionBinding( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + return bindingCreator.newFunctionBinding(celOptions, runtimeEquality); + } + + NotStrictlyFalseOverload(CelStandardOverload bindingCreator) { + this.bindingCreator = bindingCreator; + } + } + + private NotStrictlyFalseFunction(ImmutableSet overloads) { + super(overloads); + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 81e206fdb..4fda11d9b 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -82,6 +82,7 @@ java_library( "//runtime:type_resolver", "//runtime:unknown_attributes", "//runtime:unknown_options", + "//runtime/standard:not_strictly_false", "//runtime/standard:standard_overload", "//testing/protos:message_with_enum_cel_java_proto", "//testing/protos:message_with_enum_java_proto", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java index 03bed8ae6..3f9e1e105 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java @@ -38,7 +38,7 @@ public void findOverload_singleMatchingFunction_isPresent() throws Exception { CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1L}); assertThat(overload).isPresent(); assertThat(overload.get().getOverloadId()).isEqualTo("increment_int"); @@ -54,7 +54,7 @@ public void findOverload_noMatchingFunctionSameArgCount_isEmpty() throws Excepti CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1.0}); assertThat(overload).isEmpty(); } @@ -67,7 +67,7 @@ public void findOverload_noMatchingFunctionDifferentArgCount_isEmpty() throws Ex CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1.0, 1.0}); @@ -89,7 +89,7 @@ public void findOverload_badInput_throwsException() throws Exception { return arg.plus(UnsignedLong.ONE); })); Optional overload = - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_uint"), new Object[] {UnsignedLong.MAX_VALUE}); assertThat(overload).isPresent(); assertThat(overload.get().getOverloadId()).isEqualTo("increment_uint"); @@ -111,7 +111,7 @@ public void findOverload_multipleMatchingFunctions_throwsException() throws Exce Assert.assertThrows( CelEvaluationException.class, () -> - bindings.findOverload( + bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1L})); @@ -124,7 +124,8 @@ public void findOverload_nullPrimitiveArg_isEmpty() throws Exception { CelLateFunctionBindings.from( CelFunctionBinding.from("identity_int", Long.class, (arg) -> arg)); Optional overload = - bindings.findOverload("identity", ImmutableList.of("identity_int"), new Object[] {null}); + bindings.findOverloadMatchingArgs( + "identity", ImmutableList.of("identity_int"), new Object[] {null}); assertThat(overload).isEmpty(); } @@ -134,7 +135,8 @@ public void findOverload_nullMessageArg_returnsOverload() throws Exception { CelLateFunctionBindings.from( CelFunctionBinding.from("identity_msg", TestAllTypes.class, (arg) -> arg)); Optional overload = - bindings.findOverload("identity", ImmutableList.of("identity_msg"), new Object[] {null}); + bindings.findOverloadMatchingArgs( + "identity", ImmutableList.of("identity_msg"), new Object[] {null}); assertThat(overload).isPresent(); assertThat(overload.get().getOverloadId()).isEqualTo("identity_msg"); assertThat(overload.get().getParameterTypes()).containsExactly(TestAllTypes.class); diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java index fb6831201..fbcfbd813 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java @@ -50,7 +50,7 @@ public void findOverload_multipleMatches_throwsException() { Assert.assertThrows( CelEvaluationException.class, () -> - DefaultDispatcher.findOverload( + DefaultDispatcher.findOverloadMatchingArgs( "overloads", ImmutableList.of("overload_1", "overload_2"), overloads, diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java index b70bffdb6..1a8f45161 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultInterpreterTest.java @@ -29,6 +29,7 @@ import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.DefaultInterpreter.DefaultInterpretable; import dev.cel.runtime.DefaultInterpreter.ExecutionFrame; +import dev.cel.runtime.standard.NotStrictlyFalseFunction.NotStrictlyFalseOverload; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; @@ -80,6 +81,15 @@ public Object adapt(String messageName, Object message) { ImmutableList.of(long.class), /* isStrict= */ true, (args) -> new IllegalArgumentException("Always throws")); + CelFunctionBinding notStrictlyFalseBinding = + NotStrictlyFalseOverload.NOT_STRICTLY_FALSE.newFunctionBinding( + CelOptions.DEFAULT, + RuntimeEquality.create(RuntimeHelpers.create(), CelOptions.DEFAULT)); + dispatcherBuilder.addOverload( + notStrictlyFalseBinding.getOverloadId(), + notStrictlyFalseBinding.getArgTypes(), + notStrictlyFalseBinding.isStrict(), + notStrictlyFalseBinding.getDefinition()); DefaultInterpreter defaultInterpreter = new DefaultInterpreter( new TypeResolver(), emptyProvider, dispatcherBuilder.build(), CelOptions.DEFAULT); diff --git a/runtime/src/test/resources/nonstrictQuantifierTests.baseline b/runtime/src/test/resources/nonstrictQuantifierTests.baseline index 3a7a05b2b..728e4bfc9 100644 --- a/runtime/src/test/resources/nonstrictQuantifierTests.baseline +++ b/runtime/src/test/resources/nonstrictQuantifierTests.baseline @@ -38,4 +38,12 @@ declare four { } =====> bindings: {four=4} +result: true + +Source: [0, 1].exists(x, x > four || true) +declare four { + value int +} +=====> +bindings: {} result: true \ No newline at end of file diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index a69f69887..bc6ab9ed3 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -406,6 +406,16 @@ cel_android_library( exports = ["//runtime/src/main/java/dev/cel/runtime/standard:uint_android"], ) +java_library( + name = "not_strictly_false", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false"], +) + +cel_android_library( + name = "not_strictly_false_android", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false_android"], +) + java_library( name = "standard_overload", exports = ["//runtime/src/main/java/dev/cel/runtime/standard:standard_overload"], diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 24ed1ea8d..4be456fb7 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -1439,6 +1439,10 @@ public void nonstrictQuantifierTests() { source = "![0, 2, four].all(x, four/x != 2 && four/(four-x) != 2)"; runTest(ImmutableMap.of("four", 4L)); + + // Unknown argument + source = "[0, 1].exists(x, x > four || true)"; + runTest(); } @Test From 0c00aaa2f77f7e1f717311de4596979217c9051b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 4 Nov 2025 14:56:34 -0800 Subject: [PATCH 011/100] Enable evaluateCanonicalTypesToNativeValues by default PiperOrigin-RevId: 828147408 --- codelab/README.md | 4 ++-- codelab/src/test/codelab/Exercise8Test.java | 4 ++-- .../src/test/codelab/solutions/Exercise8Test.java | 4 ++-- common/src/main/java/dev/cel/common/CelOptions.java | 4 ++++ .../validators/DurationLiteralValidator.java | 2 +- .../validators/TimestampLiteralValidator.java | 4 ++-- .../validators/DurationLiteralValidatorTest.java | 4 ++-- .../validators/TimestampLiteralValidatorTest.java | 13 ++++++------- 8 files changed, 21 insertions(+), 18 deletions(-) diff --git a/codelab/README.md b/codelab/README.md index 6de1a2fb0..f7d248b13 100644 --- a/codelab/README.md +++ b/codelab/README.md @@ -1065,8 +1065,8 @@ public void validate_invalidTimestampLiteral_returnsError() throws Exception { assertThat(validationResult.hasError()).isTrue(); assertThat(validationResult.getErrorString()) .isEqualTo( - "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Failed to" - + " parse timestamp: invalid timestamp \"bad\"\n" + "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Text 'bad'" + + " could not be parsed at index 0\n" + " | timestamp('bad')\n" + " | ..........^"); } diff --git a/codelab/src/test/codelab/Exercise8Test.java b/codelab/src/test/codelab/Exercise8Test.java index 9ebbc505a..36fef248c 100644 --- a/codelab/src/test/codelab/Exercise8Test.java +++ b/codelab/src/test/codelab/Exercise8Test.java @@ -43,8 +43,8 @@ public void validate_invalidTimestampLiteral_returnsError() throws Exception { assertThat(validationResult.hasError()).isTrue(); assertThat(validationResult.getErrorString()) .isEqualTo( - "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Failed to" - + " parse timestamp: invalid timestamp \"bad\"\n" + "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Text 'bad'" + + " could not be parsed at index 0\n" + " | timestamp('bad')\n" + " | ..........^"); } diff --git a/codelab/src/test/codelab/solutions/Exercise8Test.java b/codelab/src/test/codelab/solutions/Exercise8Test.java index ac39340e0..73a0ddc91 100644 --- a/codelab/src/test/codelab/solutions/Exercise8Test.java +++ b/codelab/src/test/codelab/solutions/Exercise8Test.java @@ -43,8 +43,8 @@ public void validate_invalidTimestampLiteral_returnsError() throws Exception { assertThat(validationResult.hasError()).isTrue(); assertThat(validationResult.getErrorString()) .isEqualTo( - "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Failed to" - + " parse timestamp: invalid timestamp \"bad\"\n" + "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Text 'bad'" + + " could not be parsed at index 0\n" + " | timestamp('bad')\n" + " | ..........^"); } diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java index 3cbfb62b1..d39d53803 100644 --- a/common/src/main/java/dev/cel/common/CelOptions.java +++ b/common/src/main/java/dev/cel/common/CelOptions.java @@ -184,6 +184,7 @@ public static Builder current() { .enableUnsignedLongs(true) .enableRegexPartialMatch(true) .errorOnDuplicateMapKeys(true) + .evaluateCanonicalTypesToNativeValues(true) .errorOnIntWrap(true) .resolveTypeDependencies(true) .disableCelStandardEquality(false); @@ -465,7 +466,10 @@ public abstract static class Builder { *

  • Timestamp: {@code java.time.Instant} instead of {@code com.google.protobuf.Timestamp}. *
  • Duration: {@code java.time.Duration} instead of {@code com.google.protobuf.Duration}. * + * + * @deprecated Do not use. This flag is enabled by default and will be removed in the future. */ + @Deprecated public abstract Builder evaluateCanonicalTypesToNativeValues(boolean value); /** diff --git a/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java b/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java index 349374ca2..f0220e6ab 100644 --- a/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java +++ b/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java @@ -14,7 +14,7 @@ package dev.cel.validator.validators; -import com.google.protobuf.Duration; +import java.time.Duration; /** DurationLiteralValidator ensures that duration literal arguments are valid. */ public final class DurationLiteralValidator extends LiteralValidator { diff --git a/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java b/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java index 4f6a5209e..f1167f4eb 100644 --- a/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java +++ b/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java @@ -14,12 +14,12 @@ package dev.cel.validator.validators; -import com.google.protobuf.Timestamp; +import java.time.Instant; /** TimestampLiteralValidator ensures that timestamp literal arguments are valid. */ public final class TimestampLiteralValidator extends LiteralValidator { public static final TimestampLiteralValidator INSTANCE = - new TimestampLiteralValidator("timestamp", Timestamp.class); + new TimestampLiteralValidator("timestamp", Instant.class); private TimestampLiteralValidator(String functionName, Class expectedResultType) { super(functionName, expectedResultType); diff --git a/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java b/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java index e89e5ce35..1ec3ef4aa 100644 --- a/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java +++ b/validator/src/test/java/dev/cel/validator/validators/DurationLiteralValidatorTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableMap; -import com.google.protobuf.Duration; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; @@ -35,6 +34,7 @@ import dev.cel.validator.CelValidator; import dev.cel.validator.CelValidatorFactory; import java.text.ParseException; +import java.time.Duration; import org.junit.Test; import org.junit.runner.RunWith; @@ -174,7 +174,7 @@ public void duration_unexpectedResultType_throws() throws Exception { assertThat(result.getAllIssues().get(0).toDisplayString(ast.getSource())) .isEqualTo( "ERROR: :1:10: duration validation failed. Reason: Expected" - + " com.google.protobuf.Duration type but got java.lang.Integer instead\n" + + " java.time.Duration type but got java.lang.Integer instead\n" + " | duration('1h')\n" + " | .........^"); } diff --git a/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java b/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java index 65b7d593a..d263fc6bf 100644 --- a/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java +++ b/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableMap; -import com.google.protobuf.Timestamp; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; @@ -36,6 +35,7 @@ import dev.cel.validator.CelValidator; import dev.cel.validator.CelValidatorFactory; import java.text.ParseException; +import java.time.Instant; import org.junit.Test; import org.junit.runner.RunWith; @@ -63,7 +63,7 @@ public void timestamp_validFormat(String source) throws Exception { assertThat(result.hasError()).isFalse(); assertThat(result.getAllIssues()).isEmpty(); - assertThat(CEL.createProgram(ast).eval()).isInstanceOf(Timestamp.class); + assertThat(CEL.createProgram(ast).eval()).isInstanceOf(Instant.class); } @Test @@ -100,8 +100,7 @@ public void timestamp_withVariable_noOp() throws Exception { () -> CEL.createProgram(ast).eval(ImmutableMap.of("str_var", "bad"))); assertThat(e) .hasMessageThat() - .contains( - "evaluation error at :9: Failed to parse timestamp: invalid timestamp \"bad\""); + .contains("evaluation error at :9: Text 'bad' could not be parsed at index 0"); } @Test @@ -153,8 +152,8 @@ public void timestamp_invalidFormat() throws Exception { assertThat(result.getAllIssues().get(0).getSeverity()).isEqualTo(Severity.ERROR); assertThat(result.getAllIssues().get(0).toDisplayString(ast.getSource())) .isEqualTo( - "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Failed to" - + " parse timestamp: invalid timestamp \"bad\"\n" + "ERROR: :1:11: timestamp validation failed. Reason: evaluation error: Text 'bad'" + + " could not be parsed at index 0\n" + " | timestamp('bad')\n" + " | ..........^"); } @@ -185,7 +184,7 @@ public void timestamp_unexpectedResultType_throws() throws Exception { assertThat(result.getAllIssues().get(0).toDisplayString(ast.getSource())) .isEqualTo( "ERROR: :1:11: timestamp validation failed. Reason: Expected" - + " com.google.protobuf.Timestamp type but got java.lang.Integer instead\n" + + " java.time.Instant type but got java.lang.Integer instead\n" + " | timestamp(0)\n" + " | ..........^"); } From 1b465a6f96dd1f23890260fdb8daf0f887aa14af Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 4 Nov 2025 16:47:17 -0800 Subject: [PATCH 012/100] Remove no-op evaluateCanonicalTypesToNativeValues flag in tests PiperOrigin-RevId: 828187012 --- .../test/java/dev/cel/bundle/CelImplTest.java | 26 +++---------------- .../cel/common/internal/ProtoAdapterTest.java | 2 +- .../values/ProtoMessageValueProviderTest.java | 2 +- .../dev/cel/conformance/ConformanceTest.java | 1 - .../extensions/CelEncoderExtensionsTest.java | 2 +- .../extensions/CelOptionalLibraryTest.java | 1 - .../ConstantFoldingOptimizerTest.java | 1 - .../java/dev/cel/runtime/LiteRuntimeImpl.java | 1 - .../dev/cel/testing/BaseInterpreterTest.java | 1 - 9 files changed, 7 insertions(+), 30 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 2d337e4cf..00b47494d 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -817,7 +817,6 @@ public void program_messageConstruction() throws Exception { public void program_duplicateTypeDescriptor() throws Exception { Cel cel = standardCelBuilderWithMacros() - .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()) .addMessageTypes(Timestamp.getDescriptor()) .addMessageTypes(ImmutableList.of(Timestamp.getDescriptor())) .setContainer(CelContainer.ofName("google")) @@ -833,7 +832,6 @@ public void program_duplicateTypeDescriptor() throws Exception { public void program_hermeticDescriptors_wellKnownProtobuf() throws Exception { Cel cel = standardCelBuilderWithMacros() - .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()) .addMessageTypes(Timestamp.getDescriptor()) .setContainer(CelContainer.ofName("google")) .setResultType(SimpleType.TIMESTAMP) @@ -962,7 +960,6 @@ public void program_deepTypeResolutionDisabledForRuntime_fails() throws Exceptio public void program_typeProvider() throws Exception { Cel cel = standardCelBuilderWithMacros() - .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()) .setTypeProvider( new DescriptorTypeProvider(ImmutableList.of(Timestamp.getDescriptor()))) .setContainer(CelContainer.ofName("google")) @@ -1029,10 +1026,7 @@ public void program_enumTypeReferenceResolution(boolean resolveTypeDependencies) Cel cel = standardCelBuilderWithMacros() .setOptions( - CelOptions.current() - .evaluateCanonicalTypesToNativeValues(true) - .resolveTypeDependencies(resolveTypeDependencies) - .build()) + CelOptions.current().resolveTypeDependencies(resolveTypeDependencies).build()) .addMessageTypes(Struct.getDescriptor()) .setResultType(StructTypeReference.create("google.protobuf.NullValue")) .setContainer(CelContainer.ofName("google.protobuf")) @@ -1050,11 +1044,7 @@ public void program_enumTypeReferenceResolution(boolean resolveTypeDependencies) public void program_enumTypeTransitiveResolution() throws Exception { Cel cel = standardCelBuilderWithMacros() - .setOptions( - CelOptions.current() - .evaluateCanonicalTypesToNativeValues(true) - .resolveTypeDependencies(true) - .build()) + .setOptions(CelOptions.current().resolveTypeDependencies(true).build()) .addMessageTypes(Proto2ExtensionScopedMessage.getDescriptor()) .setResultType(StructTypeReference.create("google.protobuf.NullValue")) .setContainer(CelContainer.ofName("google.protobuf")) @@ -1643,11 +1633,7 @@ public void programAdvanceEvaluation_indexOnUnknownContainer() throws Exception public void programAdvanceEvaluation_unsupportedIndexIgnored() throws Exception { Cel cel = standardCelBuilderWithMacros() - .setOptions( - CelOptions.current() - .evaluateCanonicalTypesToNativeValues(true) - .enableUnknownTracking(true) - .build()) + .setOptions(CelOptions.current().enableUnknownTracking(true).build()) .addVar("unk", MapType.create(SimpleType.STRING, SimpleType.BOOL)) .setContainer(CelContainer.ofName("")) .addFunctionBindings() @@ -2168,11 +2154,7 @@ public void program_fdsContainsWktDependency_descriptorInstancesMatch() throws E Cel cel = standardCelBuilderWithMacros() .addMessageTypes(descriptors) - .setOptions( - CelOptions.current() - .evaluateCanonicalTypesToNativeValues(true) - .enableTimestampEpoch(true) - .build()) + .setOptions(CelOptions.current().enableTimestampEpoch(true).build()) .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) .build(); CelAbstractSyntaxTree ast = diff --git a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java index 0ce37ac1d..61c71e4a6 100644 --- a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java +++ b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java @@ -153,7 +153,7 @@ public void adaptValueToProto_bidirectionalConversion() { ProtoAdapter protoAdapter = new ProtoAdapter( dynamicProto, - CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build()); + CelOptions.current().build()); assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName())) .isEqualTo(proto); assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value); diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java index f9bee1af1..b7e078f81 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java @@ -66,7 +66,7 @@ public void newValue_createEmptyProtoMessage() { public void newValue_createProtoMessage_fieldsPopulated() { ProtoMessageValueProvider protoMessageValueProvider = ProtoMessageValueProvider.newInstance( - CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build(), DYNAMIC_PROTO); + CelOptions.current().build(), DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java index c4cf8193d..bc5ecfd12 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java @@ -54,7 +54,6 @@ public final class ConformanceTest extends Statement { private static final CelOptions OPTIONS = CelOptions.current() - .evaluateCanonicalTypesToNativeValues(true) .enableTimestampEpoch(true) .enableHeterogeneousNumericComparisons(true) .enableProtoDifferencerEquality(true) diff --git a/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java index ece060011..7eed3dd5a 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelEncoderExtensionsTest.java @@ -37,7 +37,7 @@ @RunWith(TestParameterInjector.class) public class CelEncoderExtensionsTest { private static final CelOptions CEL_OPTIONS = - CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build(); + CelOptions.current().build(); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index a51fbedbf..dd94333c3 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -100,7 +100,6 @@ private static CelBuilder newCelBuilder(int version) { return CelFactory.standardCelBuilder() .setOptions( CelOptions.current() - .evaluateCanonicalTypesToNativeValues(true) .enableTimestampEpoch(true) .build()) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index 821a31d69..9b3810b99 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -49,7 +49,6 @@ public class ConstantFoldingOptimizerTest { private static final CelOptions CEL_OPTIONS = CelOptions.current() .enableTimestampEpoch(true) - .evaluateCanonicalTypesToNativeValues(true) .build(); private static final Cel CEL = CelFactory.standardCelBuilder() diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index e1415eabd..45c322da9 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -204,7 +204,6 @@ private Builder() { this.celOptions = CelOptions.current() .enableCelValue(true) - .evaluateCanonicalTypesToNativeValues(true) .build(); this.celValueProvider = (structType, fields) -> Optional.empty(); this.customFunctionBindings = new HashMap<>(); diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 4be456fb7..096114757 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -111,7 +111,6 @@ public abstract class BaseInterpreterTest extends CelBaselineTestCase { .enableTimestampEpoch(true) .enableHeterogeneousNumericComparisons(true) .enableOptionalSyntax(true) - .evaluateCanonicalTypesToNativeValues(true) .comprehensionMaxIterations(1_000) .build(); private CelRuntime celRuntime; From 2eed0fe47b56f0d518ffaaf203da4beae8958432 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 4 Nov 2025 18:28:52 -0800 Subject: [PATCH 013/100] Split Program into its own interface PiperOrigin-RevId: 828221559 --- runtime/BUILD.bazel | 11 +++ .../src/main/java/dev/cel/runtime/BUILD.bazel | 78 +++++++++++++++---- .../java/dev/cel/runtime/CelLiteRuntime.java | 20 ----- .../main/java/dev/cel/runtime/CelRuntime.java | 2 +- .../java/dev/cel/runtime/LiteProgramImpl.java | 4 +- .../main/java/dev/cel/runtime/Program.java | 36 +++++++++ .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 + .../runtime/CelLiteRuntimeAndroidTest.java | 1 - 8 files changed, 116 insertions(+), 37 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/Program.java diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 22926832a..251d45650 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -244,3 +244,14 @@ cel_android_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:internal_function_binder_andriod"], ) + +java_library( + name = "program", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:program"], +) + +cel_android_library( + name = "program_android", + exports = ["//runtime/src/main/java/dev/cel/runtime:program_android"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 90808a8d0..56f9a7bd2 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -41,10 +41,14 @@ LITE_RUNTIME_SOURCES = [ # keep sorted LITE_RUNTIME_IMPL_SOURCES = [ - "LiteProgramImpl.java", "LiteRuntimeImpl.java", ] +# keep sorted +LITE_PROGRAM_IMPL_SOURCES = [ + "LiteProgramImpl.java", +] + # keep sorted FUNCTION_BINDING_SOURCES = [ "CelFunctionBinding.java", @@ -833,7 +837,6 @@ java_library( ":function_resolver", ":interpretable", ":interpreter", - ":lite_runtime", ":proto_message_activation_factory", ":proto_message_runtime_equality", ":runtime_equality", @@ -853,6 +856,7 @@ java_library( "//common/types:cel_types", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", + "//runtime:program", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -869,12 +873,12 @@ java_library( deps = [ ":evaluation_exception", ":function_binding", - ":function_resolver", "//:auto_value", "//common:cel_ast", "//common:options", "//common/annotations", "//common/values:cel_value_provider", + "//runtime:program", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -888,14 +892,11 @@ java_library( tags = [ ], deps = [ - ":activation", ":cel_value_runtime_type_provider", ":dispatcher", - ":evaluation_exception", ":function_binding", - ":function_resolver", - ":interpretable", ":interpreter", + ":lite_program_impl", ":lite_runtime", ":runtime_equality", ":runtime_helpers", @@ -904,28 +905,54 @@ java_library( "//common:cel_ast", "//common:options", "//common/values:cel_value_provider", + "//runtime:program", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", - "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) +java_library( + name = "lite_program_impl", + srcs = LITE_PROGRAM_IMPL_SOURCES, + deps = [ + ":activation", + ":evaluation_exception", + ":function_resolver", + ":interpretable", + ":program", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +cel_android_library( + name = "lite_program_impl_android", + srcs = LITE_PROGRAM_IMPL_SOURCES, + deps = [ + ":activation_android", + ":evaluation_exception", + ":function_resolver_android", + ":interpretable_android", + ":program_android", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + cel_android_library( name = "lite_runtime_impl_android", srcs = LITE_RUNTIME_IMPL_SOURCES, tags = [ ], deps = [ - ":activation_android", ":cel_value_runtime_type_provider_android", ":dispatcher_android", - ":evaluation_exception", ":function_binding_android", - ":function_resolver_android", - ":interpretable_android", ":interpreter_android", + ":lite_program_impl_android", ":lite_runtime_android", + ":program_android", ":runtime_equality_android", ":runtime_helpers_android", ":type_resolver_android", @@ -1127,7 +1154,7 @@ cel_android_library( deps = [ ":evaluation_exception", ":function_binding_android", - ":function_resolver_android", + ":program_android", "//:auto_value", "//common:cel_ast_android", "//common:options", @@ -1195,6 +1222,31 @@ cel_android_library( ], ) +java_library( + name = "program", + srcs = ["Program.java"], + tags = [ + ], + deps = [ + ":evaluation_exception", + ":function_resolver", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +cel_android_library( + name = "program_android", + srcs = ["Program.java"], + tags = [ + ], + deps = [ + ":evaluation_exception", + ":function_resolver_android", + "//:auto_value", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "internal_function_binder", srcs = ["InternalFunctionBinder.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java index 0652a7a63..af8e918f6 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLiteRuntime.java @@ -14,11 +14,9 @@ package dev.cel.runtime; -import com.google.errorprone.annotations.Immutable; import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.annotations.Beta; -import java.util.Map; /** * CelLiteRuntime creates executable {@link Program} instances from {@link CelAbstractSyntaxTree} @@ -34,22 +32,4 @@ public interface CelLiteRuntime { Program createProgram(CelAbstractSyntaxTree ast) throws CelEvaluationException; CelLiteRuntimeBuilder toRuntimeBuilder(); - - /** Creates an evaluable {@code Program} instance which is thread-safe and immutable. */ - @Immutable - interface Program { - - /** Evaluate the expression without any variables. */ - Object eval() throws CelEvaluationException; - - /** Evaluate the expression using a {@code mapValue} as the source of input variables. */ - Object eval(Map mapValue) throws CelEvaluationException; - - /** - * Evaluate a compiled program with {@code mapValue} and late-bound functions {@code - * lateBoundFunctionResolver}. - */ - Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) - throws CelEvaluationException; - } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java index d730701e7..a42b7f969 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java @@ -37,7 +37,7 @@ public interface CelRuntime { /** Creates an evaluable {@code Program} instance which is thread-safe and immutable. */ @Immutable - interface Program extends CelLiteRuntime.Program { + interface Program extends dev.cel.runtime.Program { /** Evaluate the expression using {@code message} fields as the source of input variables. */ Object eval(Message message) throws CelEvaluationException; diff --git a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java index 3002f7c7c..e54f848b7 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java @@ -20,7 +20,7 @@ @Immutable @AutoValue -abstract class LiteProgramImpl implements CelLiteRuntime.Program { +abstract class LiteProgramImpl implements Program { abstract Interpretable interpretable(); @@ -40,7 +40,7 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio return interpretable().eval(Activation.copyOf(mapValue), lateBoundFunctionResolver); } - static CelLiteRuntime.Program plan(Interpretable interpretable) { + static Program plan(Interpretable interpretable) { return new AutoValue_LiteProgramImpl(interpretable); } } diff --git a/runtime/src/main/java/dev/cel/runtime/Program.java b/runtime/src/main/java/dev/cel/runtime/Program.java new file mode 100644 index 000000000..3eb074317 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/Program.java @@ -0,0 +1,36 @@ +// Copyright 2025 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.runtime; + +import com.google.errorprone.annotations.Immutable; +import java.util.Map; + +/** Creates an evaluable {@code Program} instance which is thread-safe and immutable. */ +@Immutable +public interface Program { + + /** Evaluate the expression without any variables. */ + Object eval() throws CelEvaluationException; + + /** Evaluate the expression using a {@code mapValue} as the source of input variables. */ + Object eval(Map mapValue) throws CelEvaluationException; + + /** + * Evaluate a compiled program with {@code mapValue} and late-bound functions {@code + * lateBoundFunctionResolver}. + */ + Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException; +} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 4fda11d9b..092f61f04 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -156,6 +156,7 @@ cel_android_local_test( "//runtime:lite_runtime_impl_android", "//runtime:standard_functions_android", "//runtime:unknown_attributes_android", + "//runtime/src/main/java/dev/cel/runtime:program_android", "//runtime/standard:equals_android", "//runtime/standard:int_android", "//testing/protos:test_all_types_cel_java_proto2_lite", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java index a960b6249..638782c2e 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java @@ -47,7 +47,6 @@ import dev.cel.expr.conformance.proto3.TestAllTypesCelLiteDescriptor; import dev.cel.extensions.CelLiteExtensions; import dev.cel.extensions.SetsFunction; -import dev.cel.runtime.CelLiteRuntime.Program; import dev.cel.runtime.standard.EqualsOperator; import dev.cel.runtime.standard.IntFunction; import dev.cel.runtime.standard.IntFunction.IntOverload; From 9083e06186604a27f00de7448233681cd4fa74c1 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 5 Nov 2025 11:10:20 -0800 Subject: [PATCH 014/100] Fix argument matching to validate all args for non-strict functions PiperOrigin-RevId: 828548887 --- .../dev/cel/runtime/ResolvedOverload.java | 5 +-- .../cel/runtime/CelResolvedOverloadTest.java | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java index a46a92fae..5d632e695 100644 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java @@ -72,9 +72,10 @@ default boolean canHandle(Object[] arguments) { } if (arg instanceof Exception || arg instanceof CelUnknownSet) { + // Only non-strict functions can accept errors/unknowns as arguments to a function if (!isStrict()) { - // Only non-strict functions can accept errors/unknowns as arguments to a function - return true; + // Skip assignability check below, but continue to validate remaining args + continue; } } diff --git a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java index 0fdc4f65d..40e2075aa 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java @@ -65,4 +65,35 @@ public void canHandle_nonMatchingTypes_returnsFalse() { public void canHandle_nonMatchingArgCount_returnsFalse() { assertThat(getIncrementIntOverload().canHandle(new Object[] {1L, 2L})).isFalse(); } + + @Test + public void canHandle_nonStrictOverload_returnsTrue() { + CelResolvedOverload nonStrictOverload = + CelResolvedOverload.of( + "non_strict", + (args) -> { + return false; + }, + /* isStrict= */ false, + Long.class, + Long.class); + assertThat( + nonStrictOverload.canHandle( + new Object[] {new RuntimeException(), CelUnknownSet.create()})) + .isTrue(); + } + + @Test + public void canHandle_nonStrictOverload_returnsFalse() { + CelResolvedOverload nonStrictOverload = + CelResolvedOverload.of( + "non_strict", + (args) -> { + return false; + }, + /* isStrict= */ false, + Long.class, + Long.class); + assertThat(nonStrictOverload.canHandle(new Object[] {new RuntimeException(), "Foo"})).isFalse(); + } } From 039290865ee46f73582f3f072b68758939b515f1 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 11 Nov 2025 12:21:33 -0800 Subject: [PATCH 015/100] Allow expected result type to be configured per AST validator instance PiperOrigin-RevId: 831021405 --- .../main/java/dev/cel/validator/BUILD.bazel | 2 ++ .../dev/cel/validator/CelAstValidator.java | 9 +++++++- .../dev/cel/validator/CelValidatorImpl.java | 4 +++- .../dev/cel/validator/validators/BUILD.bazel | 7 +++++-- .../validators/DurationLiteralValidator.java | 9 +++++--- .../HomogeneousLiteralValidator.java | 8 +++---- .../validators/LiteralValidator.java | 21 +++++++++++++------ .../validators/TimestampLiteralValidator.java | 9 +++++--- .../TimestampLiteralValidatorTest.java | 19 +++++++++++++++++ 9 files changed, 68 insertions(+), 20 deletions(-) diff --git a/validator/src/main/java/dev/cel/validator/BUILD.bazel b/validator/src/main/java/dev/cel/validator/BUILD.bazel index c9dd20575..95cf65f8a 100644 --- a/validator/src/main/java/dev/cel/validator/BUILD.bazel +++ b/validator/src/main/java/dev/cel/validator/BUILD.bazel @@ -72,6 +72,8 @@ java_library( "//common:compiler_common", "//common:source_location", "//common/navigation", + "//common/types", + "//common/types:type_providers", "@maven//:com_google_guava_guava", ], ) diff --git a/validator/src/main/java/dev/cel/validator/CelAstValidator.java b/validator/src/main/java/dev/cel/validator/CelAstValidator.java index 764864b4a..ae919696f 100644 --- a/validator/src/main/java/dev/cel/validator/CelAstValidator.java +++ b/validator/src/main/java/dev/cel/validator/CelAstValidator.java @@ -21,6 +21,8 @@ import dev.cel.common.CelSource; import dev.cel.common.CelSourceLocation; import dev.cel.common.navigation.CelNavigableAst; +import dev.cel.common.types.CelType; +import dev.cel.common.types.SimpleType; import java.util.Optional; /** Public interface for performing a single, custom validation on an AST. */ @@ -28,8 +30,13 @@ public interface CelAstValidator { void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issuesFactory); + /** Enforces a specific expected result type during validation, if set. */ + default CelType expectedResultType() { + return SimpleType.DYN; + } + /** Factory for populating issues while performing AST validation. */ - public final class IssuesFactory { + final class IssuesFactory { private final ImmutableList.Builder issuesBuilder; private final CelNavigableAst navigableAst; diff --git a/validator/src/main/java/dev/cel/validator/CelValidatorImpl.java b/validator/src/main/java/dev/cel/validator/CelValidatorImpl.java index 971868c82..9561799ca 100644 --- a/validator/src/main/java/dev/cel/validator/CelValidatorImpl.java +++ b/validator/src/main/java/dev/cel/validator/CelValidatorImpl.java @@ -48,9 +48,11 @@ public CelValidationResult validate(CelAbstractSyntaxTree ast) { ImmutableList.Builder issueBuilder = ImmutableList.builder(); for (CelAstValidator validator : astValidators) { + Cel celEnv = this.cel.toCelBuilder().setResultType(validator.expectedResultType()).build(); + CelNavigableAst navigableAst = CelNavigableAst.fromAst(ast); IssuesFactory issuesFactory = new IssuesFactory(navigableAst); - validator.validate(navigableAst, cel, issuesFactory); + validator.validate(navigableAst, celEnv, issuesFactory); issueBuilder.addAll(issuesFactory.getIssues()); } diff --git a/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel b/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel index d24cbd20c..93a2c0d28 100644 --- a/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel @@ -17,7 +17,8 @@ java_library( ], deps = [ ":literal_validator", - "@maven//:com_google_protobuf_protobuf_java", + "//common/types", + "//common/types:type_providers", ], ) @@ -30,7 +31,8 @@ java_library( ], deps = [ ":literal_validator", - "@maven//:com_google_protobuf_protobuf_java", + "//common/types", + "//common/types:type_providers", ], ) @@ -116,6 +118,7 @@ java_library( "//common/ast", "//common/ast:expr_factory", "//common/navigation", + "//common/types:type_providers", "//runtime", "//validator:ast_validator", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java b/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java index f0220e6ab..0670893b3 100644 --- a/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java +++ b/validator/src/main/java/dev/cel/validator/validators/DurationLiteralValidator.java @@ -14,14 +14,17 @@ package dev.cel.validator.validators; +import dev.cel.common.types.CelType; +import dev.cel.common.types.SimpleType; import java.time.Duration; /** DurationLiteralValidator ensures that duration literal arguments are valid. */ public final class DurationLiteralValidator extends LiteralValidator { public static final DurationLiteralValidator INSTANCE = - new DurationLiteralValidator("duration", Duration.class); + new DurationLiteralValidator("duration", Duration.class, SimpleType.DURATION); - private DurationLiteralValidator(String functionName, Class expectedResultType) { - super(functionName, expectedResultType); + private DurationLiteralValidator( + String functionName, Class expectedJavaType, CelType expectedResultType) { + super(functionName, expectedJavaType, expectedResultType); } } diff --git a/validator/src/main/java/dev/cel/validator/validators/HomogeneousLiteralValidator.java b/validator/src/main/java/dev/cel/validator/validators/HomogeneousLiteralValidator.java index 58947f0fb..f83be31c6 100644 --- a/validator/src/main/java/dev/cel/validator/validators/HomogeneousLiteralValidator.java +++ b/validator/src/main/java/dev/cel/validator/validators/HomogeneousLiteralValidator.java @@ -38,16 +38,16 @@ public final class HomogeneousLiteralValidator implements CelAstValidator { private final ImmutableSet exemptFunctions; /** - * Construct a new instance of {@link HomogeneousLiteralValidator}. This validator will not for - * functions in {@code exemptFunctions}. + * Construct a new instance of {@link HomogeneousLiteralValidator}. This validator will not run + * for functions in {@code exemptFunctions}. */ public static HomogeneousLiteralValidator newInstance(Iterable exemptFunctions) { return new HomogeneousLiteralValidator(exemptFunctions); } /** - * Construct a new instance of {@link HomogeneousLiteralValidator}. This validator will not for - * functions in {@code exemptFunctions}. + * Construct a new instance of {@link HomogeneousLiteralValidator}. This validator will not run + * for functions in {@code exemptFunctions}. */ public static HomogeneousLiteralValidator newInstance(String... exemptFunctions) { return newInstance(Arrays.asList(exemptFunctions)); diff --git a/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java b/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java index e16b9369e..6d48c4f0c 100644 --- a/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java +++ b/validator/src/main/java/dev/cel/validator/validators/LiteralValidator.java @@ -24,6 +24,7 @@ import dev.cel.common.ast.CelExprFactory; import dev.cel.common.navigation.CelNavigableAst; import dev.cel.common.navigation.CelNavigableExpr; +import dev.cel.common.types.CelType; import dev.cel.runtime.CelEvaluationException; import dev.cel.validator.CelAstValidator; @@ -34,13 +35,21 @@ */ public abstract class LiteralValidator implements CelAstValidator { private final String functionName; - private final Class expectedResultType; + private final Class expectedJavaType; + private final CelType expectedResultType; - protected LiteralValidator(String functionName, Class expectedResultType) { + protected LiteralValidator( + String functionName, Class expectedJavaType, CelType expectedResultType) { this.functionName = functionName; + this.expectedJavaType = expectedJavaType; this.expectedResultType = expectedResultType; } + @Override + public CelType expectedResultType() { + return expectedResultType; + } + @Override public void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issuesFactory) { CelExprFactory exprFactory = CelExprFactory.newInstance(); @@ -61,7 +70,7 @@ public void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issues CelExpr callExpr = exprFactory.newGlobalCall(functionName, exprFactory.newConstant(expr.constant())); try { - evaluateExpr(cel, callExpr, expectedResultType); + evaluateExpr(cel, callExpr, expectedJavaType); } catch (Exception e) { issuesFactory.addError( expr.id(), @@ -72,18 +81,18 @@ public void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issues } @CanIgnoreReturnValue - private static Object evaluateExpr(Cel cel, CelExpr expr, Class expectedResultType) + private static Object evaluateExpr(Cel cel, CelExpr expr, Class expectedJavaType) throws CelValidationException, CelEvaluationException { CelAbstractSyntaxTree ast = CelAbstractSyntaxTree.newParsedAst(expr, CelSource.newBuilder().build()); ast = cel.check(ast).getAst(); Object result = cel.createProgram(ast).eval(); - if (!expectedResultType.isInstance(result)) { + if (!expectedJavaType.isInstance(result)) { throw new IllegalStateException( String.format( "Expected %s type but got %s instead", - expectedResultType.getName(), result.getClass().getName())); + expectedJavaType.getName(), result.getClass().getName())); } return result; } diff --git a/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java b/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java index f1167f4eb..da3630548 100644 --- a/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java +++ b/validator/src/main/java/dev/cel/validator/validators/TimestampLiteralValidator.java @@ -14,14 +14,17 @@ package dev.cel.validator.validators; +import dev.cel.common.types.CelType; +import dev.cel.common.types.SimpleType; import java.time.Instant; /** TimestampLiteralValidator ensures that timestamp literal arguments are valid. */ public final class TimestampLiteralValidator extends LiteralValidator { public static final TimestampLiteralValidator INSTANCE = - new TimestampLiteralValidator("timestamp", Instant.class); + new TimestampLiteralValidator("timestamp", Instant.class, SimpleType.TIMESTAMP); - private TimestampLiteralValidator(String functionName, Class expectedResultType) { - super(functionName, expectedResultType); + private TimestampLiteralValidator( + String functionName, Class expectedJavaType, CelType expectedResultType) { + super(functionName, expectedJavaType, expectedResultType); } } diff --git a/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java b/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java index d263fc6bf..7770df54c 100644 --- a/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java +++ b/validator/src/test/java/dev/cel/validator/validators/TimestampLiteralValidatorTest.java @@ -200,4 +200,23 @@ public void parentIsNotCallExpr_doesNotThrow(String source) throws Exception { assertThat(result.hasError()).isFalse(); assertThat(result.getAllIssues()).isEmpty(); } + + @Test + public void env_withSetResultType_success() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .setOptions(CelOptions.current().enableTimestampEpoch(true).build()) + .setResultType(SimpleType.BOOL) + .build(); + CelValidator validator = + CelValidatorFactory.standardCelValidatorBuilder(cel) + .addAstValidators(TimestampLiteralValidator.INSTANCE) + .build(); + CelAbstractSyntaxTree ast = cel.compile("timestamp(123) == timestamp(123)").getAst(); + + CelValidationResult result = validator.validate(ast); + + assertThat(result.hasError()).isFalse(); + assertThat(result.getAllIssues()).isEmpty(); + } } From 4cacabb482ebb03a4565c68bc58e10d58641dcf9 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 11 Nov 2025 12:53:13 -0800 Subject: [PATCH 016/100] Add new interfaces for ProgramPlanner, plan constants PiperOrigin-RevId: 831032679 --- runtime/planner/BUILD.bazel | 11 ++ .../java/dev/cel/runtime/planner/BUILD.bazel | 58 +++++++++ .../dev/cel/runtime/planner/EvalConstant.java | 116 ++++++++++++++++++ .../cel/runtime/planner/PlannedProgram.java | 50 ++++++++ .../cel/runtime/planner/ProgramPlanner.java | 103 ++++++++++++++++ .../java/dev/cel/runtime/planner/BUILD.bazel | 42 +++++++ .../runtime/planner/ProgramPlannerTest.java | 92 ++++++++++++++ 7 files changed, 472 insertions(+) create mode 100644 runtime/planner/BUILD.bazel create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java create mode 100644 runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel create mode 100644 runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java diff --git a/runtime/planner/BUILD.bazel b/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..8da29f270 --- /dev/null +++ b/runtime/planner/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//:internal"], +) + +java_library( + name = "program_planner", + exports = ["//runtime/src/main/java/dev/cel/runtime/planner:program_planner"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..e3fb32d30 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -0,0 +1,58 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//runtime/planner:__pkg__", + ], +) + +java_library( + name = "program_planner", + srcs = ["ProgramPlanner.java"], + tags = [ + ], + deps = [ + ":eval_const", + ":planned_program", + "//:auto_value", + "//common:cel_ast", + "//common/annotations", + "//common/ast", + "//common/types:type_providers", + "//runtime:evaluation_exception", + "//runtime:evaluation_exception_builder", + "//runtime:interpretable", + "//runtime:program", + "@maven//:com_google_code_findbugs_annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "planned_program", + srcs = ["PlannedProgram.java"], + deps = [ + "//:auto_value", + "//runtime:evaluation_exception", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:program", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "eval_const", + srcs = ["EvalConstant.java"], + deps = [ + "//common/values", + "//common/values:cel_byte_string", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java new file mode 100644 index 000000000..ca4ff49ec --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -0,0 +1,116 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.primitives.UnsignedLong; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelByteString; +import dev.cel.common.values.NullValue; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +@Immutable +final class EvalConstant implements Interpretable { + + // Pre-allocation of common constants + private static final EvalConstant NULL_VALUE = new EvalConstant(NullValue.NULL_VALUE); + private static final EvalConstant TRUE = new EvalConstant(true); + private static final EvalConstant FALSE = new EvalConstant(false); + private static final EvalConstant ZERO = new EvalConstant(0L); + private static final EvalConstant ONE = new EvalConstant(1L); + private static final EvalConstant UNSIGNED_ZERO = new EvalConstant(UnsignedLong.ZERO); + private static final EvalConstant UNSIGNED_ONE = new EvalConstant(UnsignedLong.ONE); + private static final EvalConstant EMPTY_STRING = new EvalConstant(""); + private static final EvalConstant EMPTY_BYTES = new EvalConstant(CelByteString.EMPTY); + + @SuppressWarnings("Immutable") // Known CEL constants that aren't mutated are stored + private final Object constant; + + @Override + public Object eval(GlobalResolver resolver) { + return constant; + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + return constant; + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + return constant; + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + return constant; + } + + static EvalConstant create(boolean value) { + return value ? TRUE : FALSE; + } + + static EvalConstant create(String value) { + if (value.isEmpty()) { + return EMPTY_STRING; + } + + return new EvalConstant(value); + } + + static EvalConstant create(long value) { + if (value == 0L) { + return ZERO; + } else if (value == 1L) { + return ONE; + } + + return new EvalConstant(Long.valueOf(value)); + } + + static EvalConstant create(double value) { + return new EvalConstant(Double.valueOf(value)); + } + + static EvalConstant create(UnsignedLong unsignedLong) { + if (unsignedLong.longValue() == 0L) { + return UNSIGNED_ZERO; + } else if (unsignedLong.longValue() == 1L) { + return UNSIGNED_ONE; + } + + return new EvalConstant(unsignedLong); + } + + static EvalConstant create(NullValue unused) { + return NULL_VALUE; + } + + static EvalConstant create(CelByteString byteString) { + if (byteString.isEmpty()) { + return EMPTY_BYTES; + } + return new EvalConstant(byteString); + } + + private EvalConstant(Object constant) { + this.constant = constant; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java new file mode 100644 index 000000000..bc6a113cd --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -0,0 +1,50 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.auto.value.AutoValue; +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; +import dev.cel.runtime.Program; +import java.util.Map; + +@Immutable +@AutoValue +abstract class PlannedProgram implements Program { + abstract Interpretable interpretable(); + + @Override + public Object eval() throws CelEvaluationException { + return interpretable().eval(GlobalResolver.EMPTY); + } + + @Override + public Object eval(Map mapValue) throws CelEvaluationException { + throw new UnsupportedOperationException("Not yet implemented"); + } + + @Override + public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + throw new UnsupportedOperationException("Late bound functions not supported yet"); + } + + static Program create(Interpretable interpretable) { + return new AutoValue_PlannedProgram(interpretable); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java new file mode 100644 index 000000000..192f9791c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -0,0 +1,103 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import javax.annotation.concurrent.ThreadSafe; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.annotations.Internal; +import dev.cel.common.ast.CelConstant; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelReference; +import dev.cel.common.types.CelType; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationExceptionBuilder; +import dev.cel.runtime.Interpretable; +import dev.cel.runtime.Program; + +/** + * {@code ProgramPlanner} resolves functions, types, and identifiers at plan time given a + * parsed-only or a type-checked expression. + */ +@ThreadSafe +@Internal +public final class ProgramPlanner { + + /** + * Plans a {@link Program} from the provided parsed-only or type-checked {@link + * CelAbstractSyntaxTree}. + */ + public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException { + Interpretable plannedInterpretable; + try { + plannedInterpretable = plan(ast.getExpr(), PlannerContext.create(ast)); + } catch (RuntimeException e) { + throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e).build(); + } + + return PlannedProgram.create(plannedInterpretable); + } + + private Interpretable plan(CelExpr celExpr, PlannerContext unused) { + switch (celExpr.getKind()) { + case CONSTANT: + return planConstant(celExpr.constant()); + case NOT_SET: + throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); + default: + throw new IllegalArgumentException("Not yet implemented kind: " + celExpr.getKind()); + } + } + + private Interpretable planConstant(CelConstant celConstant) { + switch (celConstant.getKind()) { + case NULL_VALUE: + return EvalConstant.create(celConstant.nullValue()); + case BOOLEAN_VALUE: + return EvalConstant.create(celConstant.booleanValue()); + case INT64_VALUE: + return EvalConstant.create(celConstant.int64Value()); + case UINT64_VALUE: + return EvalConstant.create(celConstant.uint64Value()); + case DOUBLE_VALUE: + return EvalConstant.create(celConstant.doubleValue()); + case STRING_VALUE: + return EvalConstant.create(celConstant.stringValue()); + case BYTES_VALUE: + return EvalConstant.create(celConstant.bytesValue()); + default: + throw new IllegalStateException("Unsupported kind: " + celConstant.getKind()); + } + } + + @AutoValue + abstract static class PlannerContext { + + abstract ImmutableMap referenceMap(); + + abstract ImmutableMap typeMap(); + + private static PlannerContext create(CelAbstractSyntaxTree ast) { + return new AutoValue_ProgramPlanner_PlannerContext(ast.getReferenceMap(), ast.getTypeMap()); + } + } + + public static ProgramPlanner newPlanner() { + return new ProgramPlanner(); + } + + private ProgramPlanner() {} +} diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel new file mode 100644 index 000000000..08c7a7a7b --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_java//java:defs.bzl", "java_library") +load("//:testing.bzl", "junit4_test_suites") + +package( + default_applicable_licenses = ["//:license"], + default_testonly = True, +) + +java_library( + name = "tests", + testonly = 1, + srcs = glob( + ["*.java"], + ), + deps = [ + "//:java_truth", + "//common:cel_ast", + "//common:cel_source", + "//common/ast", + "//common/values", + "//common/values:cel_byte_string", + "//compiler", + "//compiler:compiler_builder", + "//runtime", + "//runtime:program", + "//runtime/planner:program_planner", + "@maven//:com_google_guava_guava", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + +junit4_test_suites( + name = "test_suites", + sizes = [ + "small", + ], + src_dir = "src/test/java", + deps = [ + ":tests", + ], +) diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java new file mode 100644 index 000000000..4dbb181b7 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -0,0 +1,92 @@ +// Copyright 2025 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.runtime.planner; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertThrows; + +import com.google.common.primitives.UnsignedLong; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelSource; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.values.CelByteString; +import dev.cel.common.values.NullValue; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.Program; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public final class ProgramPlannerTest { + private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner(); + private static final CelCompiler CEL_COMPILER = + CelCompilerFactory.standardCelCompilerBuilder().build(); + + @TestParameter boolean isParseOnly; + + @Test + public void plan_notSet_throws() { + CelAbstractSyntaxTree invalidAst = + CelAbstractSyntaxTree.newParsedAst(CelExpr.ofNotSet(0L), CelSource.newBuilder().build()); + + CelEvaluationException e = + assertThrows(CelEvaluationException.class, () -> PLANNER.plan(invalidAst)); + + assertThat(e).hasMessageThat().contains("evaluation error: Unsupported kind: NOT_SET"); + } + + @Test + public void plan_constant(@TestParameter ConstantTestCase testCase) throws Exception { + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(testCase.expected); + } + + private CelAbstractSyntaxTree compile(String expression) throws Exception { + CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); + if (isParseOnly) { + return ast; + } + + return CEL_COMPILER.check(ast).getAst(); + } + + @SuppressWarnings("ImmutableEnumChecker") // Test only + private enum ConstantTestCase { + NULL("null", NullValue.NULL_VALUE), + BOOLEAN("true", true), + INT64("42", 42L), + UINT64("42u", UnsignedLong.valueOf(42)), + DOUBLE("1.5", 1.5d), + STRING("'hello world'", "hello world"), + BYTES("b'abc'", CelByteString.of("abc".getBytes(UTF_8))); + + private final String expression; + private final Object expected; + + ConstantTestCase(String expression, Object expected) { + this.expression = expression; + this.expected = expected; + } + } +} From d8fb28641387ebe2080abf246893a99ed2088fc5 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Wed, 12 Nov 2025 12:24:19 -0800 Subject: [PATCH 017/100] Accumulate unknowns into a set to avoid intermediate duplication. PiperOrigin-RevId: 831492436 --- .../dev/cel/runtime/AccumulatedUnknowns.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java index 9d58549fd..77435a042 100644 --- a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java +++ b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java @@ -18,7 +18,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.List; +import java.util.HashSet; +import java.util.Set; /** * An internal representation used for fast accumulation of unknown expr IDs and attributes. For @@ -27,14 +28,14 @@ */ final class AccumulatedUnknowns { private static final int MAX_UNKNOWN_ATTRIBUTE_SIZE = 500_000; - private final List exprIds; - private final List attributes; + private final Set exprIds; + private final Set attributes; - List exprIds() { + Set exprIds() { return exprIds; } - List attributes() { + Set attributes() { return attributes; } @@ -55,19 +56,20 @@ static AccumulatedUnknowns create(Collection ids) { } static AccumulatedUnknowns create(Collection exprIds, Collection attributes) { - return new AccumulatedUnknowns(new ArrayList<>(exprIds), new ArrayList<>(attributes)); + return new AccumulatedUnknowns(new HashSet<>(exprIds), new HashSet<>(attributes)); } private static void enforceMaxAttributeSize( - List lhsAttributes, List rhsAttributes) { - int totalSize = lhsAttributes.size() + rhsAttributes.size(); - if (totalSize > MAX_UNKNOWN_ATTRIBUTE_SIZE) { + Set lhsAttributes, Set rhsAttributes) { + if (lhsAttributes.size() + rhsAttributes.size() > MAX_UNKNOWN_ATTRIBUTE_SIZE) { throw new IllegalArgumentException( - "Exceeded maximum allowed unknown attributes: " + totalSize); + String.format( + "Exceeded maximum allowed unknown attributes when merging: %s, %s", + lhsAttributes.size(), rhsAttributes.size())); } } - private AccumulatedUnknowns(List exprIds, List attributes) { + private AccumulatedUnknowns(Set exprIds, Set attributes) { this.exprIds = exprIds; this.attributes = attributes; } From 5ee0d7ba691e3c3ce7497ee4468d2fef9506d99e Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 13 Nov 2025 14:57:56 -0800 Subject: [PATCH 018/100] Instantiate List/Map values with expected size PiperOrigin-RevId: 832021501 --- .../main/java/dev/cel/common/values/CelValueConverter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index a281318f8..5286ae288 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -41,7 +41,8 @@ public Object fromCelValueToJavaObject(CelValue celValue) { if (celValue instanceof MapValue) { MapValue mapValue = (MapValue) celValue; - ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); + ImmutableMap.Builder mapBuilder = + ImmutableMap.builderWithExpectedSize(mapValue.size()); for (Entry entry : mapValue.value().entrySet()) { Object key = fromCelValueToJavaObject(entry.getKey()); Object value = fromCelValueToJavaObject(entry.getValue()); @@ -51,7 +52,8 @@ public Object fromCelValueToJavaObject(CelValue celValue) { return mapBuilder.buildOrThrow(); } else if (celValue instanceof ListValue) { ListValue listValue = (ListValue) celValue; - ImmutableList.Builder listBuilder = ImmutableList.builder(); + ImmutableList.Builder listBuilder = + ImmutableList.builderWithExpectedSize(listValue.size()); for (CelValue element : listValue.value()) { listBuilder.add(fromCelValueToJavaObject(element)); } From 1c1dcc47d97f43eea3e513ca65f5a966f2ce246f Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 17 Nov 2025 13:56:15 -0800 Subject: [PATCH 019/100] Plan identifiers PiperOrigin-RevId: 833484173 --- .../java/dev/cel/common/types/BUILD.bazel | 14 +++ .../cel/common/types/DefaultTypeProvider.java | 52 +++++++++ .../java/dev/cel/common/types/SimpleType.java | 2 +- common/types/BUILD.bazel | 6 ++ .../dev/cel/runtime/planner/Attribute.java | 77 ++++++++++++++ .../cel/runtime/planner/AttributeFactory.java | 49 +++++++++ .../java/dev/cel/runtime/planner/BUILD.bazel | 34 ++++++ .../cel/runtime/planner/EvalAttribute.java | 61 +++++++++++ .../dev/cel/runtime/planner/EvalConstant.java | 4 + .../cel/runtime/planner/PlannedProgram.java | 3 +- .../cel/runtime/planner/ProgramPlanner.java | 54 +++++++++- .../java/dev/cel/runtime/planner/BUILD.bazel | 6 ++ .../runtime/planner/ProgramPlannerTest.java | 100 +++++++++++++++++- 13 files changed, 454 insertions(+), 8 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/Attribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java diff --git a/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel index 579fd31c5..a35a897b8 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -183,6 +183,20 @@ java_library( ], ) +java_library( + name = "default_type_provider", + srcs = [ + "DefaultTypeProvider.java", + ], + tags = [ + ], + deps = [ + ":type_providers", + ":types", + "@maven//:com_google_guava_guava", + ], +) + cel_android_library( name = "cel_types_android", srcs = ["CelTypes.java"], diff --git a/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java new file mode 100644 index 000000000..84e6c9ede --- /dev/null +++ b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java @@ -0,0 +1,52 @@ +// Copyright 2025 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.common.types; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; +import java.util.Optional; + +/** {@code DefaultTypeProvider} is a registry of common CEL types. */ +public class DefaultTypeProvider implements CelTypeProvider { + + private static final DefaultTypeProvider INSTANCE = new DefaultTypeProvider(); + private final ImmutableMap commonTypes; + + @Override + public ImmutableCollection types() { + return commonTypes.values(); + } + + @Override + public Optional findType(String typeName) { + return Optional.ofNullable(commonTypes.get(typeName)); + } + + public static DefaultTypeProvider getInstance() { + return INSTANCE; + } + + private DefaultTypeProvider() { + ImmutableMap.Builder typeMapBuilder = ImmutableMap.builder(); + typeMapBuilder.putAll(SimpleType.TYPE_MAP); + typeMapBuilder.put("list", ListType.create(SimpleType.DYN)); + typeMapBuilder.put("map", MapType.create(SimpleType.DYN, SimpleType.DYN)); + typeMapBuilder.put( + "optional_type", + // TODO: Move to CelOptionalLibrary and register it on demand + OptionalType.create(SimpleType.DYN)); + this.commonTypes = typeMapBuilder.buildOrThrow(); + } +} diff --git a/common/src/main/java/dev/cel/common/types/SimpleType.java b/common/src/main/java/dev/cel/common/types/SimpleType.java index cbf02e9c6..6c43ab53f 100644 --- a/common/src/main/java/dev/cel/common/types/SimpleType.java +++ b/common/src/main/java/dev/cel/common/types/SimpleType.java @@ -44,7 +44,7 @@ public abstract class SimpleType extends CelType { public static final CelType TIMESTAMP = create(CelKind.TIMESTAMP, "google.protobuf.Timestamp"); public static final CelType UINT = create(CelKind.UINT, "uint"); - private static final ImmutableMap TYPE_MAP = + public static final ImmutableMap TYPE_MAP = ImmutableMap.of( DYN.name(), DYN, BOOL.name(), BOOL, diff --git a/common/types/BUILD.bazel b/common/types/BUILD.bazel index 6c2b1a269..41d3d59b2 100644 --- a/common/types/BUILD.bazel +++ b/common/types/BUILD.bazel @@ -50,6 +50,12 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/types:cel_proto_message_types"], ) +java_library( + name = "default_type_provider", + visibility = ["//:internal"], + exports = ["//common/src/main/java/dev/cel/common/types:default_type_provider"], +) + java_library( name = "cel_v1alpha1_types", visibility = ["//:internal"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java new file mode 100644 index 000000000..0ce487ceb --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -0,0 +1,77 @@ +// Copyright 2025 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.runtime.planner; + + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.TypeType; +import dev.cel.runtime.GlobalResolver; + +@Immutable +interface Attribute { + Object resolve(GlobalResolver ctx); + + final class MaybeAttribute implements Attribute { + private final ImmutableList attributes; + + @Override + public Object resolve(GlobalResolver ctx) { + for (Attribute attr : attributes) { + Object value = attr.resolve(ctx); + if (value != null) { + return value; + } + } + + // TODO: Handle unknowns + throw new UnsupportedOperationException("Unknown attributes is not supported yet"); + } + + MaybeAttribute(ImmutableList attributes) { + this.attributes = attributes; + } + } + + final class NamespacedAttribute implements Attribute { + private final ImmutableList namespacedNames; + private final CelTypeProvider typeProvider; + + @Override + public Object resolve(GlobalResolver ctx) { + for (String name : namespacedNames) { + Object value = ctx.resolve(name); + if (value != null) { + // TODO: apply qualifiers + return value; + } + + TypeType type = typeProvider.findType(name).map(TypeType::create).orElse(null); + if (type != null) { + return type; + } + } + + // TODO: Handle unknowns + throw new UnsupportedOperationException("Unknown attributes is not supported yet"); + } + + NamespacedAttribute(CelTypeProvider typeProvider, ImmutableList namespacedNames) { + this.typeProvider = typeProvider; + this.namespacedNames = namespacedNames; + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java new file mode 100644 index 000000000..bda7c46a6 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java @@ -0,0 +1,49 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.CelContainer; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.runtime.planner.Attribute.MaybeAttribute; +import dev.cel.runtime.planner.Attribute.NamespacedAttribute; + +@Immutable +final class AttributeFactory { + + private final CelContainer unusedContainer; + private final CelTypeProvider typeProvider; + + NamespacedAttribute newAbsoluteAttribute(String... names) { + return new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)); + } + + MaybeAttribute newMaybeAttribute(String... names) { + // TODO: Resolve container names + return new MaybeAttribute( + ImmutableList.of(new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)))); + } + + static AttributeFactory newAttributeFactory( + CelContainer celContainer, CelTypeProvider typeProvider) { + return new AttributeFactory(celContainer, typeProvider); + } + + private AttributeFactory(CelContainer container, CelTypeProvider typeProvider) { + this.unusedContainer = container; + this.typeProvider = typeProvider; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index e3fb32d30..e90fe6d19 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -13,12 +13,16 @@ java_library( tags = [ ], deps = [ + ":attribute", + ":eval_attribute", ":eval_const", ":planned_program", "//:auto_value", "//common:cel_ast", + "//common:container", "//common/annotations", "//common/ast", + "//common/types", "//common/types:type_providers", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", @@ -35,6 +39,7 @@ java_library( srcs = ["PlannedProgram.java"], deps = [ "//:auto_value", + "//runtime:activation", "//runtime:evaluation_exception", "//runtime:function_resolver", "//runtime:interpretable", @@ -56,3 +61,32 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_library( + name = "attribute", + srcs = [ + "Attribute.java", + "AttributeFactory.java", + ], + deps = [ + "//common:container", + "//common/types", + "//common/types:type_providers", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_attribute", + srcs = ["EvalAttribute.java"], + deps = [ + ":attribute", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java new file mode 100644 index 000000000..23b7fa63c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -0,0 +1,61 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +@Immutable +final class EvalAttribute implements Interpretable { + + private final Attribute attr; + + @Override + public Object eval(GlobalResolver resolver) { + return attr.resolve(resolver); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalAttribute create(Attribute attr) { + return new EvalAttribute(attr); + } + + private EvalAttribute(Attribute attr) { + this.attr = attr; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java index ca4ff49ec..a1a2dc998 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -110,6 +110,10 @@ static EvalConstant create(CelByteString byteString) { return new EvalConstant(byteString); } + static EvalConstant create(Object value) { + return new EvalConstant(value); + } + private EvalConstant(Object constant) { this.constant = constant; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index bc6a113cd..c8aa1bc0d 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -16,6 +16,7 @@ import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.Activation; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; @@ -35,7 +36,7 @@ public Object eval() throws CelEvaluationException { @Override public Object eval(Map mapValue) throws CelEvaluationException { - throw new UnsupportedOperationException("Not yet implemented"); + return interpretable().eval(Activation.copyOf(mapValue)); } @Override diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 192f9791c..e482a0bac 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -18,15 +18,20 @@ import com.google.common.collect.ImmutableMap; import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelReference; +import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.TypeType; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.Interpretable; import dev.cel.runtime.Program; +import java.util.NoSuchElementException; /** * {@code ProgramPlanner} resolves functions, types, and identifiers at plan time given a @@ -36,6 +41,9 @@ @Internal public final class ProgramPlanner { + private final CelTypeProvider typeProvider; + private final AttributeFactory attributeFactory; + /** * Plans a {@link Program} from the provided parsed-only or type-checked {@link * CelAbstractSyntaxTree}. @@ -51,10 +59,12 @@ public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException { return PlannedProgram.create(plannedInterpretable); } - private Interpretable plan(CelExpr celExpr, PlannerContext unused) { + private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { switch (celExpr.getKind()) { case CONSTANT: return planConstant(celExpr.constant()); + case IDENT: + return planIdent(celExpr, ctx); case NOT_SET: throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); default: @@ -83,6 +93,37 @@ private Interpretable planConstant(CelConstant celConstant) { } } + private Interpretable planIdent(CelExpr celExpr, PlannerContext ctx) { + CelReference ref = ctx.referenceMap().get(celExpr.id()); + if (ref != null) { + return planCheckedIdent(celExpr.id(), ref, ctx.typeMap()); + } + + return EvalAttribute.create(attributeFactory.newMaybeAttribute(celExpr.ident().name())); + } + + private Interpretable planCheckedIdent( + long id, CelReference identRef, ImmutableMap typeMap) { + if (identRef.value().isPresent()) { + return planConstant(identRef.value().get()); + } + + CelType type = typeMap.get(id); + if (type.kind().equals(CelKind.TYPE)) { + TypeType identType = + typeProvider + .findType(identRef.name()) + .map(TypeType::create) + .orElseThrow( + () -> + new NoSuchElementException( + "Reference to an undefined type: " + identRef.name())); + return EvalConstant.create(identType); + } + + return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name())); + } + @AutoValue abstract static class PlannerContext { @@ -95,9 +136,14 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) { } } - public static ProgramPlanner newPlanner() { - return new ProgramPlanner(); + public static ProgramPlanner newPlanner(CelTypeProvider typeProvider) { + return new ProgramPlanner(typeProvider); } - private ProgramPlanner() {} + private ProgramPlanner(CelTypeProvider typeProvider) { + this.typeProvider = typeProvider; + // TODO: Container support + this.attributeFactory = + AttributeFactory.newAttributeFactory(CelContainer.newBuilder().build(), typeProvider); + } } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 08c7a7a7b..46ef0f279 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,13 +17,19 @@ java_library( "//common:cel_ast", "//common:cel_source", "//common/ast", + "//common/types", + "//common/types:default_type_provider", + "//common/types:message_type_provider", + "//common/types:type_providers", "//common/values", "//common/values:cel_byte_string", "//compiler", "//compiler:compiler_builder", + "//extensions", "//runtime", "//runtime:program", "//runtime/planner:program_planner", + "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 4dbb181b7..ce159755f 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -18,16 +18,31 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelSource; import dev.cel.common.ast.CelExpr; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.CelTypeProvider.CombinedCelTypeProvider; +import dev.cel.common.types.DefaultTypeProvider; +import dev.cel.common.types.ListType; +import dev.cel.common.types.MapType; +import dev.cel.common.types.OptionalType; +import dev.cel.common.types.ProtoMessageTypeProvider; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; import dev.cel.common.values.NullValue; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; +import dev.cel.expr.conformance.proto3.GlobalEnum; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.extensions.CelExtensions; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.Program; import org.junit.Test; @@ -35,9 +50,19 @@ @RunWith(TestParameterInjector.class) public final class ProgramPlannerTest { - private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner(); + // Note that the following deps will be built from top-level builder APIs + private static final CelTypeProvider TYPE_PROVIDER = + new CombinedCelTypeProvider( + DefaultTypeProvider.getInstance(), + new ProtoMessageTypeProvider(ImmutableSet.of(TestAllTypes.getDescriptor()))); + + private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner(TYPE_PROVIDER); private static final CelCompiler CEL_COMPILER = - CelCompilerFactory.standardCelCompilerBuilder().build(); + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("int_var", SimpleType.INT) + .addMessageTypes(TestAllTypes.getDescriptor()) + .addLibraries(CelExtensions.optional()) + .build(); @TestParameter boolean isParseOnly; @@ -62,6 +87,49 @@ public void plan_constant(@TestParameter ConstantTestCase testCase) throws Excep assertThat(result).isEqualTo(testCase.expected); } + @Test + public void planIdent_enum() throws Exception { + if (isParseOnly) { + // TODO Skip for now, requires attribute qualification + return; + } + CelAbstractSyntaxTree ast = + compile(GlobalEnum.getDescriptor().getFullName() + "." + GlobalEnum.GAR); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(1); + } + + @Test + public void planIdent_variable() throws Exception { + CelAbstractSyntaxTree ast = compile("int_var"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("int_var", 1L)); + + assertThat(result).isEqualTo(1); + } + + @Test + public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { + if (isParseOnly) { + if (testCase.equals(TypeLiteralTestCase.DURATION) + || testCase.equals(TypeLiteralTestCase.TIMESTAMP) + || testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) { + // TODO Skip for now, requires attribute qualification + return; + } + } + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + TypeType result = (TypeType) program.eval(); + + assertThat(result).isEqualTo(testCase.type); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { @@ -89,4 +157,32 @@ private enum ConstantTestCase { this.expected = expected; } } + + @SuppressWarnings("ImmutableEnumChecker") // Test only + private enum TypeLiteralTestCase { + BOOL("bool", SimpleType.BOOL), + BYTES("bytes", SimpleType.BYTES), + DOUBLE("double", SimpleType.DOUBLE), + INT("int", SimpleType.INT), + UINT("uint", SimpleType.UINT), + STRING("string", SimpleType.STRING), + DYN("dyn", SimpleType.DYN), + LIST("list", ListType.create(SimpleType.DYN)), + MAP("map", MapType.create(SimpleType.DYN, SimpleType.DYN)), + NULL("null_type", SimpleType.NULL_TYPE), + DURATION("google.protobuf.Duration", SimpleType.DURATION), + TIMESTAMP("google.protobuf.Timestamp", SimpleType.TIMESTAMP), + OPTIONAL("optional_type", OptionalType.create(SimpleType.DYN)), + PROTO_MESSAGE_TYPE( + "cel.expr.conformance.proto3.TestAllTypes", + TYPE_PROVIDER.findType(TestAllTypes.getDescriptor().getFullName()).get()); + + private final String expression; + private final TypeType type; + + TypeLiteralTestCase(String expression, CelType type) { + this.expression = expression; + this.type = TypeType.create(type); + } + } } From 02ac761a576388e42c326e7b353523b7f4306490 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 17 Nov 2025 14:51:18 -0800 Subject: [PATCH 020/100] Plan CreateList PiperOrigin-RevId: 833505696 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 14 ++++ .../cel/runtime/planner/EvalCreateList.java | 67 +++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 17 +++++ .../runtime/planner/ProgramPlannerTest.java | 16 ++++- 4 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index e90fe6d19..4a550ede8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -16,6 +16,7 @@ java_library( ":attribute", ":eval_attribute", ":eval_const", + ":eval_create_list", ":planned_program", "//:auto_value", "//common:cel_ast", @@ -90,3 +91,16 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_library( + name = "eval_create_list", + srcs = ["EvalCreateList.java"], + deps = [ + "//runtime", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java new file mode 100644 index 000000000..6a1917475 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -0,0 +1,67 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +@Immutable +final class EvalCreateList implements Interpretable { + + // Array contents are not mutated + @SuppressWarnings("Immutable") + private final Interpretable[] values; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(values.length); + for (Interpretable value : values) { + builder.add(value.eval(resolver)); + } + return builder.build(); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalCreateList create(Interpretable[] values) { + return new EvalCreateList(values); + } + + private EvalCreateList(Interpretable[] values) { + this.values = values; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index e482a0bac..80ffdab6a 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -15,6 +15,7 @@ package dev.cel.runtime.planner; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; @@ -22,6 +23,7 @@ import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelReference; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; @@ -65,6 +67,8 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { return planConstant(celExpr.constant()); case IDENT: return planIdent(celExpr, ctx); + case LIST: + return planCreateList(celExpr, ctx); case NOT_SET: throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); default: @@ -124,6 +128,19 @@ private Interpretable planCheckedIdent( return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name())); } + private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { + CelList list = celExpr.list(); + + ImmutableList elements = list.elements(); + Interpretable[] values = new Interpretable[elements.size()]; + + for (int i = 0; i < elements.size(); i++) { + values[i] = plan(elements.get(i), ctx); + } + + return EvalCreateList.create(values); + } + @AutoValue abstract static class PlannerContext { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index ce159755f..a761e4925 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -18,6 +18,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; @@ -88,7 +89,7 @@ public void plan_constant(@TestParameter ConstantTestCase testCase) throws Excep } @Test - public void planIdent_enum() throws Exception { + public void plan_ident_enum() throws Exception { if (isParseOnly) { // TODO Skip for now, requires attribute qualification return; @@ -103,7 +104,7 @@ public void planIdent_enum() throws Exception { } @Test - public void planIdent_variable() throws Exception { + public void plan_ident_variable() throws Exception { CelAbstractSyntaxTree ast = compile("int_var"); Program program = PLANNER.plan(ast); @@ -112,6 +113,17 @@ public void planIdent_variable() throws Exception { assertThat(result).isEqualTo(1); } + @Test + @SuppressWarnings("unchecked") // test only + public void plan_createList() throws Exception { + CelAbstractSyntaxTree ast = compile("[1, 'foo', true, [2, false]]"); + Program program = PLANNER.plan(ast); + + ImmutableList result = (ImmutableList) program.eval(); + + assertThat(result).containsExactly(1L, "foo", true, ImmutableList.of(2L, false)).inOrder(); + } + @Test public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { if (isParseOnly) { From 87d3a4fa15b1bb2144a3efbddcd292948a2e1748 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 19 Nov 2025 11:48:41 -0800 Subject: [PATCH 021/100] Remove primitives, list and map variants of CelValue PiperOrigin-RevId: 834383145 --- .../java/dev/cel/common/values/BUILD.bazel | 31 +-- .../values/BaseProtoCelValueConverter.java | 101 ++++--- .../java/dev/cel/common/values/BoolValue.java | 43 --- .../dev/cel/common/values/BytesValue.java | 43 --- .../cel/common/values/CelValueConverter.java | 97 +++---- .../cel/common/values/CelValueProvider.java | 8 +- .../values/CombinedCelValueProvider.java | 4 +- .../dev/cel/common/values/DoubleValue.java | 74 ----- .../dev/cel/common/values/DurationValue.java | 44 --- .../java/dev/cel/common/values/EnumValue.java | 49 ---- .../cel/common/values/ImmutableListValue.java | 134 ---------- .../cel/common/values/ImmutableMapValue.java | 109 -------- .../java/dev/cel/common/values/IntValue.java | 73 ----- .../java/dev/cel/common/values/ListValue.java | 211 --------------- .../java/dev/cel/common/values/MapValue.java | 253 ------------------ .../dev/cel/common/values/OptionalValue.java | 32 ++- .../common/values/ProtoCelValueConverter.java | 69 +++-- .../values/ProtoLiteCelValueConverter.java | 50 ++-- .../common/values/ProtoMessageLiteValue.java | 13 +- .../values/ProtoMessageLiteValueProvider.java | 4 +- .../cel/common/values/ProtoMessageValue.java | 10 +- .../values/ProtoMessageValueProvider.java | 4 +- .../cel/common/values/SelectableValue.java | 6 +- .../dev/cel/common/values/StringValue.java | 43 --- .../dev/cel/common/values/StructValue.java | 3 +- .../dev/cel/common/values/TimestampValue.java | 44 --- .../java/dev/cel/common/values/TypeValue.java | 41 --- .../java/dev/cel/common/values/UintValue.java | 52 ---- .../java/dev/cel/common/values/BUILD.bazel | 3 - .../dev/cel/common/values/BoolValueTest.java | 55 ---- .../dev/cel/common/values/BytesValueTest.java | 55 ---- .../common/values/CelValueConverterTest.java | 59 +--- .../cel/common/values/DoubleValueTest.java | 84 ------ .../cel/common/values/DurationValueTest.java | 56 ---- .../dev/cel/common/values/EnumValueTest.java | 53 ---- .../common/values/ImmutableListValueTest.java | 73 ----- .../common/values/ImmutableMapValueTest.java | 109 -------- .../dev/cel/common/values/IntValueTest.java | 81 ------ .../cel/common/values/OptionalValueTest.java | 67 +++-- .../values/ProtoCelValueConverterTest.java | 39 +-- .../ProtoLiteCelValueConverterTest.java | 50 ++-- .../ProtoMessageLiteValueProviderTest.java | 2 +- .../values/ProtoMessageLiteValueTest.java | 168 ++++++------ .../values/ProtoMessageValueProviderTest.java | 67 ++--- .../common/values/ProtoMessageValueTest.java | 132 ++++----- .../cel/common/values/StringValueTest.java | 63 ----- .../cel/common/values/StructValueTest.java | 18 +- .../cel/common/values/TimestampValueTest.java | 56 ---- .../dev/cel/common/values/TypeValueTest.java | 48 ---- .../dev/cel/common/values/UintValueTest.java | 56 ---- .../runtime/CelValueRuntimeTypeProvider.java | 56 ++-- 51 files changed, 441 insertions(+), 2654 deletions(-) delete mode 100644 common/src/main/java/dev/cel/common/values/BoolValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/BytesValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/DoubleValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/DurationValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/EnumValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/ImmutableListValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/ImmutableMapValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/IntValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/ListValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/MapValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/StringValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/TimestampValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/TypeValue.java delete mode 100644 common/src/main/java/dev/cel/common/values/UintValue.java delete mode 100644 common/src/test/java/dev/cel/common/values/BoolValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/BytesValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/DoubleValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/DurationValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/EnumValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/ImmutableListValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/IntValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/StringValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/TimestampValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/TypeValueTest.java delete mode 100644 common/src/test/java/dev/cel/common/values/UintValueTest.java diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index 9ffa5bad3..e9b4be4f1 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -12,27 +12,13 @@ package( # keep sorted CEL_VALUES_SOURCES = [ - "BoolValue.java", - "BytesValue.java", "CelValueConverter.java", - "DoubleValue.java", - "DurationValue.java", - "EnumValue.java", "ErrorValue.java", - "ImmutableListValue.java", - "ImmutableMapValue.java", - "IntValue.java", - "ListValue.java", - "MapValue.java", "NullValue.java", "OpaqueValue.java", "OptionalValue.java", "SelectableValue.java", - "StringValue.java", "StructValue.java", - "TimestampValue.java", - "TypeValue.java", - "UintValue.java", ] # keep sorted @@ -71,7 +57,6 @@ java_library( tags = [ ], deps = [ - ":cel_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -83,7 +68,6 @@ cel_android_library( tags = [ ], deps = [ - ":cel_value_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -95,7 +79,6 @@ java_library( tags = [ ], deps = [ - "//common/values:cel_value", "//common/values:cel_value_provider", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -108,7 +91,6 @@ cel_android_library( tags = [ ], deps = [ - ":cel_value_android", "//common/values:cel_value_provider_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -124,8 +106,6 @@ java_library( ":cel_byte_string", ":cel_value", "//:auto_value", - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", "//common/types", "//common/types:type_providers", @@ -144,8 +124,6 @@ cel_android_library( ":cel_byte_string", ":cel_value_android", "//:auto_value", - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", "//common/types:type_providers_android", "//common/types:types_android", @@ -173,11 +151,11 @@ java_library( ], deps = [ ":cel_byte_string", - ":cel_value", ":values", "//common/annotations", "//common/internal:proto_time_utils", "//common/internal:well_known_proto", + "//common/values", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -191,7 +169,6 @@ cel_android_library( ], deps = [ ":cel_byte_string", - ":cel_value_android", ":values_android", "//common/annotations", "//common/internal:proto_time_utils_android", @@ -209,7 +186,6 @@ java_library( ], deps = [ ":base_proto_cel_value_converter", - ":cel_value", ":values", "//:auto_value", "//common/annotations", @@ -232,7 +208,6 @@ java_library( ], deps = [ ":base_proto_message_value_provider", - ":cel_value", ":proto_message_value", "//common:options", "//common/annotations", @@ -254,7 +229,6 @@ java_library( ], deps = [ ":base_proto_cel_value_converter", - ":cel_value", ":values", "//:auto_value", "//common/annotations", @@ -282,7 +256,6 @@ cel_android_library( ], deps = [ ":base_proto_cel_value_converter_android", - ":cel_value_android", ":values_android", "//:auto_value", "//common/annotations", @@ -307,7 +280,6 @@ java_library( ], deps = [ ":base_proto_message_value_provider", - ":cel_value", ":proto_message_lite_value", "//common/annotations", "//common/internal:cel_lite_descriptor_pool", @@ -327,7 +299,6 @@ cel_android_library( ], deps = [ ":base_proto_message_value_provider_android", - ":cel_value_android", ":proto_message_lite_value_android", "//common/annotations", "//common/internal:cel_lite_descriptor_pool_android", diff --git a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java index 450b273c2..b05a21e24 100644 --- a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -18,15 +18,19 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.BoolValue; import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; import com.google.protobuf.DoubleValue; import com.google.protobuf.Duration; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; import com.google.protobuf.Int64Value; -import com.google.protobuf.MessageLite; +import com.google.protobuf.ListValue; import com.google.protobuf.MessageLiteOrBuilder; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; @@ -37,7 +41,6 @@ import dev.cel.common.annotations.Internal; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.internal.WellKnownProto; -import java.util.Optional; /** * {@code BaseProtoCelValueConverter} contains the common logic for converting between native Java @@ -51,77 +54,68 @@ @Internal public abstract class BaseProtoCelValueConverter extends CelValueConverter { - public abstract CelValue fromProtoMessageToCelValue(MessageLite msg); - /** {@inheritDoc} Protobuf semantics take precedence for conversion. */ @Override - public CelValue fromJavaObjectToCelValue(Object value) { + public Object toRuntimeValue(Object value) { Preconditions.checkNotNull(value); - Optional wellKnownProto = WellKnownProto.getByClass(value.getClass()); - if (wellKnownProto.isPresent()) { - return fromWellKnownProtoToCelValue((MessageLiteOrBuilder) value, wellKnownProto.get()); - } - if (value instanceof ByteString) { - return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray())); + return CelByteString.of(((ByteString) value).toByteArray()); } else if (value instanceof com.google.protobuf.NullValue) { return NullValue.NULL_VALUE; } - return super.fromJavaObjectToCelValue(value); + return super.toRuntimeValue(value); } - protected CelValue fromWellKnownProtoToCelValue( - MessageLiteOrBuilder message, WellKnownProto wellKnownProto) { + protected Object fromWellKnownProto(MessageLiteOrBuilder message, WellKnownProto wellKnownProto) { switch (wellKnownProto) { case JSON_VALUE: - return adaptJsonValueToCelValue((Value) message); + return adaptJsonValue((Value) message); case JSON_STRUCT_VALUE: - return adaptJsonStructToCelValue((Struct) message); + return adaptJsonStruct((Struct) message); case JSON_LIST_VALUE: - return adaptJsonListToCelValue((com.google.protobuf.ListValue) message); + return adaptJsonList((ListValue) message); case DURATION: - return DurationValue.create(ProtoTimeUtils.toJavaDuration((Duration) message)); + return ProtoTimeUtils.toJavaDuration((Duration) message); case TIMESTAMP: - return TimestampValue.create(ProtoTimeUtils.toJavaInstant((Timestamp) message)); + return ProtoTimeUtils.toJavaInstant((Timestamp) message); case BOOL_VALUE: - return fromJavaPrimitiveToCelValue(((BoolValue) message).getValue()); + return normalizePrimitive(((BoolValue) message).getValue()); case BYTES_VALUE: - return fromJavaPrimitiveToCelValue( - ((com.google.protobuf.BytesValue) message).getValue().toByteArray()); + return normalizePrimitive(((BytesValue) message).getValue().toByteArray()); case DOUBLE_VALUE: - return fromJavaPrimitiveToCelValue(((DoubleValue) message).getValue()); + return normalizePrimitive(((DoubleValue) message).getValue()); case FLOAT_VALUE: - return fromJavaPrimitiveToCelValue(((FloatValue) message).getValue()); + return normalizePrimitive(((FloatValue) message).getValue()); case INT32_VALUE: - return fromJavaPrimitiveToCelValue(((Int32Value) message).getValue()); + return normalizePrimitive(((Int32Value) message).getValue()); case INT64_VALUE: - return fromJavaPrimitiveToCelValue(((Int64Value) message).getValue()); + return normalizePrimitive(((Int64Value) message).getValue()); case STRING_VALUE: - return fromJavaPrimitiveToCelValue(((StringValue) message).getValue()); + return normalizePrimitive(((StringValue) message).getValue()); case UINT32_VALUE: - return UintValue.create(((UInt32Value) message).getValue()); + return UnsignedLong.valueOf(((UInt32Value) message).getValue()); case UINT64_VALUE: - return UintValue.create(((UInt64Value) message).getValue()); + return UnsignedLong.fromLongBits(((UInt64Value) message).getValue()); default: throw new UnsupportedOperationException( - "Unsupported message to CelValue conversion - " + message); + "Unsupported well known proto conversion - " + wellKnownProto); } } - private CelValue adaptJsonValueToCelValue(Value value) { + private Object adaptJsonValue(Value value) { switch (value.getKindCase()) { case BOOL_VALUE: - return fromJavaPrimitiveToCelValue(value.getBoolValue()); + return normalizePrimitive(value.getBoolValue()); case NUMBER_VALUE: - return fromJavaPrimitiveToCelValue(value.getNumberValue()); + return normalizePrimitive(value.getNumberValue()); case STRING_VALUE: - return fromJavaPrimitiveToCelValue(value.getStringValue()); + return normalizePrimitive(value.getStringValue()); case LIST_VALUE: - return adaptJsonListToCelValue(value.getListValue()); + return adaptJsonList(value.getListValue()); case STRUCT_VALUE: - return adaptJsonStructToCelValue(value.getStructValue()); + return adaptJsonStruct(value.getStructValue()); case NULL_VALUE: case KIND_NOT_SET: // Fall-through is intended return NullValue.NULL_VALUE; @@ -130,28 +124,23 @@ private CelValue adaptJsonValueToCelValue(Value value) { "Unsupported Json to CelValue conversion: " + value.getKindCase()); } - private ListValue adaptJsonListToCelValue(com.google.protobuf.ListValue listValue) { - return ImmutableListValue.create( - listValue.getValuesList().stream() - .map(this::adaptJsonValueToCelValue) - .collect(toImmutableList())); + private ImmutableList adaptJsonList(ListValue listValue) { + return listValue.getValuesList().stream().map(this::adaptJsonValue).collect(toImmutableList()); } - private MapValue adaptJsonStructToCelValue( - Struct struct) { - return ImmutableMapValue.create( - struct.getFieldsMap().entrySet().stream() - .collect( - toImmutableMap( - e -> { - CelValue key = fromJavaObjectToCelValue(e.getKey()); - if (!(key instanceof dev.cel.common.values.StringValue)) { - throw new IllegalStateException( - "Expected a string type for the key, but instead got: " + key); - } - return (dev.cel.common.values.StringValue) key; - }, - e -> adaptJsonValueToCelValue(e.getValue())))); + private ImmutableMap adaptJsonStruct(Struct struct) { + return struct.getFieldsMap().entrySet().stream() + .collect( + toImmutableMap( + e -> { + Object key = toRuntimeValue(e.getKey()); + if (!(key instanceof String)) { + throw new IllegalStateException( + "Expected a string type for the key, but instead got: " + key); + } + return (String) key; + }, + e -> adaptJsonValue(e.getValue()))); } protected BaseProtoCelValueConverter() {} diff --git a/common/src/main/java/dev/cel/common/values/BoolValue.java b/common/src/main/java/dev/cel/common/values/BoolValue.java deleted file mode 100644 index 1327e5691..000000000 --- a/common/src/main/java/dev/cel/common/values/BoolValue.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; - -/** BoolValue is a simple CelValue wrapper around Java booleans. */ -@AutoValue -@Immutable -public abstract class BoolValue extends CelValue { - - @Override - public abstract Boolean value(); - - @Override - public boolean isZeroValue() { - return !value(); - } - - @Override - public CelType celType() { - return SimpleType.BOOL; - } - - public static BoolValue create(Boolean value) { - return new AutoValue_BoolValue(value); - } -} diff --git a/common/src/main/java/dev/cel/common/values/BytesValue.java b/common/src/main/java/dev/cel/common/values/BytesValue.java deleted file mode 100644 index d4c309364..000000000 --- a/common/src/main/java/dev/cel/common/values/BytesValue.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; - -/** BytesValue is a simple CelValue wrapper around CelByteString (immutable byte string). */ -@AutoValue -@Immutable -public abstract class BytesValue extends CelValue { - - @Override - public abstract CelByteString value(); - - @Override - public boolean isZeroValue() { - return value().isEmpty(); - } - - @Override - public CelType celType() { - return SimpleType.BYTES; - } - - public static BytesValue create(CelByteString value) { - return new AutoValue_BytesValue(value); - } -} diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index 5286ae288..3ab68c80e 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -17,9 +17,9 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; import dev.cel.common.annotations.Internal; +import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; @@ -36,116 +36,87 @@ abstract class CelValueConverter { /** Adapts a {@link CelValue} to a plain old Java Object. */ - public Object fromCelValueToJavaObject(CelValue celValue) { + public Object unwrap(CelValue celValue) { Preconditions.checkNotNull(celValue); - if (celValue instanceof MapValue) { - MapValue mapValue = (MapValue) celValue; - ImmutableMap.Builder mapBuilder = - ImmutableMap.builderWithExpectedSize(mapValue.size()); - for (Entry entry : mapValue.value().entrySet()) { - Object key = fromCelValueToJavaObject(entry.getKey()); - Object value = fromCelValueToJavaObject(entry.getValue()); - mapBuilder.put(key, value); - } - - return mapBuilder.buildOrThrow(); - } else if (celValue instanceof ListValue) { - ListValue listValue = (ListValue) celValue; - ImmutableList.Builder listBuilder = - ImmutableList.builderWithExpectedSize(listValue.size()); - for (CelValue element : listValue.value()) { - listBuilder.add(fromCelValueToJavaObject(element)); - } - return listBuilder.build(); - } else if (celValue instanceof OptionalValue) { - OptionalValue optionalValue = (OptionalValue) celValue; + if (celValue instanceof OptionalValue) { + OptionalValue optionalValue = (OptionalValue) celValue; if (optionalValue.isZeroValue()) { return Optional.empty(); } - return Optional.of(fromCelValueToJavaObject(optionalValue.value())); + return Optional.of(optionalValue.value()); } return celValue.value(); } - /** Adapts a plain old Java Object to a {@link CelValue}. */ - public CelValue fromJavaObjectToCelValue(Object value) { + /** + * Canonicalizes an inbound {@code value} into a suitable Java object representation for + * evaluation. + */ + public Object toRuntimeValue(Object value) { Preconditions.checkNotNull(value); if (value instanceof CelValue) { - return (CelValue) value; + return value; } - if (value instanceof Iterable) { - return toListValue((Iterable) value); + if (value instanceof Collection) { + return toListValue((Collection) value); } else if (value instanceof Map) { return toMapValue((Map) value); } else if (value instanceof Optional) { - Optional optionalValue = (Optional) value; + Optional optionalValue = (Optional) value; return optionalValue - .map(o -> OptionalValue.create(fromJavaObjectToCelValue(o))) + .map(this::toRuntimeValue) + .map(OptionalValue::create) .orElse(OptionalValue.EMPTY); } else if (value instanceof Exception) { return ErrorValue.create((Exception) value); } - return fromJavaPrimitiveToCelValue(value); + return normalizePrimitive(value); } - /** Adapts a plain old Java Object that are considered primitives to a {@link CelValue}. */ - protected CelValue fromJavaPrimitiveToCelValue(Object value) { + protected Object normalizePrimitive(Object value) { Preconditions.checkNotNull(value); - if (value instanceof Boolean) { - return BoolValue.create((Boolean) value); - } else if (value instanceof Long) { - return IntValue.create((Long) value); - } else if (value instanceof Integer) { - return IntValue.create((Integer) value); - } else if (value instanceof String) { - return StringValue.create((String) value); + if (value instanceof Integer) { + return ((Integer) value).longValue(); } else if (value instanceof byte[]) { - return BytesValue.create(CelByteString.of((byte[]) value)); - } else if (value instanceof Double) { - return DoubleValue.create((Double) value); + return CelByteString.of((byte[]) value); } else if (value instanceof Float) { - return DoubleValue.create(Double.valueOf((Float) value)); - } else if (value instanceof UnsignedLong) { - return UintValue.create((UnsignedLong) value); - } else if (value instanceof CelByteString) { - return BytesValue.create((CelByteString) value); + return ((Float) value).doubleValue(); } - // Fall back to an Opaque value, as a custom class was supplied in the runtime. The legacy - // interpreter allows this but this should not be allowed when a new runtime is introduced. - // TODO: Migrate consumers to directly supply an appropriate CelValue. - return OpaqueValue.create(value.toString(), value); + return value; } - private ListValue toListValue(Iterable iterable) { + private ImmutableList toListValue(Collection iterable) { Preconditions.checkNotNull(iterable); - ImmutableList.Builder listBuilder = ImmutableList.builder(); + ImmutableList.Builder listBuilder = + ImmutableList.builderWithExpectedSize(iterable.size()); for (Object entry : iterable) { - listBuilder.add(fromJavaObjectToCelValue(entry)); + listBuilder.add(toRuntimeValue(entry)); } - return ImmutableListValue.create(listBuilder.build()); + return listBuilder.build(); } - private MapValue toMapValue(Map map) { + private ImmutableMap toMapValue(Map map) { Preconditions.checkNotNull(map); - ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); + ImmutableMap.Builder mapBuilder = + ImmutableMap.builderWithExpectedSize(map.size()); for (Entry entry : map.entrySet()) { - CelValue mapKey = fromJavaObjectToCelValue(entry.getKey()); - CelValue mapValue = fromJavaObjectToCelValue(entry.getValue()); + Object mapKey = toRuntimeValue(entry.getKey()); + Object mapValue = toRuntimeValue(entry.getValue()); mapBuilder.put(mapKey, mapValue); } - return ImmutableMapValue.create(mapBuilder.buildOrThrow()); + return mapBuilder.buildOrThrow(); } protected CelValueConverter() {} diff --git a/common/src/main/java/dev/cel/common/values/CelValueProvider.java b/common/src/main/java/dev/cel/common/values/CelValueProvider.java index e5080e180..717834660 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CelValueProvider.java @@ -23,10 +23,8 @@ public interface CelValueProvider { /** - * Constructs a new struct value. - * - *

    Note that the return type is defined as CelValue rather than StructValue to account for - * special cases such as wrappers where its primitive is returned. + * Constructs a new struct value, or a primitive value in case the fully qualified struct name is + * a wrapper. */ - Optional newValue(String structType, Map fields); + Optional newValue(String structType, Map fields); } diff --git a/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java b/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java index a674beb8d..8fe62cb7b 100644 --- a/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CombinedCelValueProvider.java @@ -38,9 +38,9 @@ public static CombinedCelValueProvider combine(CelValueProvider... providers) { } @Override - public Optional newValue(String structType, Map fields) { + public Optional newValue(String structType, Map fields) { for (CelValueProvider provider : celValueProviders) { - Optional newValue = provider.newValue(structType, fields); + Optional newValue = provider.newValue(structType, fields); if (newValue.isPresent()) { return newValue; } diff --git a/common/src/main/java/dev/cel/common/values/DoubleValue.java b/common/src/main/java/dev/cel/common/values/DoubleValue.java deleted file mode 100644 index 97e0c86b6..000000000 --- a/common/src/main/java/dev/cel/common/values/DoubleValue.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; - -/** DoubleValue is a simple CelValue wrapper around Java doubles. */ -@Immutable -public final class DoubleValue extends CelValue { - private final double value; - - @Override - public Double value() { - return value; - } - - public double doubleValue() { - return value; - } - - @Override - public boolean isZeroValue() { - return value() == 0; - } - - @Override - public CelType celType() { - return SimpleType.DOUBLE; - } - - public static DoubleValue create(double value) { - return new DoubleValue(value); - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= Double.hashCode(value); - return h; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (!(o instanceof DoubleValue)) { - return false; - } - - return Double.doubleToLongBits(((DoubleValue) o).doubleValue()) - == Double.doubleToLongBits(this.doubleValue()); - } - - private DoubleValue(double value) { - this.value = value; - } -} diff --git a/common/src/main/java/dev/cel/common/values/DurationValue.java b/common/src/main/java/dev/cel/common/values/DurationValue.java deleted file mode 100644 index 702109de2..000000000 --- a/common/src/main/java/dev/cel/common/values/DurationValue.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; -import java.time.Duration; - -/** DurationValue is a simple CelValue wrapper around {@link java.time.Duration} */ -@AutoValue -@Immutable -public abstract class DurationValue extends CelValue { - - @Override - public abstract Duration value(); - - @Override - public boolean isZeroValue() { - return value().isZero(); - } - - @Override - public CelType celType() { - return SimpleType.DURATION; - } - - public static DurationValue create(Duration value) { - return new AutoValue_DurationValue(value); - } -} diff --git a/common/src/main/java/dev/cel/common/values/EnumValue.java b/common/src/main/java/dev/cel/common/values/EnumValue.java deleted file mode 100644 index 1a8ea3e7c..000000000 --- a/common/src/main/java/dev/cel/common/values/EnumValue.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; - -/** - * EnumValue is a simple CelValue wrapper around Java enums. - * - *

    Note: CEL-Java currently does not support strongly typed enum. This value class will not be - * used until the said support is added in. - */ -@AutoValue -@Immutable(containerOf = "E") -public abstract class EnumValue> extends CelValue { - - @Override - public abstract Enum value(); - - @Override - public boolean isZeroValue() { - return false; - } - - @Override - public CelType celType() { - // (b/178627883) Strongly typed enum is not supported yet - return SimpleType.INT; - } - - public static > EnumValue create(Enum value) { - return new AutoValue_EnumValue<>(value); - } -} diff --git a/common/src/main/java/dev/cel/common/values/ImmutableListValue.java b/common/src/main/java/dev/cel/common/values/ImmutableListValue.java deleted file mode 100644 index 1a0d6a0ce..000000000 --- a/common/src/main/java/dev/cel/common/values/ImmutableListValue.java +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.common.collect.ImmutableList; -import com.google.errorprone.annotations.Immutable; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -/** - * ImmutableListValue is a representation of an immutable list containing zero or more {@link - * CelValue}. - */ -@Immutable -@SuppressWarnings( - "PreferredInterfaceType") // We intentionally store List type to avoid copying on instantiation -public final class ImmutableListValue extends ListValue { - - @SuppressWarnings("Immutable") // ListValue APIs prohibit mutation. - private final List originalList; - - @SuppressWarnings("Immutable") // The value is lazily populated only once via synchronization. - private volatile ImmutableList cachedImmutableList = null; - - public static ImmutableListValue create(List value) { - return new ImmutableListValue<>(value); - } - - private ImmutableListValue(List originalList) { - this.originalList = ImmutableList.copyOf(originalList); - } - - @Override - public ImmutableList value() { - if (cachedImmutableList == null) { - synchronized (this) { - if (cachedImmutableList == null) { - cachedImmutableList = ImmutableList.copyOf(originalList); - } - } - } - - return cachedImmutableList; - } - - @Override - public int size() { - return originalList.size(); - } - - @Override - public boolean isEmpty() { - return originalList.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return originalList.contains(o); - } - - @Override - public Iterator iterator() { - return originalList.iterator(); - } - - @Override - public Object[] toArray() { - return originalList.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return originalList.toArray(a); - } - - @Override - public boolean containsAll(Collection c) { - return originalList.containsAll(c); - } - - @Override - public E get(int index) { - return originalList.get(index); - } - - @Override - public int indexOf(Object o) { - return originalList.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return originalList.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return originalList.listIterator(); - } - - @Override - public ListIterator listIterator(int index) { - return originalList.listIterator(index); - } - - @Override - public List subList(int fromIndex, int toIndex) { - return originalList.subList(fromIndex, toIndex); - } - - @Override - public int hashCode() { - return originalList.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return originalList.equals(obj); - } -} diff --git a/common/src/main/java/dev/cel/common/values/ImmutableMapValue.java b/common/src/main/java/dev/cel/common/values/ImmutableMapValue.java deleted file mode 100644 index eb429a19d..000000000 --- a/common/src/main/java/dev/cel/common/values/ImmutableMapValue.java +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableMap; -import com.google.errorprone.annotations.Immutable; -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -/** - * MapValue is an abstract representation of an immutable map containing {@link CelValue} as keys - * and values. - */ -@Immutable(containerOf = {"K", "V"}) -@SuppressWarnings( - "PreferredInterfaceType") // We intentionally store List type to avoid copying on instantiation -public final class ImmutableMapValue - extends MapValue { - - @SuppressWarnings("Immutable") // MapValue APIs prohibit mutation. - private final Map originalMap; - - @SuppressWarnings("Immutable") // The value is lazily populated only once via synchronization. - private volatile ImmutableMap cachedImmutableMap = null; - - public static ImmutableMapValue create( - Map value) { - return new ImmutableMapValue<>(value); - } - - private ImmutableMapValue(Map originalMap) { - Preconditions.checkNotNull(originalMap); - this.originalMap = originalMap; - } - - @Override - public ImmutableMap value() { - if (cachedImmutableMap == null) { - synchronized (this) { - if (cachedImmutableMap == null) { - cachedImmutableMap = ImmutableMap.copyOf(originalMap); - } - } - } - - return cachedImmutableMap; - } - - @Override - public int size() { - return originalMap.size(); - } - - @Override - public boolean isEmpty() { - return originalMap.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return originalMap.containsKey(key); - } - - @Override - public boolean containsValue(Object val) { - return originalMap.containsValue(val); - } - - @Override - public int hashCode() { - return originalMap.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return originalMap.equals(obj); - } - - // Note that the following three methods are produced from the immutable map to avoid key/value - // mutation. - @Override - public Set keySet() { - return value().keySet(); - } - - @Override - public Collection values() { - return value().values(); - } - - @Override - public Set> entrySet() { - return value().entrySet(); - } -} diff --git a/common/src/main/java/dev/cel/common/values/IntValue.java b/common/src/main/java/dev/cel/common/values/IntValue.java deleted file mode 100644 index b756804f6..000000000 --- a/common/src/main/java/dev/cel/common/values/IntValue.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; - -/** IntValue is a simple CelValue wrapper around Java longs. */ -@Immutable -public final class IntValue extends CelValue { - private final long value; - - @Override - public Long value() { - return value; - } - - public long longValue() { - return value; - } - - @Override - public boolean isZeroValue() { - return value() == 0; - } - - @Override - public CelType celType() { - return SimpleType.INT; - } - - public static IntValue create(long value) { - return new IntValue(value); - } - - @Override - public int hashCode() { - int h = 1; - h *= 1000003; - h ^= Long.hashCode(value); - return h; - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (!(o instanceof IntValue)) { - return false; - } - - return ((IntValue) o).value == this.value; - } - - private IntValue(long value) { - this.value = value; - } -} diff --git a/common/src/main/java/dev/cel/common/values/ListValue.java b/common/src/main/java/dev/cel/common/values/ListValue.java deleted file mode 100644 index 814884d34..000000000 --- a/common/src/main/java/dev/cel/common/values/ListValue.java +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.DoNotCall; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.ListType; -import dev.cel.common.types.SimpleType; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.function.UnaryOperator; -import org.jspecify.annotations.Nullable; - -/** - * ListValue is an abstract representation of a generic list containing zero or more {@link - * CelValue}. - * - *

    All methods that can mutate the list are disallowed. - */ -@Immutable -public abstract class ListValue extends CelValue implements List { - private static final ListType LIST_TYPE = ListType.create(SimpleType.DYN); - - @Override - @SuppressWarnings("Immutable") // ListValue APIs prohibit mutation. - public abstract List value(); - - @Override - public boolean isZeroValue() { - return isEmpty(); - } - - @Override - public CelType celType() { - return LIST_TYPE; - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final void clear() { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean retainAll(Collection c) { - return false; - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @CanIgnoreReturnValue - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final E set(int index, E element) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final void replaceAll(UnaryOperator operator) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final void sort(@Nullable Comparator c) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final void add(int index, E element) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean add(E e) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @CanIgnoreReturnValue - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean addAll(int index, Collection newElements) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @CanIgnoreReturnValue - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final E remove(int index) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean remove(Object o) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the list unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean removeAll(Collection c) { - throw new UnsupportedOperationException(); - } -} diff --git a/common/src/main/java/dev/cel/common/values/MapValue.java b/common/src/main/java/dev/cel/common/values/MapValue.java deleted file mode 100644 index 0f79b0464..000000000 --- a/common/src/main/java/dev/cel/common/values/MapValue.java +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.DoNotCall; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; -import dev.cel.common.types.CelType; -import dev.cel.common.types.MapType; -import dev.cel.common.types.SimpleType; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Function; -import org.jspecify.annotations.Nullable; - -/** - * MapValue is an abstract representation of a generic map containing {@link CelValue} as keys and - * values. - * - *

    All methods that can mutate the map are disallowed. - */ -@Immutable(containerOf = {"K", "V"}) -public abstract class MapValue extends CelValue - implements Map, SelectableValue { - - private static final MapType MAP_TYPE = MapType.create(SimpleType.DYN, SimpleType.DYN); - - @Override - public abstract Map value(); - - @Override - public boolean isZeroValue() { - return isEmpty(); - } - - @Override - @SuppressWarnings("unchecked") - public V get(Object key) { - return select((K) key); - } - - @Override - @SuppressWarnings("unchecked") - public V select(K field) { - return (V) - find(field) - .orElseThrow( - () -> - new CelRuntimeException( - new IllegalArgumentException( - String.format("key '%s' is not present in map.", field.value())), - CelErrorCode.ATTRIBUTE_NOT_FOUND)); - } - - @Override - public Optional find(K field) { - return value().containsKey(field) ? Optional.of(value().get(field)) : Optional.empty(); - } - - @Override - public CelType celType() { - return MAP_TYPE; - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @CanIgnoreReturnValue - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V put(K key, V value) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @CanIgnoreReturnValue - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V putIfAbsent(K key, V value) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean replace(K key, V oldValue, V newValue) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V replace(K key, V value) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V computeIfAbsent(K key, Function mappingFunction) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V computeIfPresent( - K key, BiFunction remappingFunction) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V compute( - K key, BiFunction remappingFunction) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V merge( - K key, V value, BiFunction function) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final void putAll(Map map) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final void replaceAll(BiFunction function) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final V remove(Object o) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final boolean remove(Object key, Object value) { - throw new UnsupportedOperationException(); - } - - /** - * Guaranteed to throw an exception and leave the map unmodified. - * - * @throws UnsupportedOperationException always - * @deprecated Unsupported operation. - */ - @Deprecated - @Override - @DoNotCall("Always throws UnsupportedOperationException") - public final void clear() { - throw new UnsupportedOperationException(); - } -} diff --git a/common/src/main/java/dev/cel/common/values/OptionalValue.java b/common/src/main/java/dev/cel/common/values/OptionalValue.java index 3866ef72b..9a2204fca 100644 --- a/common/src/main/java/dev/cel/common/values/OptionalValue.java +++ b/common/src/main/java/dev/cel/common/values/OptionalValue.java @@ -19,6 +19,7 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.types.OptionalType; import dev.cel.common.types.SimpleType; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import org.jspecify.annotations.Nullable; @@ -30,12 +31,11 @@ */ @AutoValue @Immutable(containerOf = "E") -public abstract class OptionalValue extends CelValue - implements SelectableValue { +public abstract class OptionalValue extends CelValue implements SelectableValue { private static final OptionalType OPTIONAL_TYPE = OptionalType.create(SimpleType.DYN); /** Sentinel value representing an empty optional ('optional.none()' in CEL) */ - public static final OptionalValue EMPTY = empty(); + public static final OptionalValue EMPTY = empty(); // There is only one scenario where the value is null and it's `optional.none`. abstract @Nullable E innerValue(); @@ -68,28 +68,40 @@ public OptionalType celType() { * */ @Override - public CelValue select(CelValue field) { + public OptionalValue select(T field) { return find(field).orElse(EMPTY); } @Override @SuppressWarnings("unchecked") - public Optional find(CelValue field) { + public Optional> find(T field) { if (isZeroValue()) { return Optional.empty(); } - SelectableValue selectableValue = (SelectableValue) value(); - Optional selectedField = selectableValue.find(field); - return selectedField.map(OptionalValue::create); + E value = value(); + if (value instanceof Map) { + Map map = (Map) value; + Object selectedVal = map.get(field); + if (selectedVal == null) { + return Optional.empty(); + } + + return Optional.of(OptionalValue.create((E) selectedVal)); + } else if (value instanceof SelectableValue) { + SelectableValue selectableValue = (SelectableValue) value; + return selectableValue.find(field).map(OptionalValue::create); + } + + return Optional.empty(); } - public static OptionalValue create(E value) { + public static OptionalValue create(E value) { Preconditions.checkNotNull(value); return new AutoValue_OptionalValue<>(value); } - private static OptionalValue empty() { + private static OptionalValue empty() { return new AutoValue_OptionalValue<>(null); } } diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 7a679270d..08f30b9d1 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -15,6 +15,7 @@ package dev.cel.common.values; import com.google.common.base.Preconditions; +import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.Any; import com.google.protobuf.Descriptors.EnumValueDescriptor; @@ -23,7 +24,6 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MapEntry; import com.google.protobuf.Message; -import com.google.protobuf.MessageLite; import com.google.protobuf.MessageLiteOrBuilder; import com.google.protobuf.MessageOrBuilder; import dev.cel.common.annotations.Internal; @@ -57,31 +57,7 @@ public static ProtoCelValueConverter newInstance( } @Override - public CelValue fromProtoMessageToCelValue(MessageLite msg) { - return fromDescriptorMessageToCelValue((MessageOrBuilder) msg); - } - - /** Adapts a Protobuf message into a {@link CelValue}. */ - public CelValue fromDescriptorMessageToCelValue(MessageOrBuilder message) { - Preconditions.checkNotNull(message); - - // Attempt to convert the proto from a dynamic message into a concrete message if possible. - if (message instanceof DynamicMessage) { - message = dynamicProto.maybeAdaptDynamicMessage((DynamicMessage) message); - } - - WellKnownProto wellKnownProto = - WellKnownProto.getByTypeName(message.getDescriptorForType().getFullName()).orElse(null); - if (wellKnownProto == null) { - return ProtoMessageValue.create((Message) message, celDescriptorPool, this); - } - - return fromWellKnownProtoToCelValue(message, wellKnownProto); - } - - @Override - protected CelValue fromWellKnownProtoToCelValue( - MessageLiteOrBuilder msg, WellKnownProto wellKnownProto) { + protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wellKnownProto) { MessageOrBuilder message = (MessageOrBuilder) msg; switch (wellKnownProto) { case ANY_VALUE: @@ -92,26 +68,41 @@ protected CelValue fromWellKnownProtoToCelValue( throw new IllegalStateException( "Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e); } - return fromProtoMessageToCelValue(unpackedMessage); + return toRuntimeValue(unpackedMessage); default: - return super.fromWellKnownProtoToCelValue(message, wellKnownProto); + return super.fromWellKnownProto(message, wellKnownProto); } } @Override - public CelValue fromJavaObjectToCelValue(Object value) { + public Object toRuntimeValue(Object value) { if (value instanceof EnumValueDescriptor) { // (b/178627883) Strongly typed enum is not supported yet - return IntValue.create(((EnumValueDescriptor) value).getNumber()); + return Long.valueOf(((EnumValueDescriptor) value).getNumber()); + } + + if (value instanceof MessageOrBuilder) { + MessageOrBuilder message = (MessageOrBuilder) value; + // Attempt to convert the proto from a dynamic message into a concrete message if possible. + if (message instanceof DynamicMessage) { + message = dynamicProto.maybeAdaptDynamicMessage((DynamicMessage) message); + } + + WellKnownProto wellKnownProto = + WellKnownProto.getByTypeName(message.getDescriptorForType().getFullName()).orElse(null); + if (wellKnownProto == null) { + return ProtoMessageValue.create((Message) message, celDescriptorPool, this); + } + + return fromWellKnownProto(message, wellKnownProto); } - return super.fromJavaObjectToCelValue(value); + return super.toRuntimeValue(value); } - /** Adapts the protobuf message field into {@link CelValue}. */ + /** Adapts the protobuf message field. */ @SuppressWarnings({"unchecked", "rawtypes"}) - public CelValue fromProtoMessageFieldToCelValue( - Message message, FieldDescriptor fieldDescriptor) { + public Object fromProtoMessageFieldToCelValue(Message message, FieldDescriptor fieldDescriptor) { Preconditions.checkNotNull(message); Preconditions.checkNotNull(fieldDescriptor); @@ -148,19 +139,19 @@ public CelValue fromProtoMessageFieldToCelValue( map.put(mapKey, mapValue); } - return fromJavaObjectToCelValue(map); + return toRuntimeValue(map); } - return fromDescriptorMessageToCelValue((MessageOrBuilder) result); + return toRuntimeValue(result); case UINT32: - return UintValue.create((int) result); + return UnsignedLong.valueOf((int) result); case UINT64: - return UintValue.create((long) result); + return UnsignedLong.fromLongBits((long) result); default: break; } - return fromJavaObjectToCelValue(result); + return toRuntimeValue(result); } private ProtoCelValueConverter(CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) { diff --git a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java index c23ad54de..3fbb0ad75 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoLiteCelValueConverter.java @@ -150,16 +150,38 @@ private MessageLite.Builder getDefaultMessageBuilder(String protoTypeName) { return descriptorPool.getDescriptorOrThrow(protoTypeName).newMessageBuilder(); } - CelValue getDefaultCelValue(String protoTypeName, String fieldName) { + Object getDefaultCelValue(String protoTypeName, String fieldName) { MessageLiteDescriptor messageDescriptor = descriptorPool.getDescriptorOrThrow(protoTypeName); FieldLiteDescriptor fieldDescriptor = messageDescriptor.getByFieldNameOrThrow(fieldName); Object defaultValue = getDefaultValue(fieldDescriptor); - if (defaultValue instanceof MessageLite) { - return fromProtoMessageToCelValue((MessageLite) defaultValue); + + return toRuntimeValue(defaultValue); + } + + @Override + @SuppressWarnings("LiteProtoToString") // No alternative identifier to use. Debug only info is OK. + public Object toRuntimeValue(Object value) { + checkNotNull(value); + if (value instanceof MessageLite) { + MessageLite msg = (MessageLite) value; + + MessageLiteDescriptor descriptor = + descriptorPool + .findDescriptor(msg) + .orElseThrow( + () -> new NoSuchElementException("Could not find a descriptor for: " + msg)); + WellKnownProto wellKnownProto = + WellKnownProto.getByTypeName(descriptor.getProtoTypeName()).orElse(null); + + if (wellKnownProto == null) { + return ProtoMessageLiteValue.create(msg, descriptor.getProtoTypeName(), this); + } + + return super.fromWellKnownProto(msg, wellKnownProto); } - return fromJavaObjectToCelValue(defaultValue); + return super.toRuntimeValue(value); } private Object getDefaultValue(FieldLiteDescriptor fieldDescriptor) { @@ -354,26 +376,6 @@ private static Object readUnknownField(int tagWireType, CodedInputStream inputSt } } - @Override - @SuppressWarnings("LiteProtoToString") // No alternative identifier to use. Debug only info is OK. - public CelValue fromProtoMessageToCelValue(MessageLite msg) { - checkNotNull(msg); - - MessageLiteDescriptor descriptor = - descriptorPool - .findDescriptor(msg) - .orElseThrow( - () -> new NoSuchElementException("Could not find a descriptor for: " + msg)); - WellKnownProto wellKnownProto = - WellKnownProto.getByTypeName(descriptor.getProtoTypeName()).orElse(null); - - if (wellKnownProto == null) { - return ProtoMessageLiteValue.create(msg, descriptor.getProtoTypeName(), this); - } - - return super.fromWellKnownProtoToCelValue(msg, wellKnownProto); - } - @AutoValue @SuppressWarnings("AutoValueImmutableFields") // Unknowns are inaccessible to users. abstract static class MessageFields { diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java index 9cb3b522d..52f0f1594 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValue.java @@ -35,7 +35,7 @@ */ @AutoValue @Immutable -public abstract class ProtoMessageLiteValue extends StructValue { +public abstract class ProtoMessageLiteValue extends StructValue { @Override public abstract MessageLite value(); @@ -60,17 +60,16 @@ public boolean isZeroValue() { } @Override - public CelValue select(StringValue field) { + public Object select(String field) { return find(field) - .orElseGet( - () -> protoLiteCelValueConverter().getDefaultCelValue(celType().name(), field.value())); + .orElseGet(() -> protoLiteCelValueConverter().getDefaultCelValue(celType().name(), field)); } @Override - public Optional find(StringValue field) { - Object fieldValue = fieldValues().get(field.value()); + public Optional find(String field) { + Object fieldValue = fieldValues().get(field); return Optional.ofNullable(fieldValue) - .map(value -> protoLiteCelValueConverter().fromJavaObjectToCelValue(fieldValue)); + .map(value -> protoLiteCelValueConverter().toRuntimeValue(fieldValue)); } public static ProtoMessageLiteValue create( diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java index fc0e18c2e..041d850a6 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageLiteValueProvider.java @@ -42,7 +42,7 @@ public BaseProtoCelValueConverter protoCelValueConverter() { } @Override - public Optional newValue(String structType, Map fields) { + public Optional newValue(String structType, Map fields) { MessageLiteDescriptor descriptor = descriptorPool.findDescriptor(structType).orElse(null); if (descriptor == null) { return Optional.empty(); @@ -55,7 +55,7 @@ public Optional newValue(String structType, Map fields } MessageLite message = descriptor.newMessageBuilder().build(); - return Optional.of(protoLiteCelValueConverter.fromProtoMessageToCelValue(message)); + return Optional.of(protoLiteCelValueConverter.toRuntimeValue(message)); } public static ProtoMessageLiteValueProvider newInstance(CelLiteDescriptor... descriptors) { diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java index f9abdbea1..3e809975e 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java @@ -28,7 +28,7 @@ /** ProtoMessageValue is a struct value with protobuf support. */ @AutoValue @Immutable -public abstract class ProtoMessageValue extends StructValue { +public abstract class ProtoMessageValue extends StructValue { @Override public abstract Message value(); @@ -46,17 +46,17 @@ public boolean isZeroValue() { } @Override - public CelValue select(StringValue field) { + public Object select(String field) { FieldDescriptor fieldDescriptor = - findField(celDescriptorPool(), value().getDescriptorForType(), field.value()); + findField(celDescriptorPool(), value().getDescriptorForType(), field); return protoCelValueConverter().fromProtoMessageFieldToCelValue(value(), fieldDescriptor); } @Override - public Optional find(StringValue field) { + public Optional find(String field) { FieldDescriptor fieldDescriptor = - findField(celDescriptorPool(), value().getDescriptorForType(), field.value()); + findField(celDescriptorPool(), value().getDescriptorForType(), field); // Selecting a field on a protobuf message yields a default value even if the field is not // declared. Therefore, we must exhaustively test whether they are actually declared. diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java index 6dc9dfed2..5bf2927ab 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java @@ -45,7 +45,7 @@ public BaseProtoCelValueConverter protoCelValueConverter() { } @Override - public Optional newValue(String structType, Map fields) { + public Optional newValue(String structType, Map fields) { Message.Builder builder = protoMessageFactory.newBuilder(structType).orElse(null); if (builder == null) { return Optional.empty(); @@ -60,7 +60,7 @@ public Optional newValue(String structType, Map fields fieldValue.ifPresent(o -> builder.setField(fieldDescriptor, o)); } - return Optional.of(protoCelValueConverter.fromProtoMessageToCelValue(builder.build())); + return Optional.of(protoCelValueConverter.toRuntimeValue(builder.build())); } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException(e.getMessage()); } diff --git a/common/src/main/java/dev/cel/common/values/SelectableValue.java b/common/src/main/java/dev/cel/common/values/SelectableValue.java index 5fa0a8939..4db43d144 100644 --- a/common/src/main/java/dev/cel/common/values/SelectableValue.java +++ b/common/src/main/java/dev/cel/common/values/SelectableValue.java @@ -20,18 +20,18 @@ * SelectableValue is an interface for representing a value that supports field selection and * presence tests. Few examples are structs, protobuf messages, maps and optional values. */ -public interface SelectableValue { +public interface SelectableValue { /** * Performs field selection. The behavior depends on the concrete implementation of the value * being selected. For structs and maps, this must throw an exception if the field does not exist. * For optional values, this will return an {@code optional.none()}. */ - CelValue select(T field); + Object select(T field); /** * Finds the field. This will return an {@link Optional#empty()} if the field does not exist. This * can be used for presence testing. */ - Optional find(T field); + Optional find(T field); } diff --git a/common/src/main/java/dev/cel/common/values/StringValue.java b/common/src/main/java/dev/cel/common/values/StringValue.java deleted file mode 100644 index e14c8979c..000000000 --- a/common/src/main/java/dev/cel/common/values/StringValue.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; - -/** StringValue is a simple CelValue wrapper around Java strings. */ -@AutoValue -@Immutable -public abstract class StringValue extends CelValue { - - @Override - public abstract String value(); - - @Override - public boolean isZeroValue() { - return value().isEmpty(); - } - - @Override - public CelType celType() { - return SimpleType.STRING; - } - - public static StringValue create(String value) { - return new AutoValue_StringValue(value); - } -} diff --git a/common/src/main/java/dev/cel/common/values/StructValue.java b/common/src/main/java/dev/cel/common/values/StructValue.java index 8a2351ded..8775ef5c4 100644 --- a/common/src/main/java/dev/cel/common/values/StructValue.java +++ b/common/src/main/java/dev/cel/common/values/StructValue.java @@ -28,5 +28,4 @@ * implementation should return an appropriate default value based on the struct's semantics. */ @Immutable -public abstract class StructValue extends CelValue - implements SelectableValue {} +public abstract class StructValue extends CelValue implements SelectableValue {} diff --git a/common/src/main/java/dev/cel/common/values/TimestampValue.java b/common/src/main/java/dev/cel/common/values/TimestampValue.java deleted file mode 100644 index a03ee2eda..000000000 --- a/common/src/main/java/dev/cel/common/values/TimestampValue.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; -import java.time.Instant; - -/** TimestampValue is a simple CelValue wrapper around {@link java.time.Instant} */ -@AutoValue -@Immutable -public abstract class TimestampValue extends CelValue { - - @Override - public abstract Instant value(); - - @Override - public boolean isZeroValue() { - return Instant.EPOCH.equals(value()); - } - - @Override - public CelType celType() { - return SimpleType.TIMESTAMP; - } - - public static TimestampValue create(Instant value) { - return new AutoValue_TimestampValue(value); - } -} diff --git a/common/src/main/java/dev/cel/common/values/TypeValue.java b/common/src/main/java/dev/cel/common/values/TypeValue.java deleted file mode 100644 index cacffcd48..000000000 --- a/common/src/main/java/dev/cel/common/values/TypeValue.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.TypeType; - -/** TypeValue holds the CEL type information for the underlying CelValue. */ -@AutoValue -@Immutable -public abstract class TypeValue extends CelValue { - - @Override - public abstract CelType value(); - - @Override - public boolean isZeroValue() { - return false; - } - - @Override - public abstract TypeType celType(); - - public static TypeValue create(CelType value) { - return new AutoValue_TypeValue(value, TypeType.create(value)); - } -} diff --git a/common/src/main/java/dev/cel/common/values/UintValue.java b/common/src/main/java/dev/cel/common/values/UintValue.java deleted file mode 100644 index af931e78b..000000000 --- a/common/src/main/java/dev/cel/common/values/UintValue.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2023 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.common.values; - -import com.google.auto.value.AutoValue; -import com.google.common.primitives.UnsignedLong; -import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelType; -import dev.cel.common.types.SimpleType; - -/** - * UintValue represents CelValue for unsigned longs. This either leverages Guava's implementation of - * {@link UnsignedLong}, or just holds a primitive long. - */ -@Immutable -@AutoValue -@AutoValue.CopyAnnotations -public abstract class UintValue extends CelValue { - - @Override - public abstract UnsignedLong value(); - - @Override - public boolean isZeroValue() { - return value().longValue() == 0; - } - - @Override - public CelType celType() { - return SimpleType.UINT; - } - - public static UintValue create(UnsignedLong value) { - return new AutoValue_UintValue(value); - } - - public static UintValue create(long value) { - return new AutoValue_UintValue(UnsignedLong.fromLongBits(value)); - } -} diff --git a/common/src/test/java/dev/cel/common/values/BUILD.bazel b/common/src/test/java/dev/cel/common/values/BUILD.bazel index 131344e1a..ab7eae8dd 100644 --- a/common/src/test/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel @@ -13,19 +13,16 @@ java_library( "//common:cel_ast", "//common:cel_descriptor_util", "//common:options", - "//common:runtime_exception", "//common/internal:cel_descriptor_pools", "//common/internal:cel_lite_descriptor_pool", "//common/internal:default_lite_descriptor_pool", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", - "//common/internal:proto_time_utils", "//common/types", "//common/types:type_providers", "//common/values", "//common/values:cel_byte_string", - "//common/values:cel_value", "//common/values:cel_value_provider", "//common/values:combined_cel_value_provider", "//common/values:proto_message_lite_value", diff --git a/common/src/test/java/dev/cel/common/values/BoolValueTest.java b/common/src/test/java/dev/cel/common/values/BoolValueTest.java deleted file mode 100644 index dbe9ab577..000000000 --- a/common/src/test/java/dev/cel/common/values/BoolValueTest.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class BoolValueTest { - - @Test - public void falseBool() { - BoolValue boolValue = BoolValue.create(false); - - assertThat(boolValue.value()).isFalse(); - assertThat(boolValue.isZeroValue()).isTrue(); - } - - @Test - public void trueBool() { - BoolValue boolValue = BoolValue.create(true); - - assertThat(boolValue.value()).isTrue(); - assertThat(boolValue.isZeroValue()).isFalse(); - } - - @Test - public void celTypeTest() { - BoolValue value = BoolValue.create(true); - - assertThat(value.celType()).isEqualTo(SimpleType.BOOL); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> BoolValue.create(null)); - } -} diff --git a/common/src/test/java/dev/cel/common/values/BytesValueTest.java b/common/src/test/java/dev/cel/common/values/BytesValueTest.java deleted file mode 100644 index 115838a0f..000000000 --- a/common/src/test/java/dev/cel/common/values/BytesValueTest.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class BytesValueTest { - - @Test - public void emptyBytes() { - BytesValue bytesValue = BytesValue.create(CelByteString.EMPTY); - - assertThat(bytesValue.value()).isEqualTo(CelByteString.of(new byte[0])); - assertThat(bytesValue.isZeroValue()).isTrue(); - } - - @Test - public void constructBytes() { - BytesValue bytesValue = BytesValue.create(CelByteString.of(new byte[] {0x1, 0x5, 0xc})); - - assertThat(bytesValue.value()).isEqualTo(CelByteString.of(new byte[] {0x1, 0x5, 0xc})); - assertThat(bytesValue.isZeroValue()).isFalse(); - } - - @Test - public void celTypeTest() { - BytesValue value = BytesValue.create(CelByteString.EMPTY); - - assertThat(value.celType()).isEqualTo(SimpleType.BYTES); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> BytesValue.create(null)); - } -} diff --git a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java index 528cddee4..e4d767ef4 100644 --- a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java @@ -16,8 +16,6 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,74 +25,37 @@ public class CelValueConverterTest { private static final CelValueConverter CEL_VALUE_CONVERTER = new CelValueConverter() {}; - @Test - public void fromJavaPrimitiveToCelValue_returnsOpaqueValue() { - OpaqueValue opaqueValue = - (OpaqueValue) CEL_VALUE_CONVERTER.fromJavaPrimitiveToCelValue(new UserDefinedClass()); - - assertThat(opaqueValue.celType().name()).contains("UserDefinedClass"); - } - @Test @SuppressWarnings("unchecked") // Test only - public void fromJavaObjectToCelValue_optionalValue() { - OptionalValue optionalValue = - (OptionalValue) - CEL_VALUE_CONVERTER.fromJavaObjectToCelValue(Optional.of("test")); + public void toRuntimeValue_optionalValue() { + OptionalValue optionalValue = + (OptionalValue) CEL_VALUE_CONVERTER.toRuntimeValue(Optional.of("test")); - assertThat(optionalValue).isEqualTo(OptionalValue.create(StringValue.create("test"))); + assertThat(optionalValue).isEqualTo(OptionalValue.create("test")); } @Test - public void fromJavaObjectToCelValue_errorValue() { + public void toRuntimeValue_errorValue() { IllegalArgumentException e = new IllegalArgumentException("error"); - ErrorValue errorValue = (ErrorValue) CEL_VALUE_CONVERTER.fromJavaObjectToCelValue(e); + ErrorValue errorValue = (ErrorValue) CEL_VALUE_CONVERTER.toRuntimeValue(e); assertThat(errorValue.value()).isEqualTo(e); } @Test @SuppressWarnings("unchecked") // Test only - public void fromCelValueToJavaObject_mapValue() { - ImmutableMap result = - (ImmutableMap) - CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - ImmutableMapValue.create( - ImmutableMap.of(StringValue.create("test"), IntValue.create(1)))); - - assertThat(result).containsExactly("test", 1L); - } - - @Test - @SuppressWarnings("unchecked") // Test only - public void fromCelValueToJavaObject_listValue() { - ImmutableList result = - (ImmutableList) - CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - ImmutableListValue.create(ImmutableList.of(BoolValue.create(true)))); - - assertThat(result).containsExactly(true); - } - - @Test - @SuppressWarnings("unchecked") // Test only - public void fromCelValueToJavaObject_optionalValue() { - Optional result = - (Optional) - CEL_VALUE_CONVERTER.fromCelValueToJavaObject(OptionalValue.create(IntValue.create(2))); + public void unwrap_optionalValue() { + Optional result = (Optional) CEL_VALUE_CONVERTER.unwrap(OptionalValue.create(2L)); assertThat(result).isEqualTo(Optional.of(2L)); } @Test @SuppressWarnings("unchecked") // Test only - public void fromCelValueToJavaObject_emptyOptionalValue() { - Optional result = - (Optional) CEL_VALUE_CONVERTER.fromCelValueToJavaObject(OptionalValue.EMPTY); + public void unwrap_emptyOptionalValue() { + Optional result = (Optional) CEL_VALUE_CONVERTER.unwrap(OptionalValue.EMPTY); assertThat(result).isEqualTo(Optional.empty()); } - - private static class UserDefinedClass {} } diff --git a/common/src/test/java/dev/cel/common/values/DoubleValueTest.java b/common/src/test/java/dev/cel/common/values/DoubleValueTest.java deleted file mode 100644 index f2c8374a1..000000000 --- a/common/src/test/java/dev/cel/common/values/DoubleValueTest.java +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.testing.ClassSanityTester; -import com.google.common.testing.EqualsTester; -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class DoubleValueTest { - - @Test - public void emptyDouble() { - DoubleValue doubleValue = DoubleValue.create(0.0d); - - assertThat(doubleValue.value()).isEqualTo(0.0d); - assertThat(doubleValue.isZeroValue()).isTrue(); - } - - @Test - public void constructDouble() { - DoubleValue doubleValue = DoubleValue.create(5.0d); - - assertThat(doubleValue.value()).isEqualTo(5.0d); - assertThat(doubleValue.isZeroValue()).isFalse(); - } - - @Test - public void celTypeTest() { - DoubleValue value = DoubleValue.create(0.0d); - - assertThat(value.celType()).isEqualTo(SimpleType.DOUBLE); - } - - @Test - public void equalityTest() { - new EqualsTester() - .addEqualityGroup(DoubleValue.create(10.5)) - .addEqualityGroup(DoubleValue.create(0.0d), DoubleValue.create(0)) - .addEqualityGroup(DoubleValue.create(15.3), DoubleValue.create(15.3)) - .addEqualityGroup( - DoubleValue.create(Double.MAX_VALUE), DoubleValue.create(Double.MAX_VALUE)) - .addEqualityGroup( - DoubleValue.create(Double.MIN_VALUE), DoubleValue.create(Double.MIN_VALUE)) - .testEquals(); - } - - @Test - public void sanityTest() throws Exception { - new ClassSanityTester() - .setDefault(DoubleValue.class, DoubleValue.create(100.94d)) - .setDistinctValues(DoubleValue.class, DoubleValue.create(0.0d), DoubleValue.create(100.0d)) - .forAllPublicStaticMethods(DoubleValue.class) - .thatReturn(DoubleValue.class) - .testEquals() - .testNulls(); - } - - @Test - public void hashCode_smokeTest() { - assertThat(DoubleValue.create(0).hashCode()).isEqualTo(1000003); - assertThat(DoubleValue.create(0.0d).hashCode()).isEqualTo(1000003); - assertThat(DoubleValue.create(100.5d).hashCode()).isEqualTo(1079403075); - assertThat(DoubleValue.create(Double.MAX_VALUE).hashCode()).isEqualTo(-2145435069); - assertThat(DoubleValue.create(Double.MIN_VALUE).hashCode()).isEqualTo(1000002); - } -} diff --git a/common/src/test/java/dev/cel/common/values/DurationValueTest.java b/common/src/test/java/dev/cel/common/values/DurationValueTest.java deleted file mode 100644 index e53e75379..000000000 --- a/common/src/test/java/dev/cel/common/values/DurationValueTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import dev.cel.common.types.SimpleType; -import java.time.Duration; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class DurationValueTest { - - @Test - public void emptyDuration() { - DurationValue durationValue = DurationValue.create(Duration.ZERO); - - assertThat(durationValue.value()).isEqualTo(Duration.ofSeconds(0)); - assertThat(durationValue.isZeroValue()).isTrue(); - } - - @Test - public void constructDuration() { - DurationValue durationValue = DurationValue.create(Duration.ofSeconds(10000)); - - assertThat(durationValue.value()).isEqualTo(Duration.ofSeconds(10000)); - assertThat(durationValue.isZeroValue()).isFalse(); - } - - @Test - public void celTypeTest() { - DurationValue value = DurationValue.create(Duration.ZERO); - - assertThat(value.celType()).isEqualTo(SimpleType.DURATION); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> DurationValue.create(null)); - } -} diff --git a/common/src/test/java/dev/cel/common/values/EnumValueTest.java b/common/src/test/java/dev/cel/common/values/EnumValueTest.java deleted file mode 100644 index d9fde4b76..000000000 --- a/common/src/test/java/dev/cel/common/values/EnumValueTest.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; - -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class EnumValueTest { - - private enum TestKind { - ONE, - TWO - } - - @Test - public void enumValue_construct() { - EnumValue one = EnumValue.create(TestKind.ONE); - EnumValue two = EnumValue.create(TestKind.TWO); - - assertThat(one.value()).isEqualTo(TestKind.ONE); - assertThat(two.value()).isEqualTo(TestKind.TWO); - } - - @Test - public void enumValue_isZeroValue_returnsFalse() { - assertThat(EnumValue.create(TestKind.ONE).isZeroValue()).isFalse(); - assertThat(EnumValue.create(TestKind.TWO).isZeroValue()).isFalse(); - } - - @Test - public void celTypeTest() { - EnumValue value = EnumValue.create(TestKind.ONE); - - assertThat(value.celType()).isEqualTo(SimpleType.INT); - } -} diff --git a/common/src/test/java/dev/cel/common/values/ImmutableListValueTest.java b/common/src/test/java/dev/cel/common/values/ImmutableListValueTest.java deleted file mode 100644 index 1d5099ec2..000000000 --- a/common/src/test/java/dev/cel/common/values/ImmutableListValueTest.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import com.google.common.collect.ImmutableList; -import dev.cel.common.types.ListType; -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class ImmutableListValueTest { - - @Test - public void emptyList() { - ListValue listValue = ImmutableListValue.create(ImmutableList.of()); - - assertThat(listValue.value()).isEmpty(); - assertThat(listValue.isZeroValue()).isTrue(); - } - - @Test - public void listValue_construct() { - IntValue one = IntValue.create(1L); - IntValue two = IntValue.create(2L); - IntValue three = IntValue.create(3L); - - ListValue listValue = ImmutableListValue.create(ImmutableList.of(one, two, three)); - - assertThat(listValue.value()).containsExactly(one, two, three).inOrder(); - assertThat(listValue.isZeroValue()).isFalse(); - } - - @Test - public void listValue_mixedTypes() { - IntValue one = IntValue.create(1L); - DoubleValue two = DoubleValue.create(2.0d); - StringValue three = StringValue.create("test"); - - ListValue listValue = ImmutableListValue.create(ImmutableList.of(one, two, three)); - - assertThat(listValue.value()).containsExactly(one, two, three).inOrder(); - assertThat(listValue.isZeroValue()).isFalse(); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> ImmutableListValue.create(null)); - } - - @Test - public void celTypeTest() { - ListValue value = ImmutableListValue.create(ImmutableList.of()); - - assertThat(value.celType()).isEqualTo(ListType.create(SimpleType.DYN)); - } -} diff --git a/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java b/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java deleted file mode 100644 index ee91712ec..000000000 --- a/common/src/test/java/dev/cel/common/values/ImmutableMapValueTest.java +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import com.google.common.collect.ImmutableMap; -import com.google.testing.junit.testparameterinjector.TestParameterInjector; -import com.google.testing.junit.testparameterinjector.TestParameters; -import dev.cel.common.CelRuntimeException; -import dev.cel.common.types.MapType; -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(TestParameterInjector.class) -public class ImmutableMapValueTest { - - @Test - public void emptyMap() { - ImmutableMapValue mapValue = ImmutableMapValue.create(ImmutableMap.of()); - - assertThat(mapValue.value()).isEmpty(); - assertThat(mapValue.isZeroValue()).isTrue(); - } - - @Test - public void mapValue_construct() { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello)); - - assertThat(mapValue.value()).containsExactly(one, hello); - assertThat(mapValue.isZeroValue()).isFalse(); - } - - @Test - public void get_success() { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello)); - - assertThat(mapValue.get(one)).isEqualTo(hello); - } - - @Test - public void get_nonExistentKey_throws() { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello)); - - CelRuntimeException exception = - assertThrows(CelRuntimeException.class, () -> mapValue.get(IntValue.create(100L))); - assertThat(exception).hasMessageThat().contains("key '100' is not present in map."); - } - - @Test - @TestParameters("{key: 1, expectedResult: true}") - @TestParameters("{key: 100, expectedResult: false}") - public void find_success(long key, boolean expectedResult) { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello)); - - assertThat(mapValue.find(IntValue.create(key)).isPresent()).isEqualTo(expectedResult); - } - - @Test - public void mapValue_mixedTypes() { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello, hello, one)); - - assertThat(mapValue.value()).containsExactly(one, hello, hello, one); - assertThat(mapValue.isZeroValue()).isFalse(); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> ImmutableMapValue.create(null)); - } - - @Test - public void celTypeTest() { - MapValue value = ImmutableMapValue.create(ImmutableMap.of()); - - assertThat(value.celType()).isEqualTo(MapType.create(SimpleType.DYN, SimpleType.DYN)); - } -} diff --git a/common/src/test/java/dev/cel/common/values/IntValueTest.java b/common/src/test/java/dev/cel/common/values/IntValueTest.java deleted file mode 100644 index fa2b15595..000000000 --- a/common/src/test/java/dev/cel/common/values/IntValueTest.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.testing.ClassSanityTester; -import com.google.common.testing.EqualsTester; -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class IntValueTest { - - @Test - public void emptyInt() { - IntValue intValue = IntValue.create(0L); - - assertThat(intValue.value()).isEqualTo(0L); - assertThat(intValue.isZeroValue()).isTrue(); - } - - @Test - public void constructInt() { - IntValue uintValue = IntValue.create(5L); - - assertThat(uintValue.value()).isEqualTo(5L); - assertThat(uintValue.isZeroValue()).isFalse(); - } - - @Test - public void equalityTest() { - new EqualsTester() - .addEqualityGroup(IntValue.create(10)) - .addEqualityGroup(IntValue.create(0), IntValue.create(0)) - .addEqualityGroup(IntValue.create(15), IntValue.create(15)) - .addEqualityGroup(IntValue.create(Long.MAX_VALUE), IntValue.create(Long.MAX_VALUE)) - .addEqualityGroup(IntValue.create(Long.MIN_VALUE), IntValue.create(Long.MIN_VALUE)) - .testEquals(); - } - - @Test - public void sanityTest() throws Exception { - new ClassSanityTester() - .setDefault(IntValue.class, IntValue.create(100)) - .setDistinctValues(IntValue.class, IntValue.create(0), IntValue.create(100)) - .forAllPublicStaticMethods(IntValue.class) - .thatReturn(IntValue.class) - .testEquals() - .testNulls(); - } - - @Test - public void hashCode_smokeTest() { - assertThat(IntValue.create(0).hashCode()).isEqualTo(1000003); - assertThat(IntValue.create(100).hashCode()).isEqualTo(999975); - assertThat(IntValue.create(Long.MAX_VALUE).hashCode()).isEqualTo(-2146483645); - assertThat(IntValue.create(Long.MIN_VALUE).hashCode()).isEqualTo(-2146483645); - } - - @Test - public void celTypeTest() { - IntValue value = IntValue.create(0); - - assertThat(value.celType()).isEqualTo(SimpleType.INT); - } -} diff --git a/common/src/test/java/dev/cel/common/values/OptionalValueTest.java b/common/src/test/java/dev/cel/common/values/OptionalValueTest.java index 6a991a6a9..24b3ea30b 100644 --- a/common/src/test/java/dev/cel/common/values/OptionalValueTest.java +++ b/common/src/test/java/dev/cel/common/values/OptionalValueTest.java @@ -34,7 +34,7 @@ public class OptionalValueTest { @Test public void emptyOptional() { - OptionalValue optionalValue = OptionalValue.EMPTY; + OptionalValue optionalValue = OptionalValue.EMPTY; assertThat(optionalValue.isZeroValue()).isTrue(); NoSuchElementException exception = @@ -44,7 +44,7 @@ public void emptyOptional() { @Test public void optionalValue_selectEmpty() { - CelValue optionalValue = OptionalValue.EMPTY.select(StringValue.create("bogus")); + OptionalValue optionalValue = OptionalValue.EMPTY.select("bogus"); assertThat(optionalValue).isEqualTo(OptionalValue.EMPTY); assertThat(optionalValue.isZeroValue()).isTrue(); @@ -52,19 +52,18 @@ public void optionalValue_selectEmpty() { @Test public void optionalValue_construct() { - OptionalValue optionalValue = OptionalValue.create(IntValue.create(1L)); + OptionalValue optionalValue = OptionalValue.create(1L); - assertThat(optionalValue.value()).isEqualTo(IntValue.create(1L)); + assertThat(optionalValue.value()).isEqualTo(1L); assertThat(optionalValue.isZeroValue()).isFalse(); } @Test public void optSelectField_map_success() { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello)); - OptionalValue> optionalValueContainingMap = + Long one = 1L; + String hello = "hello"; + ImmutableMap mapValue = ImmutableMap.of(one, hello); + OptionalValue, Long> optionalValueContainingMap = OptionalValue.create(mapValue); assertThat(optionalValueContainingMap.select(one)).isEqualTo(OptionalValue.create(hello)); @@ -72,11 +71,10 @@ public void optSelectField_map_success() { @Test public void optSelectField_map_returnsEmpty() { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello)); - OptionalValue> optionalValueContainingMap = + Long one = 1L; + String hello = "hello"; + ImmutableMap mapValue = ImmutableMap.of(one, hello); + OptionalValue, Object> optionalValueContainingMap = OptionalValue.create(mapValue); assertThat(optionalValueContainingMap.select(NullValue.NULL_VALUE)) @@ -86,36 +84,32 @@ public void optSelectField_map_returnsEmpty() { @Test public void optSelectField_struct_success() { CelCustomStruct celCustomStruct = new CelCustomStruct(5L); - OptionalValue optionalValueContainingStruct = + OptionalValue optionalValueContainingStruct = OptionalValue.create(celCustomStruct); - assertThat(optionalValueContainingStruct.select(StringValue.create("data"))) - .isEqualTo(OptionalValue.create(IntValue.create(5L))); + assertThat(optionalValueContainingStruct.select("data")).isEqualTo(OptionalValue.create(5L)); } @Test public void optSelectField_struct_returnsEmpty() { CelCustomStruct celCustomStruct = new CelCustomStruct(5L); - OptionalValue optionalValueContainingStruct = + OptionalValue optionalValueContainingStruct = OptionalValue.create(celCustomStruct); - assertThat(optionalValueContainingStruct.select(StringValue.create("bogus"))) - .isEqualTo(OptionalValue.EMPTY); + assertThat(optionalValueContainingStruct.select("bogus")).isEqualTo(OptionalValue.EMPTY); } @Test @TestParameters("{key: 1, expectedResult: true}") @TestParameters("{key: 100, expectedResult: false}") public void findField_map_success(long key, boolean expectedResult) { - IntValue one = IntValue.create(1L); - StringValue hello = StringValue.create("hello"); - ImmutableMapValue mapValue = - ImmutableMapValue.create(ImmutableMap.of(one, hello)); - OptionalValue> optionalValueContainingMap = + Long one = 1L; + String hello = "hello"; + ImmutableMap mapValue = ImmutableMap.of(one, hello); + OptionalValue, Long> optionalValueContainingMap = OptionalValue.create(mapValue); - assertThat(optionalValueContainingMap.find(IntValue.create(key)).isPresent()) - .isEqualTo(expectedResult); + assertThat(optionalValueContainingMap.find(key).isPresent()).isEqualTo(expectedResult); } @Test @@ -123,16 +117,15 @@ public void findField_map_success(long key, boolean expectedResult) { @TestParameters("{field: 'bogus', expectedResult: false}") public void findField_struct_success(String field, boolean expectedResult) { CelCustomStruct celCustomStruct = new CelCustomStruct(5L); - OptionalValue optionalValueContainingStruct = + OptionalValue optionalValueContainingStruct = OptionalValue.create(celCustomStruct); - assertThat(optionalValueContainingStruct.find(StringValue.create(field)).isPresent()) - .isEqualTo(expectedResult); + assertThat(optionalValueContainingStruct.find(field).isPresent()).isEqualTo(expectedResult); } @Test public void findField_onEmptyOptional() { - assertThat(OptionalValue.EMPTY.find(StringValue.create("bogus"))).isEmpty(); + assertThat(OptionalValue.EMPTY.find("bogus")).isEmpty(); } @Test @@ -142,13 +135,13 @@ public void create_nullValue_throws() { @Test public void celTypeTest() { - OptionalValue value = OptionalValue.EMPTY; + OptionalValue value = OptionalValue.EMPTY; assertThat(value.celType()).isEqualTo(OptionalType.create(SimpleType.DYN)); } @SuppressWarnings("Immutable") // Test only - private static class CelCustomStruct extends StructValue { + private static class CelCustomStruct extends StructValue { private final long data; @Override @@ -167,14 +160,14 @@ public CelType celType() { } @Override - public CelValue select(StringValue field) { + public Long select(String field) { return find(field).get(); } @Override - public Optional find(StringValue field) { - if (field.value().equals("data")) { - return Optional.of(IntValue.create(value())); + public Optional find(String field) { + if (field.equals("data")) { + return Optional.of(value()); } return Optional.empty(); diff --git a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java index 234411ce7..61a05cf89 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -19,8 +19,6 @@ import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; -import java.time.Duration; -import java.time.Instant; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -33,41 +31,8 @@ public class ProtoCelValueConverterTest { DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); @Test - public void fromCelValueToJavaObject_returnsInstantValue() { - Instant timestamp = - (Instant) - PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - TimestampValue.create(Instant.ofEpochSecond(50))); - - assertThat(timestamp).isEqualTo(Instant.ofEpochSecond(50)); - } - - @Test - public void fromCelValueToJavaObject_returnsDurationValue() { - Duration duration = - (Duration) - PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - DurationValue.create(Duration.ofSeconds(10))); - - assertThat(duration).isEqualTo(Duration.ofSeconds(10)); - } - - @Test - public void fromCelValueToJavaObject_returnsCelBytesValue() { - CelByteString byteString = - (CelByteString) - PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - BytesValue.create(CelByteString.of(new byte[] {0x1, 0x5, 0xc}))); - - // Note: No conversion is attempted. CelByteString is the native Java type equivalent for CEL's - // bytes. - assertThat(byteString).isEqualTo(CelByteString.of(new byte[] {0x1, 0x5, 0xc})); - } - - @Test - public void fromCelValueToJavaObject_returnsCelNullValue() { - NullValue nullValue = - (NullValue) PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject(NullValue.NULL_VALUE); + public void unwrap_nullValue() { + NullValue nullValue = (NullValue) PROTO_CEL_VALUE_CONVERTER.unwrap(NullValue.NULL_VALUE); // Note: No conversion is attempted. We're using dev.cel.common.values.NullValue.NULL_VALUE as // the diff --git a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java index 1e1d5063f..cec3e0fbf 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoLiteCelValueConverterTest.java @@ -15,19 +15,23 @@ package dev.cel.common.values; import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; +import com.google.common.primitives.UnsignedLong; +import com.google.protobuf.BoolValue; import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; +import com.google.protobuf.DoubleValue; import com.google.protobuf.Duration; import com.google.protobuf.ExtensionRegistryLite; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; import com.google.protobuf.Int64Value; import com.google.protobuf.MessageLite; +import com.google.protobuf.StringValue; import com.google.protobuf.TextFormat; import com.google.protobuf.Timestamp; import com.google.protobuf.UInt32Value; @@ -54,52 +58,50 @@ public class ProtoLiteCelValueConverterTest { ProtoLiteCelValueConverter.newInstance(DESCRIPTOR_POOL); @Test - public void fromProtoMessageToCelValue_withTestMessage_convertsToProtoMessageLiteValue() { + public void + fromProtoMessageToCelValue_withTestMessage_convertsToProtoMessageLiteValueFromProtoMessage() { ProtoMessageLiteValue protoMessageLiteValue = (ProtoMessageLiteValue) - PROTO_LITE_CEL_VALUE_CONVERTER.fromProtoMessageToCelValue( - TestAllTypes.getDefaultInstance()); + PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(TestAllTypes.getDefaultInstance()); assertThat(protoMessageLiteValue.value()).isEqualTo(TestAllTypes.getDefaultInstance()); } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum WellKnownProtoTestCase { - BOOL(com.google.protobuf.BoolValue.of(true), BoolValue.create(true)), - BYTES( - com.google.protobuf.BytesValue.of(ByteString.copyFromUtf8("test")), - BytesValue.create(CelByteString.of("test".getBytes(UTF_8)))), - FLOAT(FloatValue.of(1.0f), DoubleValue.create(1.0f)), - DOUBLE(com.google.protobuf.DoubleValue.of(1.0), DoubleValue.create(1.0)), - INT32(Int32Value.of(1), IntValue.create(1)), - INT64(Int64Value.of(1L), IntValue.create(1L)), - STRING(com.google.protobuf.StringValue.of("test"), StringValue.create("test")), + BOOL(BoolValue.of(true), true), + BYTES(BytesValue.of(ByteString.copyFromUtf8("test")), CelByteString.copyFromUtf8("test")), + FLOAT(FloatValue.of(1.0f), 1.0d), + DOUBLE(DoubleValue.of(1.0), 1.0d), + INT32(Int32Value.of(1), 1L), + INT64(Int64Value.of(1L), 1L), + STRING(StringValue.of("test"), "test"), DURATION( Duration.newBuilder().setSeconds(10).setNanos(50).build(), - DurationValue.create(java.time.Duration.ofSeconds(10, 50))), + java.time.Duration.ofSeconds(10, 50)), TIMESTAMP( Timestamp.newBuilder().setSeconds(1678886400L).setNanos(123000000).build(), - TimestampValue.create(Instant.ofEpochSecond(1678886400L, 123000000))), - UINT32(UInt32Value.of(1), UintValue.create(1)), - UINT64(UInt64Value.of(1L), UintValue.create(1L)), + Instant.ofEpochSecond(1678886400L, 123000000)), + UINT32(UInt32Value.of(1), UnsignedLong.valueOf(1)), + UINT64(UInt64Value.of(1L), UnsignedLong.valueOf(1L)), ; private final MessageLite msg; - private final CelValue celValue; + private final Object value; - WellKnownProtoTestCase(MessageLite msg, CelValue celValue) { + WellKnownProtoTestCase(MessageLite msg, Object value) { this.msg = msg; - this.celValue = celValue; + this.value = value; } } @Test - public void fromProtoMessageToCelValue_withWellKnownProto_convertsToEquivalentCelValue( + public void fromProtoMessageToCelValue_withWellKnownProto_convertsToPrimitivesFromProtoMessage( @TestParameter WellKnownProtoTestCase testCase) { - CelValue convertedCelValue = - PROTO_LITE_CEL_VALUE_CONVERTER.fromProtoMessageToCelValue(testCase.msg); + Object adaptedValue = PROTO_LITE_CEL_VALUE_CONVERTER.toRuntimeValue(testCase.msg); - assertThat(convertedCelValue).isEqualTo(testCase.celValue); + assertThat(adaptedValue).isEqualTo(testCase.value); } /** Test cases for repeated_int64: 1L,2L,3L */ diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java index 812da6963..bbe116c80 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueProviderTest.java @@ -37,7 +37,7 @@ public void newValue_unknownType_returnsEmpty() { @Test public void newValue_emptyFields_success() { - Optional value = + Optional value = VALUE_PROVIDER.newValue("cel.expr.conformance.proto3.TestAllTypes", ImmutableMap.of()); ProtoMessageLiteValue protoMessageLiteValue = (ProtoMessageLiteValue) value.get(); diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java index 03891bb09..dbfb55cf9 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageLiteValueTest.java @@ -76,78 +76,61 @@ public void create_withPopulatedMessage() { assertThat(messageLiteValue.isZeroValue()).isFalse(); } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum SelectFieldTestCase { - BOOL("single_bool", BoolValue.create(true)), - INT32("single_int32", IntValue.create(4L)), - INT64("single_int64", IntValue.create(5L)), - SINT32("single_sint32", IntValue.create(1L)), - SINT64("single_sint64", IntValue.create(2L)), - UINT32("single_uint32", UintValue.create(UnsignedLong.valueOf(1L))), - UINT64("single_uint64", UintValue.create(UnsignedLong.MAX_VALUE)), - FLOAT("single_float", DoubleValue.create(1.5d)), - DOUBLE("single_double", DoubleValue.create(2.5d)), - FIXED32("single_fixed32", IntValue.create(20)), - SFIXED32("single_sfixed32", IntValue.create(30)), - FIXED64("single_fixed64", IntValue.create(40)), - SFIXED64("single_sfixed64", IntValue.create(50)), - STRING("single_string", StringValue.create("test")), - BYTES("single_bytes", BytesValue.create(CelByteString.of(new byte[] {0x01}))), - DURATION("single_duration", DurationValue.create(Duration.ofSeconds(100))), - TIMESTAMP("single_timestamp", TimestampValue.create(Instant.ofEpochSecond(100))), - INT32_WRAPPER("single_int32_wrapper", IntValue.create(5L)), - INT64_WRAPPER("single_int64_wrapper", IntValue.create(10L)), - UINT32_WRAPPER("single_uint32_wrapper", UintValue.create(UnsignedLong.valueOf(1L))), - UINT64_WRAPPER("single_uint64_wrapper", UintValue.create(UnsignedLong.MAX_VALUE)), - FLOAT_WRAPPER("single_float_wrapper", DoubleValue.create(7.5d)), - DOUBLE_WRAPPER("single_double_wrapper", DoubleValue.create(8.5d)), - STRING_WRAPPER("single_string_wrapper", StringValue.create("hello")), - BYTES_WRAPPER("single_bytes_wrapper", BytesValue.create(CelByteString.of(new byte[] {0x02}))), - REPEATED_INT64( - "repeated_int64", - ImmutableListValue.create(ImmutableList.of(IntValue.create(5L), IntValue.create(6L)))), + BOOL("single_bool", true), + INT32("single_int32", 4L), + INT64("single_int64", 5L), + SINT32("single_sint32", 1L), + SINT64("single_sint64", 2L), + UINT32("single_uint32", UnsignedLong.valueOf(1L)), + UINT64("single_uint64", UnsignedLong.MAX_VALUE), + FLOAT("single_float", 1.5d), + DOUBLE("single_double", 2.5d), + FIXED32("single_fixed32", 20), + SFIXED32("single_sfixed32", 30), + FIXED64("single_fixed64", 40), + SFIXED64("single_sfixed64", 50), + STRING("single_string", "test"), + BYTES("single_bytes", CelByteString.of(new byte[] {0x01})), + DURATION("single_duration", Duration.ofSeconds(100)), + TIMESTAMP("single_timestamp", Instant.ofEpochSecond(100)), + INT32_WRAPPER("single_int32_wrapper", 5L), + INT64_WRAPPER("single_int64_wrapper", 10L), + UINT32_WRAPPER("single_uint32_wrapper", UnsignedLong.valueOf(1L)), + UINT64_WRAPPER("single_uint64_wrapper", UnsignedLong.MAX_VALUE), + FLOAT_WRAPPER("single_float_wrapper", 7.5d), + DOUBLE_WRAPPER("single_double_wrapper", 8.5d), + STRING_WRAPPER("single_string_wrapper", "hello"), + BYTES_WRAPPER("single_bytes_wrapper", CelByteString.of(new byte[] {0x02})), + REPEATED_INT64("repeated_int64", ImmutableList.of(5L, 6L)), REPEATED_UINT64( - "repeated_uint64", - ImmutableListValue.create(ImmutableList.of(UintValue.create(7L), UintValue.create(8L)))), - REPEATED_FLOAT( - "repeated_float", - ImmutableListValue.create( - ImmutableList.of(DoubleValue.create(1.5d), DoubleValue.create(2.5d)))), - REPEATED_DOUBLE( - "repeated_double", - ImmutableListValue.create( - ImmutableList.of(DoubleValue.create(3.5d), DoubleValue.create(4.5d)))), - REPEATED_STRING( - "repeated_string", - ImmutableListValue.create( - ImmutableList.of(StringValue.create("foo"), StringValue.create("bar")))), - MAP_INT64_INT64( - "map_int64_int64", - ImmutableMapValue.create( - ImmutableMap.of( - IntValue.create(1L), - IntValue.create(2L), - IntValue.create(3L), - IntValue.create(4L)))), + "repeated_uint64", ImmutableList.of(UnsignedLong.valueOf(7L), UnsignedLong.valueOf(8L))), + REPEATED_FLOAT("repeated_float", ImmutableList.of(1.5d, 2.5d)), + REPEATED_DOUBLE("repeated_double", ImmutableList.of(3.5d, 4.5d)), + + REPEATED_STRING("repeated_string", ImmutableList.of("foo", "bar")), + + MAP_INT64_INT64("map_int64_int64", ImmutableMap.of(1L, 2L, 3L, 4L)), + MAP_UINT32_UINT64( "map_uint32_uint64", - ImmutableMapValue.create( - ImmutableMap.of( - UintValue.create(5L), - UintValue.create(6L), - UintValue.create(7L), - UintValue.create(8L)))), - MAP_STRING_STRING( - "map_string_string", - ImmutableMapValue.create( - ImmutableMap.of(StringValue.create("a"), StringValue.create("b")))), - NESTED_ENUM("standalone_enum", IntValue.create(1L)); + ImmutableMap.of( + UnsignedLong.valueOf(5L), + UnsignedLong.valueOf(6L), + UnsignedLong.valueOf(7L), + UnsignedLong.valueOf(8L))), + + MAP_STRING_STRING("map_string_string", ImmutableMap.of("a", "b")), + + NESTED_ENUM("standalone_enum", 1L); private final String fieldName; - private final CelValue celValue; + private final Object value; - SelectFieldTestCase(String fieldName, CelValue celValue) { + SelectFieldTestCase(String fieldName, Object value) { this.fieldName = fieldName; - this.celValue = celValue; + this.value = value; } } @@ -207,30 +190,30 @@ public void selectField_success(@TestParameter SelectFieldTestCase testCase) { "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER); - CelValue selectedValue = protoMessageValue.select(StringValue.create(testCase.fieldName)); + Object selectedValue = protoMessageValue.select(testCase.fieldName); - assertThat(selectedValue).isEqualTo(testCase.celValue); - assertThat(selectedValue.isZeroValue()).isFalse(); + assertThat(selectedValue).isEqualTo(testCase.value); } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum DefaultValueTestCase { - BOOL("single_bool", BoolValue.create(false)), - INT32("single_int32", IntValue.create(0L)), - INT64("single_int64", IntValue.create(0L)), - SINT32("single_sint32", IntValue.create(0L)), - SINT64("single_sint64", IntValue.create(0L)), - UINT32("single_uint32", UintValue.create(0L)), - UINT64("single_uint64", UintValue.create(0L)), - FIXED32("single_fixed32", IntValue.create(0)), - SFIXED32("single_sfixed32", IntValue.create(0)), - FIXED64("single_fixed64", IntValue.create(0)), - SFIXED64("single_sfixed64", IntValue.create(0)), - FLOAT("single_float", DoubleValue.create(0d)), - DOUBLE("single_double", DoubleValue.create(0d)), - STRING("single_string", StringValue.create("")), - BYTES("single_bytes", BytesValue.create(CelByteString.EMPTY)), - DURATION("single_duration", DurationValue.create(Duration.ZERO)), - TIMESTAMP("single_timestamp", TimestampValue.create(Instant.EPOCH)), + BOOL("single_bool", false), + INT32("single_int32", 0L), + INT64("single_int64", 0L), + SINT32("single_sint32", 0L), + SINT64("single_sint64", 0L), + UINT32("single_uint32", UnsignedLong.ZERO), + UINT64("single_uint64", UnsignedLong.ZERO), + FIXED32("single_fixed32", 0), + SFIXED32("single_sfixed32", 0), + FIXED64("single_fixed64", 0), + SFIXED64("single_sfixed64", 0), + FLOAT("single_float", 0d), + DOUBLE("single_double", 0d), + STRING("single_string", ""), + BYTES("single_bytes", CelByteString.EMPTY), + DURATION("single_duration", Duration.ZERO), + TIMESTAMP("single_timestamp", Instant.EPOCH), INT32_WRAPPER("single_int32_wrapper", NullValue.NULL_VALUE), INT64_WRAPPER("single_int64_wrapper", NullValue.NULL_VALUE), UINT32_WRAPPER("single_uint32_wrapper", NullValue.NULL_VALUE), @@ -239,9 +222,9 @@ private enum DefaultValueTestCase { DOUBLE_WRAPPER("single_double_wrapper", NullValue.NULL_VALUE), STRING_WRAPPER("single_string_wrapper", NullValue.NULL_VALUE), BYTES_WRAPPER("single_bytes_wrapper", NullValue.NULL_VALUE), - REPEATED_INT64("repeated_int64", ImmutableListValue.create(ImmutableList.of())), - MAP_INT64_INT64("map_int64_int64", ImmutableMapValue.create(ImmutableMap.of())), - NESTED_ENUM("standalone_enum", IntValue.create(0L)), + REPEATED_INT64("repeated_int64", ImmutableList.of()), + MAP_INT64_INT64("map_int64_int64", ImmutableMap.of()), + NESTED_ENUM("standalone_enum", 0L), NESTED_MESSAGE( "single_nested_message", ProtoMessageLiteValue.create( @@ -250,11 +233,11 @@ private enum DefaultValueTestCase { PROTO_LITE_CEL_VALUE_CONVERTER)); private final String fieldName; - private final CelValue celValue; + private final Object value; - DefaultValueTestCase(String fieldName, CelValue celValue) { + DefaultValueTestCase(String fieldName, Object value) { this.fieldName = fieldName; - this.celValue = celValue; + this.value = value; } } @@ -266,9 +249,8 @@ public void selectField_defaultValue(@TestParameter DefaultValueTestCase testCas "cel.expr.conformance.proto3.TestAllTypes", PROTO_LITE_CEL_VALUE_CONVERTER); - CelValue selectedValue = protoMessageValue.select(StringValue.create(testCase.fieldName)); + Object selectedValue = protoMessageValue.select(testCase.fieldName); - assertThat(selectedValue).isEqualTo(testCase.celValue); - assertThat(selectedValue.isZeroValue()).isTrue(); + assertThat(selectedValue).isEqualTo(testCase.value); } } diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java index b7e078f81..ed8fc7b2a 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java @@ -65,8 +65,7 @@ public void newValue_createEmptyProtoMessage() { @Test public void newValue_createProtoMessage_fieldsPopulated() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance( - CelOptions.current().build(), DYNAMIC_PROTO); + ProtoMessageValueProvider.newInstance(CelOptions.current().build(), DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -95,24 +94,15 @@ public void newValue_createProtoMessage_fieldsPopulated() { .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); - assertThat(protoMessageValue.select(StringValue.create("single_int32"))) - .isEqualTo(IntValue.create(1L)); - assertThat(protoMessageValue.select(StringValue.create("single_int64"))) - .isEqualTo(IntValue.create(2L)); - assertThat(protoMessageValue.select(StringValue.create("single_uint32"))) - .isEqualTo(UintValue.create(3L)); - assertThat(protoMessageValue.select(StringValue.create("single_uint64"))) - .isEqualTo(UintValue.create(4L)); - assertThat(protoMessageValue.select(StringValue.create("single_double"))) - .isEqualTo(DoubleValue.create(5.5d)); - assertThat(protoMessageValue.select(StringValue.create("single_bool"))) - .isEqualTo(BoolValue.create(true)); - assertThat(protoMessageValue.select(StringValue.create("single_string"))) - .isEqualTo(StringValue.create("hello")); - assertThat(protoMessageValue.select(StringValue.create("single_timestamp"))) - .isEqualTo(TimestampValue.create(Instant.ofEpochSecond(50))); - assertThat(protoMessageValue.select(StringValue.create("single_duration"))) - .isEqualTo(DurationValue.create(Duration.ofSeconds(100))); + assertThat(protoMessageValue.select("single_int32")).isEqualTo(1L); + assertThat(protoMessageValue.select("single_int64")).isEqualTo(2L); + assertThat(protoMessageValue.select("single_uint32")).isEqualTo(UnsignedLong.valueOf(3L)); + assertThat(protoMessageValue.select("single_uint64")).isEqualTo(UnsignedLong.valueOf(4L)); + assertThat(protoMessageValue.select("single_double")).isEqualTo(5.5d); + assertThat(protoMessageValue.select("single_bool")).isEqualTo(true); + assertThat(protoMessageValue.select("single_string")).isEqualTo("hello"); + assertThat(protoMessageValue.select("single_timestamp")).isEqualTo(Instant.ofEpochSecond(50)); + assertThat(protoMessageValue.select("single_duration")).isEqualTo(Duration.ofSeconds(100)); } @Test @@ -130,10 +120,8 @@ public void newValue_createProtoMessage_unsignedLongFieldsPopulated() { .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); - assertThat(protoMessageValue.select(StringValue.create("single_uint32")).value()) - .isEqualTo(UnsignedLong.valueOf(3L)); - assertThat(protoMessageValue.select(StringValue.create("single_uint64")).value()) - .isEqualTo(UnsignedLong.MAX_VALUE); + assertThat(protoMessageValue.select("single_uint32")).isEqualTo(UnsignedLong.valueOf(3L)); + assertThat(protoMessageValue.select("single_uint64")).isEqualTo(UnsignedLong.MAX_VALUE); } @Test @@ -164,22 +152,16 @@ public void newValue_createProtoMessage_wrappersPopulated() { .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); - assertThat(protoMessageValue.select(StringValue.create("single_int32_wrapper")).value()) - .isEqualTo(1L); - assertThat(protoMessageValue.select(StringValue.create("single_int32_wrapper")).value()) - .isEqualTo(1L); - assertThat(protoMessageValue.select(StringValue.create("single_int64_wrapper")).value()) - .isEqualTo(2L); - assertThat(protoMessageValue.select(StringValue.create("single_uint32_wrapper")).value()) + assertThat(protoMessageValue.select("single_int32_wrapper")).isEqualTo(1L); + assertThat(protoMessageValue.select("single_int32_wrapper")).isEqualTo(1L); + assertThat(protoMessageValue.select("single_int64_wrapper")).isEqualTo(2L); + assertThat(protoMessageValue.select("single_uint32_wrapper")) .isEqualTo(UnsignedLong.valueOf(3L)); - assertThat(protoMessageValue.select(StringValue.create("single_uint64_wrapper")).value()) + assertThat(protoMessageValue.select("single_uint64_wrapper")) .isEqualTo(UnsignedLong.valueOf(4L)); - assertThat(protoMessageValue.select(StringValue.create("single_double_wrapper")).value()) - .isEqualTo(5.5d); - assertThat(protoMessageValue.select(StringValue.create("single_bool_wrapper")).value()) - .isEqualTo(true); - assertThat(protoMessageValue.select(StringValue.create("single_string_wrapper")).value()) - .isEqualTo("hello"); + assertThat(protoMessageValue.select("single_double_wrapper")).isEqualTo(5.5d); + assertThat(protoMessageValue.select("single_bool_wrapper")).isEqualTo(true); + assertThat(protoMessageValue.select("single_string_wrapper")).isEqualTo("hello"); } @Test @@ -196,11 +178,7 @@ public void newValue_createProtoMessage_extensionFieldsPopulated() { .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); - assertThat( - protoMessageValue - .select(StringValue.create("cel.expr.conformance.proto2.int32_ext")) - .value()) - .isEqualTo(1); + assertThat(protoMessageValue.select("cel.expr.conformance.proto2.int32_ext")).isEqualTo(1); } @Test @@ -246,7 +224,6 @@ public void newValue_onCombinedProvider() { .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); - assertThat(protoMessageValue.select(StringValue.create("single_int32"))) - .isEqualTo(IntValue.create(1L)); + assertThat(protoMessageValue.select("single_int32")).isEqualTo(1L); } } diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java index f2e5aef5a..ad61f0c4b 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java @@ -92,9 +92,9 @@ public void findField_fieldIsSet_fieldExists() { ProtoMessageValue.create( testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.find(StringValue.create("single_bool"))).isPresent(); - assertThat(protoMessageValue.find(StringValue.create("single_int64"))).isPresent(); - assertThat(protoMessageValue.find(StringValue.create("repeated_int64"))).isPresent(); + assertThat(protoMessageValue.find("single_bool")).isPresent(); + assertThat(protoMessageValue.find("single_int64")).isPresent(); + assertThat(protoMessageValue.find("repeated_int64")).isPresent(); } @Test @@ -105,9 +105,9 @@ public void findField_fieldIsUnset_fieldDoesNotExist() { DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.find(StringValue.create("single_int32"))).isEmpty(); - assertThat(protoMessageValue.find(StringValue.create("single_uint64"))).isEmpty(); - assertThat(protoMessageValue.find(StringValue.create("repeated_int32"))).isEmpty(); + assertThat(protoMessageValue.find("single_int32")).isEmpty(); + assertThat(protoMessageValue.find("single_uint64")).isEmpty(); + assertThat(protoMessageValue.find("repeated_int32")).isEmpty(); } @Test @@ -119,9 +119,7 @@ public void findField_fieldIsUndeclared_throwsException() { PROTO_CEL_VALUE_CONVERTER); IllegalArgumentException exception = - assertThrows( - IllegalArgumentException.class, - () -> protoMessageValue.select(StringValue.create("bogus"))); + assertThrows(IllegalArgumentException.class, () -> protoMessageValue.select("bogus")); assertThat(exception) .hasMessageThat() .isEqualTo( @@ -135,18 +133,13 @@ public void findField_extensionField_success() { DefaultDescriptorPool.create( CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( ImmutableList.of(TestAllTypesExtensions.getDescriptor()))); - ProtoCelValueConverter protoCelValueConverter = - ProtoCelValueConverter.newInstance( - DefaultDescriptorPool.INSTANCE, - DynamicProto.create(DefaultMessageFactory.create(descriptorPool))); TestAllTypes proto2Message = TestAllTypes.newBuilder().setExtension(TestAllTypesExtensions.int32Ext, 1).build(); ProtoMessageValue protoMessageValue = - ProtoMessageValue.create(proto2Message, descriptorPool, protoCelValueConverter); + ProtoMessageValue.create(proto2Message, descriptorPool, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.find(StringValue.create("cel.expr.conformance.proto2.int32_ext"))) - .isPresent(); + assertThat(protoMessageValue.find("cel.expr.conformance.proto2.int32_ext")).isPresent(); } @Test @@ -161,9 +154,7 @@ public void findField_extensionField_throwsWhenDescriptorMissing() { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> - protoMessageValue.select( - StringValue.create("cel.expr.conformance.proto2.int32_ext"))); + () -> protoMessageValue.select("cel.expr.conformance.proto2.int32_ext")); assertThat(exception) .hasMessageThat() .isEqualTo( @@ -171,49 +162,46 @@ public void findField_extensionField_throwsWhenDescriptorMissing() { + " 'cel.expr.conformance.proto2.TestAllTypes'"); } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum SelectFieldTestCase { // Primitives - BOOL("single_bool", BoolValue.create(true)), - INT32("single_int32", IntValue.create(4L)), - INT64("single_int64", IntValue.create(5L)), - UINT32("single_uint32", UintValue.create(UnsignedLong.valueOf(1L))), - UINT64("single_uint64", UintValue.create(UnsignedLong.MAX_VALUE)), - FLOAT("single_float", DoubleValue.create(1.5d)), - DOUBLE("single_double", DoubleValue.create(2.5d)), - STRING("single_string", StringValue.create("test")), - BYTES("single_bytes", BytesValue.create(CelByteString.of(new byte[] {0x01}))), + BOOL("single_bool", true), + INT32("single_int32", 4L), + INT64("single_int64", 5L), + UINT32("single_uint32", UnsignedLong.valueOf(1L)), + UINT64("single_uint64", UnsignedLong.MAX_VALUE), + FLOAT("single_float", 1.5d), + DOUBLE("single_double", 2.5d), + STRING("single_string", "test"), + BYTES("single_bytes", CelByteString.of(new byte[] {0x01})), // Well known types - ANY("single_any", BoolValue.create(true)), - DURATION("single_duration", DurationValue.create(Duration.ofSeconds(100))), - TIMESTAMP("single_timestamp", TimestampValue.create(Instant.ofEpochSecond(100))), - INT32_WRAPPER("single_int32_wrapper", IntValue.create(5L)), - INT64_WRAPPER("single_int64_wrapper", IntValue.create(10L)), - UINT32_WRAPPER("single_uint32_wrapper", UintValue.create(UnsignedLong.valueOf(1L))), - UINT64_WRAPPER("single_uint64_wrapper", UintValue.create(UnsignedLong.MAX_VALUE)), - FLOAT_WRAPPER("single_float_wrapper", DoubleValue.create(7.5d)), - DOUBLE_WRAPPER("single_double_wrapper", DoubleValue.create(8.5d)), - STRING_WRAPPER("single_string_wrapper", StringValue.create("hello")), - BYTES_WRAPPER("single_bytes_wrapper", BytesValue.create(CelByteString.of(new byte[] {0x02}))), - REPEATED_INT64( - "repeated_int64", ImmutableListValue.create(ImmutableList.of(IntValue.create(5L)))), - MAP_STRING_STRING( - "map_string_string", - ImmutableMapValue.create( - ImmutableMap.of(StringValue.create("a"), StringValue.create("b")))), + ANY("single_any", true), + DURATION("single_duration", Duration.ofSeconds(100)), + TIMESTAMP("single_timestamp", Instant.ofEpochSecond(100)), + INT32_WRAPPER("single_int32_wrapper", 5L), + INT64_WRAPPER("single_int64_wrapper", 10L), + UINT32_WRAPPER("single_uint32_wrapper", UnsignedLong.valueOf(1L)), + UINT64_WRAPPER("single_uint64_wrapper", UnsignedLong.MAX_VALUE), + FLOAT_WRAPPER("single_float_wrapper", 7.5d), + DOUBLE_WRAPPER("single_double_wrapper", 8.5d), + STRING_WRAPPER("single_string_wrapper", "hello"), + BYTES_WRAPPER("single_bytes_wrapper", CelByteString.of(new byte[] {0x02})), + REPEATED_INT64("repeated_int64", ImmutableList.of(5L)), + MAP_STRING_STRING("map_string_string", ImmutableMap.of("a", "b")), NESTED_MESSAGE( "standalone_message", ProtoMessageValue.create( NestedMessage.getDefaultInstance(), DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER)), - NESTED_ENUM("standalone_enum", IntValue.create(1L)); + NESTED_ENUM("standalone_enum", 1L); private final String fieldName; - private final CelValue celValue; + private final Object value; - SelectFieldTestCase(String fieldName, CelValue celValue) { + SelectFieldTestCase(String fieldName, Object value) { this.fieldName = fieldName; - this.celValue = celValue; + this.value = value; } } @@ -253,8 +241,7 @@ public void selectField_success(@TestParameter SelectFieldTestCase testCase) { ProtoMessageValue.create( testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create(testCase.fieldName))) - .isEqualTo(testCase.celValue); + assertThat(protoMessageValue.select(testCase.fieldName)).isEqualTo(testCase.value); } @Test @@ -268,8 +255,7 @@ public void selectField_dynamicMessage_success() { DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create("single_int32_wrapper"))) - .isEqualTo(IntValue.create(5)); + assertThat(protoMessageValue.select("single_int32_wrapper")).isEqualTo(5); } @Test @@ -285,8 +271,8 @@ public void selectField_timestampNanosOutOfRange_success(int nanos) { ProtoMessageValue.create( testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create("single_timestamp"))) - .isEqualTo(TimestampValue.create(Instant.ofEpochSecond(0, nanos))); + assertThat(protoMessageValue.select("single_timestamp")) + .isEqualTo(Instant.ofEpochSecond(0, nanos)); } @Test @@ -305,15 +291,16 @@ public void selectField_durationOutOfRange_success(int seconds, int nanos) { ProtoMessageValue.create( testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create("single_duration"))) - .isEqualTo(DurationValue.create(Duration.ofSeconds(seconds, nanos))); + assertThat(protoMessageValue.select("single_duration")) + .isEqualTo(Duration.ofSeconds(seconds, nanos)); } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum SelectFieldJsonValueTestCase { NULL(Value.newBuilder().build(), NullValue.NULL_VALUE), - BOOL(Value.newBuilder().setBoolValue(true).build(), BoolValue.create(true)), - DOUBLE(Value.newBuilder().setNumberValue(4.5d).build(), DoubleValue.create(4.5d)), - STRING(Value.newBuilder().setStringValue("test").build(), StringValue.create("test")), + BOOL(Value.newBuilder().setBoolValue(true).build(), true), + DOUBLE(Value.newBuilder().setNumberValue(4.5d).build(), 4.5d), + STRING(Value.newBuilder().setStringValue("test").build(), "test"), STRUCT( Value.newBuilder() .setStructValue( @@ -321,8 +308,7 @@ private enum SelectFieldJsonValueTestCase { .putFields("a", Value.newBuilder().setBoolValue(false).build()) .build()) .build(), - ImmutableMapValue.create( - ImmutableMap.of(StringValue.create("a"), BoolValue.create(false)))), + ImmutableMap.of("a", false)), LIST( Value.newBuilder() .setListValue( @@ -330,14 +316,14 @@ private enum SelectFieldJsonValueTestCase { .addValues(Value.newBuilder().setStringValue("test").build()) .build()) .build(), - ImmutableListValue.create(ImmutableList.of(StringValue.create("test")))); + ImmutableList.of("test")); private final Value jsonValue; - private final CelValue celValue; + private final Object value; - SelectFieldJsonValueTestCase(Value jsonValue, CelValue celValue) { + SelectFieldJsonValueTestCase(Value jsonValue, Object value) { this.jsonValue = jsonValue; - this.celValue = celValue; + this.value = value; } } @@ -350,8 +336,7 @@ public void selectField_jsonValue(@TestParameter SelectFieldJsonValueTestCase te ProtoMessageValue.create( testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create("single_value"))) - .isEqualTo(testCase.celValue); + assertThat(protoMessageValue.select("single_value")).isEqualTo(testCase.value); } @Test @@ -368,10 +353,7 @@ public void selectField_jsonStruct() { ProtoMessageValue.create( testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create("single_struct"))) - .isEqualTo( - ImmutableMapValue.create( - ImmutableMap.of(StringValue.create("a"), BoolValue.create(false)))); + assertThat(protoMessageValue.select("single_struct")).isEqualTo(ImmutableMap.of("a", false)); } @Test @@ -388,8 +370,7 @@ public void selectField_jsonList() { ProtoMessageValue.create( testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create("list_value"))) - .isEqualTo(ImmutableListValue.create(ImmutableList.of(BoolValue.create(false)))); + assertThat(protoMessageValue.select("list_value")).isEqualTo(ImmutableList.of(false)); } @Test @@ -400,8 +381,7 @@ public void selectField_wrapperFieldUnset_returnsNull() { DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); - assertThat(protoMessageValue.select(StringValue.create("single_int64_wrapper"))) - .isEqualTo(NullValue.NULL_VALUE); + assertThat(protoMessageValue.select("single_int64_wrapper")).isEqualTo(NullValue.NULL_VALUE); } @Test diff --git a/common/src/test/java/dev/cel/common/values/StringValueTest.java b/common/src/test/java/dev/cel/common/values/StringValueTest.java deleted file mode 100644 index 6a0a8113c..000000000 --- a/common/src/test/java/dev/cel/common/values/StringValueTest.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class StringValueTest { - - @Test - public void emptyString() { - StringValue stringValue = StringValue.create(""); - - assertThat(stringValue.value()).isEmpty(); - assertThat(stringValue.isZeroValue()).isTrue(); - } - - @Test - public void blankString() { - StringValue stringValue = StringValue.create(" "); - - assertThat(stringValue.value()).isEqualTo(" "); - assertThat(stringValue.isZeroValue()).isFalse(); - } - - @Test - public void constructString() { - StringValue stringValue = StringValue.create("Hello World"); - - assertThat(stringValue.value()).isEqualTo("Hello World"); - assertThat(stringValue.isZeroValue()).isFalse(); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> StringValue.create(null)); - } - - @Test - public void celTypeTest() { - StringValue value = StringValue.create(""); - - assertThat(value.celType()).isEqualTo(SimpleType.STRING); - } -} diff --git a/common/src/test/java/dev/cel/common/values/StructValueTest.java b/common/src/test/java/dev/cel/common/values/StructValueTest.java index 1ab1137e0..1db147882 100644 --- a/common/src/test/java/dev/cel/common/values/StructValueTest.java +++ b/common/src/test/java/dev/cel/common/values/StructValueTest.java @@ -87,15 +87,14 @@ public void constructStruct() { public void selectField_success() { CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); - assertThat(celCustomStruct.select(StringValue.create("data"))).isEqualTo(IntValue.create(5L)); + assertThat(celCustomStruct.select("data")).isEqualTo(5L); } @Test public void selectField_nonExistentField_throws() { CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); - assertThrows( - IllegalArgumentException.class, () -> celCustomStruct.select(StringValue.create("bogus"))); + assertThrows(IllegalArgumentException.class, () -> celCustomStruct.select("bogus")); } @Test @@ -104,8 +103,7 @@ public void selectField_nonExistentField_throws() { public void findField_success(String fieldName, boolean expectedResult) { CelCustomStructValue celCustomStruct = new CelCustomStructValue(5); - assertThat(celCustomStruct.find(StringValue.create(fieldName)).isPresent()) - .isEqualTo(expectedResult); + assertThat(celCustomStruct.find(fieldName).isPresent()).isEqualTo(expectedResult); } @Test @@ -229,7 +227,7 @@ public void evaluate_usingMultipleProviders_selectFieldFromProtobufMessage() thr } @SuppressWarnings("Immutable") // Test only - private static class CelCustomStructValue extends StructValue { + private static class CelCustomStructValue extends StructValue { private final long data; @@ -249,15 +247,15 @@ public CelType celType() { } @Override - public CelValue select(StringValue field) { + public Object select(String field) { return find(field) .orElseThrow(() -> new IllegalArgumentException("Invalid field name: " + field)); } @Override - public Optional find(StringValue field) { - if (field.value().equals("data")) { - return Optional.of(IntValue.create(value().data)); + public Optional find(String field) { + if (field.equals("data")) { + return Optional.of(value().data); } return Optional.empty(); diff --git a/common/src/test/java/dev/cel/common/values/TimestampValueTest.java b/common/src/test/java/dev/cel/common/values/TimestampValueTest.java deleted file mode 100644 index c20466533..000000000 --- a/common/src/test/java/dev/cel/common/values/TimestampValueTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import dev.cel.common.types.SimpleType; -import java.time.Instant; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class TimestampValueTest { - - @Test - public void emptyTimestamp() { - TimestampValue timestampValue = TimestampValue.create(Instant.ofEpochSecond(0)); - - assertThat(timestampValue.value()).isEqualTo(Instant.EPOCH); - assertThat(timestampValue.isZeroValue()).isTrue(); - } - - @Test - public void constructTimestamp() { - TimestampValue timestampValue = TimestampValue.create(Instant.ofEpochMilli(100000)); - - assertThat(timestampValue.value()).isEqualTo(Instant.ofEpochMilli(100000)); - assertThat(timestampValue.isZeroValue()).isFalse(); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> TimestampValue.create(null)); - } - - @Test - public void celTypeTest() { - TimestampValue value = TimestampValue.create(Instant.ofEpochSecond(0)); - - assertThat(value.celType()).isEqualTo(SimpleType.TIMESTAMP); - } -} diff --git a/common/src/test/java/dev/cel/common/values/TypeValueTest.java b/common/src/test/java/dev/cel/common/values/TypeValueTest.java deleted file mode 100644 index 7f8ab3eb7..000000000 --- a/common/src/test/java/dev/cel/common/values/TypeValueTest.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import dev.cel.common.types.SimpleType; -import dev.cel.common.types.TypeType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class TypeValueTest { - - @Test - public void constructTypeValue() { - TypeValue typeValue = TypeValue.create(SimpleType.INT); - - assertThat(typeValue.value()).isEqualTo(SimpleType.INT); - assertThat(typeValue.isZeroValue()).isFalse(); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> TypeValue.create(null)); - } - - @Test - public void celTypeTest() { - TypeValue value = TypeValue.create(SimpleType.INT); - - assertThat(value.celType()).isEqualTo(TypeType.create(SimpleType.INT)); - } -} diff --git a/common/src/test/java/dev/cel/common/values/UintValueTest.java b/common/src/test/java/dev/cel/common/values/UintValueTest.java deleted file mode 100644 index 2d073b461..000000000 --- a/common/src/test/java/dev/cel/common/values/UintValueTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 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.common.values; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import com.google.common.primitives.UnsignedLong; -import dev.cel.common.types.SimpleType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class UintValueTest { - - @Test - public void emptyUint() { - UintValue uintValue = UintValue.create(UnsignedLong.valueOf(0L)); - - assertThat(uintValue.value()).isEqualTo(UnsignedLong.valueOf(0L)); - assertThat(uintValue.isZeroValue()).isTrue(); - } - - @Test - public void constructUint() { - UintValue uintValue = UintValue.create(UnsignedLong.valueOf(5L)); - - assertThat(uintValue.value()).isEqualTo(UnsignedLong.valueOf(5L)); - assertThat(uintValue.isZeroValue()).isFalse(); - } - - @Test - public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> UintValue.create(null)); - } - - @Test - public void celTypeTest() { - UintValue value = UintValue.create(UnsignedLong.valueOf(0L)); - - assertThat(value.celType()).isEqualTo(SimpleType.UINT); - } -} diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java index cf53844c0..989d3b7be 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java @@ -27,7 +27,6 @@ import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.CombinedCelValueProvider; import dev.cel.common.values.SelectableValue; -import dev.cel.common.values.StringValue; import java.util.Map; import java.util.NoSuchElementException; @@ -39,13 +38,7 @@ final class CelValueRuntimeTypeProvider implements RuntimeTypeProvider { private final CelValueProvider valueProvider; private final BaseProtoCelValueConverter protoCelValueConverter; private static final BaseProtoCelValueConverter DEFAULT_CEL_VALUE_CONVERTER = - new BaseProtoCelValueConverter() { - @Override - public CelValue fromProtoMessageToCelValue(MessageLite msg) { - throw new UnsupportedOperationException( - "A value provider must be provided in the runtime to handle protobuf messages"); - } - }; + new BaseProtoCelValueConverter() {}; static CelValueRuntimeTypeProvider newInstance(CelValueProvider valueProvider) { BaseProtoCelValueConverter converter = DEFAULT_CEL_VALUE_CONVERTER; @@ -72,7 +65,7 @@ static CelValueRuntimeTypeProvider newInstance(CelValueProvider valueProvider) { @Override public Object createMessage(String messageName, Map values) { - return unwrapCelValue( + return maybeUnwrapCelValue( valueProvider .newValue(messageName, values) .orElseThrow( @@ -83,32 +76,39 @@ public Object createMessage(String messageName, Map values) { @Override public Object selectField(Object message, String fieldName) { - SelectableValue selectableValue = getSelectableValueOrThrow(message, fieldName); + if (message instanceof Map) { + Map map = (Map) message; + if (map.containsKey(fieldName)) { + return map.get(fieldName); + } + + throw new CelRuntimeException( + new IllegalArgumentException(String.format("key '%s' is not present in map.", fieldName)), + CelErrorCode.ATTRIBUTE_NOT_FOUND); + } + + SelectableValue selectableValue = getSelectableValueOrThrow(message, fieldName); + Object value = selectableValue.select(fieldName); - return unwrapCelValue(selectableValue.select(StringValue.create(fieldName))); + return maybeUnwrapCelValue(value); } @Override public Object hasField(Object message, String fieldName) { - SelectableValue selectableValue = getSelectableValueOrThrow(message, fieldName); + SelectableValue selectableValue = getSelectableValueOrThrow(message, fieldName); - return selectableValue.find(StringValue.create(fieldName)).isPresent(); + return selectableValue.find(fieldName).isPresent(); } @SuppressWarnings("unchecked") - private SelectableValue getSelectableValueOrThrow(Object obj, String fieldName) { - CelValue convertedCelValue; - if ((obj instanceof MessageLite)) { - convertedCelValue = protoCelValueConverter.fromProtoMessageToCelValue((MessageLite) obj); - } else { - convertedCelValue = protoCelValueConverter.fromJavaObjectToCelValue(obj); - } + private SelectableValue getSelectableValueOrThrow(Object obj, String fieldName) { + Object convertedCelValue = protoCelValueConverter.toRuntimeValue(obj); if (!(convertedCelValue instanceof SelectableValue)) { throwInvalidFieldSelection(fieldName); } - return (SelectableValue) convertedCelValue; + return (SelectableValue) convertedCelValue; } @Override @@ -123,11 +123,10 @@ public Object adapt(String messageName, Object message) { } if (message instanceof MessageLite) { - return unwrapCelValue( - protoCelValueConverter.fromProtoMessageToCelValue((MessageLite) message)); - } else { - return unwrapCelValue(protoCelValueConverter.fromJavaObjectToCelValue(message)); + return maybeUnwrapCelValue(protoCelValueConverter.toRuntimeValue(message)); } + + return message; } /** @@ -135,8 +134,11 @@ public Object adapt(String messageName, Object message) { * *

    This will become unnecessary once we introduce a rewrite of a Cel runtime. */ - private Object unwrapCelValue(CelValue object) { - return protoCelValueConverter.fromCelValueToJavaObject(object); + private Object maybeUnwrapCelValue(Object object) { + if (object instanceof CelValue) { + return protoCelValueConverter.unwrap((CelValue) object); + } + return object; } private static void throwInvalidFieldSelection(String fieldName) { From c883c311ba8539f8ac3fc2b6b2d1b15d583b852d Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 19 Nov 2025 12:15:56 -0800 Subject: [PATCH 022/100] Plan CreateMap PiperOrigin-RevId: 834393778 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 14 ++++ .../cel/runtime/planner/EvalCreateMap.java | 77 +++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 19 +++++ .../runtime/planner/ProgramPlannerTest.java | 11 +++ 4 files changed, 121 insertions(+) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 4a550ede8..43ef1965e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,6 +17,7 @@ java_library( ":eval_attribute", ":eval_const", ":eval_create_list", + ":eval_create_map", ":planned_program", "//:auto_value", "//common:cel_ast", @@ -104,3 +105,16 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_library( + name = "eval_create_map", + srcs = ["EvalCreateMap.java"], + deps = [ + "//runtime", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java new file mode 100644 index 000000000..a3fb2cb85 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -0,0 +1,77 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +@Immutable +final class EvalCreateMap implements Interpretable { + + // Array contents are not mutated + @SuppressWarnings("Immutable") + private final Interpretable[] keys; + + // Array contents are not mutated + @SuppressWarnings("Immutable") + private final Interpretable[] values; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + ImmutableMap.Builder builder = + ImmutableMap.builderWithExpectedSize(keys.length); + for (int i = 0; i < keys.length; i++) { + builder.put(keys[i].eval(resolver), values[i].eval(resolver)); + } + return builder.buildOrThrow(); + } + + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + + static EvalCreateMap create(Interpretable[] keys, Interpretable[] values) { + return new EvalCreateMap(keys, values); + } + + private EvalCreateMap(Interpretable[] keys, Interpretable[] values) { + Preconditions.checkArgument(keys.length == values.length); + this.keys = keys; + this.values = values; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 80ffdab6a..6f1f122d4 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -24,6 +24,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelList; +import dev.cel.common.ast.CelExpr.CelMap; import dev.cel.common.ast.CelReference; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; @@ -69,6 +70,8 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { return planIdent(celExpr, ctx); case LIST: return planCreateList(celExpr, ctx); + case MAP: + return planCreateMap(celExpr, ctx); case NOT_SET: throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); default: @@ -141,6 +144,22 @@ private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { return EvalCreateList.create(values); } + private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { + CelMap map = celExpr.map(); + + ImmutableList entries = map.entries(); + Interpretable[] keys = new Interpretable[entries.size()]; + Interpretable[] values = new Interpretable[entries.size()]; + + for (int i = 0; i < entries.size(); i++) { + CelMap.Entry entry = entries.get(i); + keys[i] = plan(entry.key(), ctx); + values[i] = plan(entry.value(), ctx); + } + + return EvalCreateMap.create(keys, values); + } + @AutoValue abstract static class PlannerContext { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index a761e4925..8d119c3c3 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -124,6 +124,17 @@ public void plan_createList() throws Exception { assertThat(result).containsExactly(1L, "foo", true, ImmutableList.of(2L, false)).inOrder(); } + @Test + @SuppressWarnings("unchecked") // test only + public void plan_createMap() throws Exception { + CelAbstractSyntaxTree ast = compile("{'foo': 1, true: 'bar'}"); + Program program = PLANNER.plan(ast); + + ImmutableMap result = (ImmutableMap) program.eval(); + + assertThat(result).containsExactly("foo", 1L, true, "bar").inOrder(); + } + @Test public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { if (isParseOnly) { From ceece69fcf30896fc54ebdaf30aad32bcc0ec363 Mon Sep 17 00:00:00 2001 From: Stephen Roberts Date: Fri, 21 Nov 2025 03:07:33 -0800 Subject: [PATCH 023/100] Update CelBindingsExtensions class visibility to public PiperOrigin-RevId: 835147547 --- .../main/java/dev/cel/extensions/CelBindingsExtensions.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java index 592c198b2..5eb2c2e8c 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelBindingsExtensions.java @@ -31,7 +31,8 @@ /** Internal implementation of the CEL local binding extensions. */ @Immutable -final class CelBindingsExtensions implements CelCompilerLibrary, CelExtensionLibrary.FeatureSet { +public final class CelBindingsExtensions + implements CelCompilerLibrary, CelExtensionLibrary.FeatureSet { private static final String CEL_NAMESPACE = "cel"; private static final String UNUSED_ITER_VAR = "#unused"; From ff4174a07341cef2c0c9f6bcafaba09b653d9ca2 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 21 Nov 2025 11:55:00 -0800 Subject: [PATCH 024/100] Plan CreateStruct PiperOrigin-RevId: 835306497 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 18 +++ .../cel/runtime/planner/EvalCreateStruct.java | 104 ++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 41 ++++++- .../java/dev/cel/runtime/planner/BUILD.bazel | 7 ++ .../runtime/planner/ProgramPlannerTest.java | 81 +++++++++++--- 5 files changed, 234 insertions(+), 17 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 43ef1965e..a0423310c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -18,6 +18,7 @@ java_library( ":eval_const", ":eval_create_list", ":eval_create_map", + ":eval_create_struct", ":planned_program", "//:auto_value", "//common:cel_ast", @@ -26,6 +27,7 @@ java_library( "//common/ast", "//common/types", "//common/types:type_providers", + "//common/values:cel_value_provider", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", "//runtime:interpretable", @@ -93,6 +95,22 @@ java_library( ], ) +java_library( + name = "eval_create_struct", + srcs = ["EvalCreateStruct.java"], + deps = [ + "//common/types", + "//common/values", + "//common/values:cel_value_provider", + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "eval_create_list", srcs = ["EvalCreateList.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java new file mode 100644 index 000000000..5c1f1d77b --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -0,0 +1,104 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.StructType; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.StructValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Immutable +final class EvalCreateStruct implements Interpretable { + + private final CelValueProvider valueProvider; + private final StructType structType; + + // Array contents are not mutated + @SuppressWarnings("Immutable") + private final String[] keys; + + // Array contents are not mutated + @SuppressWarnings("Immutable") + private final Interpretable[] values; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + Map fieldValues = new HashMap<>(); + for (int i = 0; i < keys.length; i++) { + Object value = values[i].eval(resolver); + fieldValues.put(keys[i], value); + } + + // Either a primitive (wrappers) or a struct is produced + Object value = + valueProvider + .newValue(structType.name(), Collections.unmodifiableMap(fieldValues)) + .orElseThrow(() -> new IllegalArgumentException("Type name not found: " + structType)); + + if (value instanceof StructValue) { + return ((StructValue) value).value(); + } + + return value; + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalCreateStruct create( + CelValueProvider valueProvider, + StructType structType, + String[] keys, + Interpretable[] values) { + return new EvalCreateStruct(valueProvider, structType, keys, values); + } + + private EvalCreateStruct( + CelValueProvider valueProvider, + StructType structType, + String[] keys, + Interpretable[] values) { + this.valueProvider = valueProvider; + this.structType = structType; + this.keys = keys; + this.values = values; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 6f1f122d4..ec71155d6 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -25,11 +25,15 @@ import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelExpr.CelMap; +import dev.cel.common.ast.CelExpr.CelStruct; +import dev.cel.common.ast.CelExpr.CelStruct.Entry; import dev.cel.common.ast.CelReference; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.StructType; import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValueProvider; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.Interpretable; @@ -45,6 +49,7 @@ public final class ProgramPlanner { private final CelTypeProvider typeProvider; + private final CelValueProvider valueProvider; private final AttributeFactory attributeFactory; /** @@ -70,6 +75,8 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { return planIdent(celExpr, ctx); case LIST: return planCreateList(celExpr, ctx); + case STRUCT: + return planCreateStruct(celExpr, ctx); case MAP: return planCreateMap(celExpr, ctx); case NOT_SET: @@ -131,6 +138,32 @@ private Interpretable planCheckedIdent( return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name())); } + private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { + CelStruct struct = celExpr.struct(); + CelType structType = + typeProvider + .findType(struct.messageName()) + .orElseThrow( + () -> new IllegalArgumentException("Undefined type name: " + struct.messageName())); + if (!structType.kind().equals(CelKind.STRUCT)) { + throw new IllegalArgumentException( + String.format( + "Expected struct type for %s, got %s", structType.name(), structType.kind())); + } + + ImmutableList entries = struct.entries(); + String[] keys = new String[entries.size()]; + Interpretable[] values = new Interpretable[entries.size()]; + + for (int i = 0; i < entries.size(); i++) { + Entry entry = entries.get(i); + keys[i] = entry.fieldKey(); + values[i] = plan(entry.value(), ctx); + } + + return EvalCreateStruct.create(valueProvider, (StructType) structType, keys, values); + } + private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { CelList list = celExpr.list(); @@ -172,12 +205,14 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) { } } - public static ProgramPlanner newPlanner(CelTypeProvider typeProvider) { - return new ProgramPlanner(typeProvider); + public static ProgramPlanner newPlanner( + CelTypeProvider typeProvider, CelValueProvider valueProvider) { + return new ProgramPlanner(typeProvider, valueProvider); } - private ProgramPlanner(CelTypeProvider typeProvider) { + private ProgramPlanner(CelTypeProvider typeProvider, CelValueProvider valueProvider) { this.typeProvider = typeProvider; + this.valueProvider = valueProvider; // TODO: Container support this.attributeFactory = AttributeFactory.newAttributeFactory(CelContainer.newBuilder().build(), typeProvider); diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 46ef0f279..0b36e20ac 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -15,14 +15,21 @@ java_library( deps = [ "//:java_truth", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_source", + "//common:options", "//common/ast", + "//common/internal:cel_descriptor_pools", + "//common/internal:default_message_factory", + "//common/internal:dynamic_proto", "//common/types", "//common/types:default_type_provider", "//common/types:message_type_provider", "//common/types:type_providers", "//common/values", "//common/values:cel_byte_string", + "//common/values:cel_value_provider", + "//common/values:proto_message_value_provider", "//compiler", "//compiler:compiler_builder", "//extensions", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 8d119c3c3..32651d73d 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -25,8 +25,14 @@ import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelOptions; import dev.cel.common.CelSource; import dev.cel.common.ast.CelExpr; +import dev.cel.common.internal.CelDescriptorPool; +import dev.cel.common.internal.DefaultDescriptorPool; +import dev.cel.common.internal.DefaultMessageFactory; +import dev.cel.common.internal.DynamicProto; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.CelTypeProvider.CombinedCelTypeProvider; @@ -38,7 +44,9 @@ import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.GlobalEnum; @@ -56,8 +64,17 @@ public final class ProgramPlannerTest { new CombinedCelTypeProvider( DefaultTypeProvider.getInstance(), new ProtoMessageTypeProvider(ImmutableSet.of(TestAllTypes.getDescriptor()))); - - private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner(TYPE_PROVIDER); + private static final CelDescriptorPool DESCRIPTOR_POOL = + DefaultDescriptorPool.create( + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + TestAllTypes.getDescriptor().getFile())); + private static final DynamicProto DYNAMIC_PROTO = + DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)); + private static final CelValueProvider VALUE_PROVIDER = + ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); + + private static final ProgramPlanner PLANNER = + ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() .addVar("int_var", SimpleType.INT) @@ -113,6 +130,24 @@ public void plan_ident_variable() throws Exception { assertThat(result).isEqualTo(1); } + @Test + public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { + if (isParseOnly) { + if (testCase.equals(TypeLiteralTestCase.DURATION) + || testCase.equals(TypeLiteralTestCase.TIMESTAMP) + || testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) { + // TODO Skip for now, requires attribute qualification + return; + } + } + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + TypeType result = (TypeType) program.eval(); + + assertThat(result).isEqualTo(testCase.type); + } + @Test @SuppressWarnings("unchecked") // test only public void plan_createList() throws Exception { @@ -136,21 +171,39 @@ public void plan_createMap() throws Exception { } @Test - public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { - if (isParseOnly) { - if (testCase.equals(TypeLiteralTestCase.DURATION) - || testCase.equals(TypeLiteralTestCase.TIMESTAMP) - || testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) { - // TODO Skip for now, requires attribute qualification - return; - } - } - CelAbstractSyntaxTree ast = compile(testCase.expression); + public void plan_createStruct() throws Exception { + CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{}"); Program program = PLANNER.plan(ast); - TypeType result = (TypeType) program.eval(); + TestAllTypes result = (TestAllTypes) program.eval(); - assertThat(result).isEqualTo(testCase.type); + assertThat(result).isEqualTo(TestAllTypes.getDefaultInstance()); + } + + @Test + public void plan_createStruct_wrapper() throws Exception { + CelAbstractSyntaxTree ast = compile("google.protobuf.StringValue { value: 'foo' }"); + Program program = PLANNER.plan(ast); + + String result = (String) program.eval(); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void planCreateStruct_withFields() throws Exception { + CelAbstractSyntaxTree ast = + compile( + "cel.expr.conformance.proto3.TestAllTypes{" + + "single_string: 'foo'," + + "single_bool: true" + + "}"); + Program program = PLANNER.plan(ast); + + TestAllTypes result = (TestAllTypes) program.eval(); + + assertThat(result) + .isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").setSingleBool(true).build()); } private CelAbstractSyntaxTree compile(String expression) throws Exception { From d7ad94d02c982fd465fcf472802a5be87766ff18 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 21 Nov 2025 13:32:55 -0800 Subject: [PATCH 025/100] Fix a thread safety issue with CelParser's toBuilder PiperOrigin-RevId: 835341182 --- .../java/dev/cel/parser/CelParserImpl.java | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/parser/src/main/java/dev/cel/parser/CelParserImpl.java b/parser/src/main/java/dev/cel/parser/CelParserImpl.java index 2624731d8..615b073ef 100644 --- a/parser/src/main/java/dev/cel/parser/CelParserImpl.java +++ b/parser/src/main/java/dev/cel/parser/CelParserImpl.java @@ -15,8 +15,10 @@ package dev.cel.parser; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.stream.Collectors.toCollection; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -32,9 +34,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; /** * Modernized parser implementation for CEL. @@ -54,9 +58,10 @@ public final class CelParserImpl implements CelParser, EnvVisitable { // Specific options for limits on parsing power. private final CelOptions options; - // Builder is mutable by design. APIs must make defensive copies in and out of this class. - @SuppressWarnings("Immutable") - private final Builder parserBuilder; + private final ImmutableList standardMacros; + + @SuppressWarnings("Immutable") // Interface not marked as immutable, however it should be. + private final ImmutableSet parserLibraries; /** Creates a new {@link Builder}. */ public static CelParserBuilder newBuilder() { @@ -75,7 +80,20 @@ public CelValidationResult parse(CelSource source) { @Override public CelParserBuilder toParserBuilder() { - return new Builder(parserBuilder); + HashSet standardMacroKeys = + standardMacros.stream() + .map(s -> s.getDefinition().getKey()) + .collect(Collectors.toCollection(HashSet::new)); + + return new Builder() + .setOptions(options) + .setStandardMacros(standardMacros) + .addMacros( + // Separate standard macros from the custom macros before constructing the builder + macros.values().stream() + .filter(m -> !standardMacroKeys.contains(m.getKey())) + .collect(toCollection(ArrayList::new))) + .addLibraries(parserLibraries); } Optional findMacro(String key) { @@ -175,12 +193,17 @@ public CelParserImpl build() { // Add libraries, such as extensions parserLibrarySet.forEach(celLibrary -> celLibrary.setParserOptions(this)); - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.putAll(macros); + ImmutableMap.Builder macroMapBuilder = ImmutableMap.builder(); + macroMapBuilder.putAll(macros); standardMacros.stream() .map(CelStandardMacro::getDefinition) - .forEach(celMacro -> builder.put(celMacro.getKey(), celMacro)); - return new CelParserImpl(builder.buildOrThrow(), checkNotNull(options), this); + .forEach(celMacro -> macroMapBuilder.put(celMacro.getKey(), celMacro)); + + return new CelParserImpl( + macroMapBuilder.buildOrThrow(), + options, + ImmutableList.copyOf(standardMacros), + celParserLibraries.build()); } private Builder() { @@ -188,24 +211,17 @@ private Builder() { this.celParserLibraries = ImmutableSet.builder(); this.standardMacros = new ArrayList<>(); } - - private Builder(Builder builder) { - // The following properties are either immutable or simple primitives, thus can be assigned - // directly. - this.options = builder.options; - // The following needs to be deep copied as they are collection builders - this.macros = new HashMap<>(builder.macros); - this.standardMacros = new ArrayList<>(builder.standardMacros); - this.celParserLibraries = ImmutableSet.builder(); - this.celParserLibraries.addAll(builder.celParserLibraries.build()); - } } private CelParserImpl( - ImmutableMap macros, CelOptions options, Builder parserBuilder) { + ImmutableMap macros, + CelOptions options, + ImmutableList standardMacros, + ImmutableSet parserLibraries) { this.macros = macros; - this.options = options; - this.parserBuilder = new Builder(parserBuilder); + this.options = checkNotNull(options); + this.standardMacros = standardMacros; + this.parserLibraries = parserLibraries; } @Override From aa9f794ed355acb11d4da9db4c2aa7a9dd59b029 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 24 Nov 2025 15:37:38 -0800 Subject: [PATCH 026/100] Remove extraneous ResolvedOverload in lieu of CelResolvedOverload PiperOrigin-RevId: 836386782 --- runtime/BUILD.bazel | 6 -- .../src/main/java/dev/cel/runtime/BUILD.bazel | 61 ++++--------- .../dev/cel/runtime/CelFunctionResolver.java | 2 +- .../cel/runtime/CelLateFunctionBindings.java | 8 +- .../dev/cel/runtime/CelResolvedOverload.java | 45 ++++++++-- .../dev/cel/runtime/DefaultDispatcher.java | 22 ++--- .../dev/cel/runtime/DefaultInterpreter.java | 8 +- .../dev/cel/runtime/ResolvedOverload.java | 88 ------------------- .../src/test/java/dev/cel/runtime/BUILD.bazel | 1 - .../runtime/CelLateFunctionBindingsTest.java | 12 +-- 10 files changed, 82 insertions(+), 171 deletions(-) delete mode 100644 runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 251d45650..f3b60d4d7 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -227,12 +227,6 @@ cel_android_library( exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_android"], ) -java_library( - name = "resolved_overload_internal", - visibility = ["//:internal"], - exports = ["//runtime/src/main/java/dev/cel/runtime:resolved_overload_internal"], -) - java_library( name = "internal_function_binder", visibility = ["//:internal"], diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 56f9a7bd2..98d6244aa 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -124,11 +124,10 @@ java_library( ":evaluation_exception", ":evaluation_exception_builder", ":function_overload", + ":function_resolver", ":resolved_overload", - ":resolved_overload_internal", "//:auto_value", "//common:error_codes", - "//runtime:function_resolver", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -145,7 +144,6 @@ cel_android_library( ":function_overload_android", ":function_resolver_android", ":resolved_overload_android", - ":resolved_overload_internal_android", "//:auto_value", "//common:error_codes", "@maven//:com_google_code_findbugs_annotations", @@ -285,10 +283,11 @@ java_library( ":evaluation_exception", ":evaluation_exception_builder", ":evaluation_listener", + ":function_resolver", ":interpretable", ":interpreter_util", ":metadata", - ":resolved_overload_internal", + ":resolved_overload", ":runtime_helpers", ":runtime_type_provider", ":type_resolver", @@ -303,7 +302,6 @@ java_library( "//common/types", "//common/types:type_providers", "//common/values:cel_byte_string", - "//runtime:function_resolver", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -328,7 +326,7 @@ cel_android_library( ":interpretable_android", ":interpreter_util_android", ":metadata", - ":resolved_overload_internal_android", + ":resolved_overload_android", ":runtime_helpers_android", ":runtime_type_provider_android", ":type_resolver_android", @@ -498,7 +496,6 @@ java_library( ":function_binding", ":function_resolver", ":resolved_overload", - ":resolved_overload_internal", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -516,7 +513,6 @@ cel_android_library( ":function_binding_android", ":function_resolver_android", ":resolved_overload_android", - ":resolved_overload_internal_android", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -584,8 +580,8 @@ java_library( deps = [ ":evaluation_exception", ":evaluation_listener", + ":function_resolver", "//common/annotations", - "//runtime:function_resolver", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", ], @@ -752,7 +748,7 @@ java_library( ], deps = [ ":evaluation_exception", - ":resolved_overload_internal", + ":resolved_overload", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -763,7 +759,7 @@ cel_android_library( srcs = ["CelFunctionResolver.java"], deps = [ ":evaluation_exception", - ":resolved_overload_internal_android", + ":resolved_overload_android", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -793,33 +789,6 @@ cel_android_library( ], ) -java_library( - name = "resolved_overload_internal", - srcs = ["ResolvedOverload.java"], - tags = [ - ], - deps = [ - ":function_overload", - ":unknown_attributes", - "@maven//:com_google_code_findbugs_annotations", - "@maven//:com_google_errorprone_error_prone_annotations", - "@maven//:com_google_protobuf_protobuf_java", - ], -) - -cel_android_library( - name = "resolved_overload_internal_android", - srcs = ["ResolvedOverload.java"], - visibility = ["//visibility:private"], - deps = [ - ":function_overload_android", - ":unknown_attributes_android", - "@maven//:com_google_code_findbugs_annotations", - "@maven//:com_google_errorprone_error_prone_annotations", - "@maven_android//:com_google_protobuf_protobuf_javalite", - ], -) - java_library( name = "runtime", srcs = RUNTIME_SOURCES, @@ -837,6 +806,7 @@ java_library( ":function_resolver", ":interpretable", ":interpreter", + ":program", ":proto_message_activation_factory", ":proto_message_runtime_equality", ":runtime_equality", @@ -856,7 +826,6 @@ java_library( "//common/types:cel_types", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", - "//runtime:program", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -873,12 +842,12 @@ java_library( deps = [ ":evaluation_exception", ":function_binding", + ":program", "//:auto_value", "//common:cel_ast", "//common:options", "//common/annotations", "//common/values:cel_value_provider", - "//runtime:program", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -898,6 +867,7 @@ java_library( ":interpreter", ":lite_program_impl", ":lite_runtime", + ":program", ":runtime_equality", ":runtime_helpers", ":type_resolver", @@ -905,7 +875,6 @@ java_library( "//common:cel_ast", "//common:options", "//common/values:cel_value_provider", - "//runtime:program", "//runtime/standard:standard_function", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_guava_guava", @@ -1201,10 +1170,11 @@ java_library( ], deps = [ ":function_overload", - ":resolved_overload_internal", + ":unknown_attributes", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", ], ) @@ -1215,10 +1185,11 @@ cel_android_library( ], deps = [ ":function_overload_android", - ":resolved_overload_internal_android", + ":unknown_attributes_android", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) @@ -1253,9 +1224,9 @@ java_library( tags = [ ], deps = [ + ":function_binding", ":function_overload", "//common/annotations", - "//runtime:function_binding", "@maven//:com_google_guava_guava", ], ) @@ -1266,9 +1237,9 @@ cel_android_library( tags = [ ], deps = [ + ":function_binding_android", ":function_overload_android", "//common/annotations", - "//runtime:function_binding_android", "@maven_android//:com_google_guava_guava", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java index 8f00eebb3..1c5491c14 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java @@ -35,6 +35,6 @@ public interface CelFunctionResolver { * @return an optional value of the resolved overload. * @throws CelEvaluationException if the overload resolution is ambiguous, */ - Optional findOverloadMatchingArgs( + Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index 75be39e92..f533766b2 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -29,14 +29,14 @@ @Immutable public final class CelLateFunctionBindings implements CelFunctionResolver { - private final ImmutableMap functions; + private final ImmutableMap functions; - private CelLateFunctionBindings(ImmutableMap functions) { + private CelLateFunctionBindings(ImmutableMap functions) { this.functions = functions; } @Override - public Optional findOverloadMatchingArgs( + public Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { return DefaultDispatcher.findOverloadMatchingArgs(functionName, overloadIds, functions, args); } @@ -54,7 +54,7 @@ public static CelLateFunctionBindings from(List functions) { CelLateFunctionBindings::createResolvedOverload))); } - private static ResolvedOverload createResolvedOverload(CelFunctionBinding binding) { + private static CelResolvedOverload createResolvedOverload(CelFunctionBinding binding) { return CelResolvedOverload.of( binding.getOverloadId(), (args) -> binding.getDefinition().apply(args), diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java index 26f86a459..74d9e269e 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java @@ -17,7 +17,9 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.MessageLite; import java.util.List; +import java.util.Map; /** * Representation of a function overload which has been resolved to a specific set of argument types @@ -25,14 +27,12 @@ */ @AutoValue @Immutable -abstract class CelResolvedOverload implements ResolvedOverload { +abstract class CelResolvedOverload { /** The overload id of the function. */ - @Override public abstract String getOverloadId(); /** The types of the function parameters. */ - @Override public abstract ImmutableList> getParameterTypes(); /* Denotes whether an overload is strict. @@ -47,11 +47,9 @@ abstract class CelResolvedOverload implements ResolvedOverload { * *

    In a vast majority of cases, this should be set to true. */ - @Override public abstract boolean isStrict(); /** The function definition. */ - @Override public abstract CelFunctionOverload getDefinition(); /** @@ -76,4 +74,41 @@ public static CelResolvedOverload of( return new AutoValue_CelResolvedOverload( overloadId, ImmutableList.copyOf(parameterTypes), isStrict, definition); } + + /** + * Returns true if the overload's expected argument types match the types of the given arguments. + */ + boolean canHandle(Object[] arguments) { + ImmutableList> parameterTypes = getParameterTypes(); + if (parameterTypes.size() != arguments.length) { + return false; + } + for (int i = 0; i < parameterTypes.size(); i++) { + Class paramType = parameterTypes.get(i); + Object arg = arguments[i]; + if (arg == null) { + // null can be assigned to messages, maps, and to objects. + // TODO: Remove null special casing + if (paramType != Object.class + && !MessageLite.class.isAssignableFrom(paramType) + && !Map.class.isAssignableFrom(paramType)) { + return false; + } + continue; + } + + if (arg instanceof Exception || arg instanceof CelUnknownSet) { + // Only non-strict functions can accept errors/unknowns as arguments to a function + if (!isStrict()) { + // Skip assignability check below, but continue to validate remaining args + continue; + } + } + + if (!paramType.isAssignableFrom(arg.getClass())) { + return false; + } + } + return true; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 35340abc5..adc99deb7 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -31,26 +31,26 @@ @Immutable final class DefaultDispatcher implements CelFunctionResolver { - private final ImmutableMap overloads; + private final ImmutableMap overloads; @Override - public Optional findOverloadMatchingArgs( + public Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { return findOverloadMatchingArgs(functionName, overloadIds, overloads, args); } /** Finds the overload that matches the given function name, overload IDs, and arguments. */ - static Optional findOverloadMatchingArgs( + static Optional findOverloadMatchingArgs( String functionName, List overloadIds, - Map overloads, + Map overloads, Object[] args) throws CelEvaluationException { int matchingOverloadCount = 0; - ResolvedOverload match = null; + CelResolvedOverload match = null; List candidates = null; for (String overloadId : overloadIds) { - ResolvedOverload overload = overloads.get(overloadId); + CelResolvedOverload overload = overloads.get(overloadId); // If the overload is null, it means that the function was not registered; however, it is // possible that the overload refers to a late-bound function. if (overload != null && overload.canHandle(args)) { @@ -85,9 +85,9 @@ static Optional findOverloadMatchingArgs( * * @throws IllegalStateException if there are multiple overloads that are marked non-strict. */ - Optional findSingleNonStrictOverload(List overloadIds) { + Optional findSingleNonStrictOverload(List overloadIds) { for (String overloadId : overloadIds) { - ResolvedOverload overload = overloads.get(overloadId); + CelResolvedOverload overload = overloads.get(overloadId); if (overload != null && !overload.isStrict()) { if (overloadIds.size() > 1) { throw new IllegalStateException( @@ -108,9 +108,9 @@ static Builder newBuilder() { @AutoBuilder(ofClass = DefaultDispatcher.class) abstract static class Builder { - abstract ImmutableMap overloads(); + abstract ImmutableMap overloads(); - abstract ImmutableMap.Builder overloadsBuilder(); + abstract ImmutableMap.Builder overloadsBuilder(); @CanIgnoreReturnValue Builder addOverload( @@ -130,7 +130,7 @@ Builder addOverload( abstract DefaultDispatcher build(); } - DefaultDispatcher(ImmutableMap overloads) { + DefaultDispatcher(ImmutableMap overloads) { this.overloads = overloads; } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index 931194d6e..546290a4e 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -494,7 +494,7 @@ private IntermediateResult dispatchCall( Object[] argArray = Arrays.stream(argResults).map(IntermediateResult::value).toArray(); ImmutableList overloadIds = reference.overloadIds(); - ResolvedOverload overload = + CelResolvedOverload overload = findOverloadOrThrow(frame, expr, callExpr.function(), overloadIds, argArray); try { Object dispatchResult = overload.getDefinition().apply(argArray); @@ -517,7 +517,7 @@ private IntermediateResult dispatchCall( } } - private ResolvedOverload findOverloadOrThrow( + private CelResolvedOverload findOverloadOrThrow( ExecutionFrame frame, CelExpr expr, String functionName, @@ -525,7 +525,7 @@ private ResolvedOverload findOverloadOrThrow( Object[] args) throws CelEvaluationException { try { - Optional funcImpl = + Optional funcImpl = dispatcher.findOverloadMatchingArgs(functionName, overloadIds, args); if (funcImpl.isPresent()) { return funcImpl.get(); @@ -1132,7 +1132,7 @@ private RuntimeUnknownResolver getResolver() { return currentResolver; } - private Optional findOverload( + private Optional findOverload( String function, List overloadIds, Object[] args) throws CelEvaluationException { if (lateBoundFunctionResolver.isPresent()) { return lateBoundFunctionResolver diff --git a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java deleted file mode 100644 index 5d632e695..000000000 --- a/runtime/src/main/java/dev/cel/runtime/ResolvedOverload.java +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2024 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.runtime; - -import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.MessageLite; -import java.util.List; -import java.util.Map; - -/** - * Representation of a function overload which has been resolved to a specific set of argument types - * and a function definition. - */ -@Immutable -interface ResolvedOverload { - - /** The overload id of the function. */ - String getOverloadId(); - - /** The types of the function parameters. */ - List> getParameterTypes(); - - /** The function definition. */ - CelFunctionOverload getDefinition(); - - /** - * Denotes whether an overload is strict. - * - *

    A strict function will not be invoked if any of its arguments are an error or unknown value. - * The runtime automatically propagates the error or unknown instead. - * - *

    A non-strict function will be invoked even if its arguments contain errors or unknowns. The - * function's implementation is then responsible for handling these values. This is primarily used - * for short-circuiting logical operators (e.g., `||`, `&&`) and comprehension's - * internal @not_strictly_false function. - * - *

    In a vast majority of cases, a function should be kept strict. - */ - boolean isStrict(); - - /** - * Returns true if the overload's expected argument types match the types of the given arguments. - */ - default boolean canHandle(Object[] arguments) { - List> parameterTypes = getParameterTypes(); - if (parameterTypes.size() != arguments.length) { - return false; - } - for (int i = 0; i < parameterTypes.size(); i++) { - Class paramType = parameterTypes.get(i); - Object arg = arguments[i]; - if (arg == null) { - // null can be assigned to messages, maps, and to objects. - if (paramType != Object.class - && !MessageLite.class.isAssignableFrom(paramType) - && !Map.class.isAssignableFrom(paramType)) { - return false; - } - continue; - } - - if (arg instanceof Exception || arg instanceof CelUnknownSet) { - // Only non-strict functions can accept errors/unknowns as arguments to a function - if (!isStrict()) { - // Skip assignability check below, but continue to validate remaining args - continue; - } - } - - if (!paramType.isAssignableFrom(arg.getClass())) { - return false; - } - } - return true; - } -} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 092f61f04..1541e9c84 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -75,7 +75,6 @@ java_library( "//runtime:proto_message_runtime_equality", "//runtime:proto_message_runtime_helpers", "//runtime:resolved_overload", - "//runtime:resolved_overload_internal", "//runtime:runtime_equality", "//runtime:runtime_helpers", "//runtime:standard_functions", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java index 3f9e1e105..395fb0897 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java @@ -37,7 +37,7 @@ public void findOverload_singleMatchingFunction_isPresent() throws Exception { CelFunctionBinding.from("increment_int", Long.class, (arg) -> arg + 1), CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); - Optional overload = + Optional overload = bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1L}); assertThat(overload).isPresent(); @@ -53,7 +53,7 @@ public void findOverload_noMatchingFunctionSameArgCount_isEmpty() throws Excepti CelFunctionBinding.from("increment_int", Long.class, (arg) -> arg + 1), CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); - Optional overload = + Optional overload = bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), new Object[] {1.0}); assertThat(overload).isEmpty(); @@ -66,7 +66,7 @@ public void findOverload_noMatchingFunctionDifferentArgCount_isEmpty() throws Ex CelFunctionBinding.from("increment_int", Long.class, (arg) -> arg + 1), CelFunctionBinding.from( "increment_uint", UnsignedLong.class, (arg) -> arg.plus(UnsignedLong.ONE))); - Optional overload = + Optional overload = bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_int", "increment_uint"), @@ -88,7 +88,7 @@ public void findOverload_badInput_throwsException() throws Exception { } return arg.plus(UnsignedLong.ONE); })); - Optional overload = + Optional overload = bindings.findOverloadMatchingArgs( "increment", ImmutableList.of("increment_uint"), new Object[] {UnsignedLong.MAX_VALUE}); assertThat(overload).isPresent(); @@ -123,7 +123,7 @@ public void findOverload_nullPrimitiveArg_isEmpty() throws Exception { CelLateFunctionBindings bindings = CelLateFunctionBindings.from( CelFunctionBinding.from("identity_int", Long.class, (arg) -> arg)); - Optional overload = + Optional overload = bindings.findOverloadMatchingArgs( "identity", ImmutableList.of("identity_int"), new Object[] {null}); assertThat(overload).isEmpty(); @@ -134,7 +134,7 @@ public void findOverload_nullMessageArg_returnsOverload() throws Exception { CelLateFunctionBindings bindings = CelLateFunctionBindings.from( CelFunctionBinding.from("identity_msg", TestAllTypes.class, (arg) -> arg)); - Optional overload = + Optional overload = bindings.findOverloadMatchingArgs( "identity", ImmutableList.of("identity_msg"), new Object[] {null}); assertThat(overload).isPresent(); From 0082ec4bd21bfba4d78b5cb28e41e6562ee94767 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 25 Nov 2025 14:00:25 -0800 Subject: [PATCH 027/100] Remove null assignability to function arguments for Protobuf messages PiperOrigin-RevId: 836803181 --- runtime/src/main/java/dev/cel/runtime/BUILD.bazel | 2 -- .../src/main/java/dev/cel/runtime/CelResolvedOverload.java | 5 +---- .../java/dev/cel/runtime/CelLateFunctionBindingsTest.java | 7 ++----- .../test/java/dev/cel/runtime/CelResolvedOverloadTest.java | 4 ++-- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 98d6244aa..35d5358e9 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -1174,7 +1174,6 @@ java_library( "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:com_google_protobuf_protobuf_java", ], ) @@ -1189,7 +1188,6 @@ cel_android_library( "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", - "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java index 74d9e269e..f7cd9f2c0 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java @@ -17,7 +17,6 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import com.google.protobuf.MessageLite; import java.util.List; import java.util.Map; @@ -89,9 +88,7 @@ boolean canHandle(Object[] arguments) { if (arg == null) { // null can be assigned to messages, maps, and to objects. // TODO: Remove null special casing - if (paramType != Object.class - && !MessageLite.class.isAssignableFrom(paramType) - && !Map.class.isAssignableFrom(paramType)) { + if (paramType != Object.class && !Map.class.isAssignableFrom(paramType)) { return false; } continue; diff --git a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java index 395fb0897..819b99665 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLateFunctionBindingsTest.java @@ -130,16 +130,13 @@ public void findOverload_nullPrimitiveArg_isEmpty() throws Exception { } @Test - public void findOverload_nullMessageArg_returnsOverload() throws Exception { + public void findOverload_nullMessageArg_isEmpty() throws Exception { CelLateFunctionBindings bindings = CelLateFunctionBindings.from( CelFunctionBinding.from("identity_msg", TestAllTypes.class, (arg) -> arg)); Optional overload = bindings.findOverloadMatchingArgs( "identity", ImmutableList.of("identity_msg"), new Object[] {null}); - assertThat(overload).isPresent(); - assertThat(overload.get().getOverloadId()).isEqualTo("identity_msg"); - assertThat(overload.get().getParameterTypes()).containsExactly(TestAllTypes.class); - assertThat(overload.get().getDefinition().apply(new Object[] {null})).isNull(); + assertThat(overload).isEmpty(); } } diff --git a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java index 40e2075aa..c1210c1ba 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelResolvedOverloadTest.java @@ -42,11 +42,11 @@ public void canHandle_matchingTypes_returnsTrue() { } @Test - public void canHandle_nullMessageType_returnsTrue() { + public void canHandle_nullMessageType_returnsFalse() { CelResolvedOverload overload = CelResolvedOverload.of( "identity", (args) -> args[0], /* isStrict= */ true, TestAllTypes.class); - assertThat(overload.canHandle(new Object[] {null})).isTrue(); + assertThat(overload.canHandle(new Object[] {null})).isFalse(); } @Test From eb0273d3207a5b96d948408adc5b01ddf99c599b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 25 Nov 2025 17:47:35 -0800 Subject: [PATCH 028/100] Plan calls PiperOrigin-RevId: 836872858 --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 4 + .../dev/cel/runtime/CelResolvedOverload.java | 15 +- .../dev/cel/runtime/DefaultDispatcher.java | 25 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 42 +++ .../dev/cel/runtime/planner/EvalUnary.java | 66 +++++ .../cel/runtime/planner/EvalVarArgsCall.java | 70 +++++ .../cel/runtime/planner/EvalZeroArity.java | 63 ++++ .../cel/runtime/planner/PlannedProgram.java | 18 +- .../cel/runtime/planner/ProgramPlanner.java | 126 +++++++- .../java/dev/cel/runtime/planner/BUILD.bazel | 17 ++ .../runtime/planner/ProgramPlannerTest.java | 273 +++++++++++++++++- 11 files changed, 704 insertions(+), 15 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 35d5358e9..847092cc3 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -128,6 +128,7 @@ java_library( ":resolved_overload", "//:auto_value", "//common:error_codes", + "//common/annotations", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -146,6 +147,7 @@ cel_android_library( ":resolved_overload_android", "//:auto_value", "//common:error_codes", + "//common/annotations", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -1172,6 +1174,7 @@ java_library( ":function_overload", ":unknown_attributes", "//:auto_value", + "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -1186,6 +1189,7 @@ cel_android_library( ":function_overload_android", ":unknown_attributes_android", "//:auto_value", + "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java index f7cd9f2c0..f6a0c4f99 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java @@ -17,6 +17,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.annotations.Internal; import java.util.List; import java.util.Map; @@ -26,7 +27,8 @@ */ @AutoValue @Immutable -abstract class CelResolvedOverload { +@Internal +public abstract class CelResolvedOverload { /** The overload id of the function. */ public abstract String getOverloadId(); @@ -78,7 +80,14 @@ public static CelResolvedOverload of( * Returns true if the overload's expected argument types match the types of the given arguments. */ boolean canHandle(Object[] arguments) { - ImmutableList> parameterTypes = getParameterTypes(); + return canHandle(arguments, getParameterTypes(), isStrict()); + } + + /** + * Returns true if the overload's expected argument types match the types of the given arguments. + */ + public static boolean canHandle( + Object[] arguments, ImmutableList> parameterTypes, boolean isStrict) { if (parameterTypes.size() != arguments.length) { return false; } @@ -96,7 +105,7 @@ boolean canHandle(Object[] arguments) { if (arg instanceof Exception || arg instanceof CelUnknownSet) { // Only non-strict functions can accept errors/unknowns as arguments to a function - if (!isStrict()) { + if (!isStrict) { // Skip assignability check below, but continue to validate remaining args continue; } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index adc99deb7..35ce243b3 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -14,6 +14,7 @@ package dev.cel.runtime; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import com.google.auto.value.AutoBuilder; @@ -22,17 +23,27 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; -/** Default implementation of dispatcher. */ +/** + * Default implementation of dispatcher. + * + *

    CEL Library Internals. Do Not Use. + */ @Immutable -final class DefaultDispatcher implements CelFunctionResolver { +@Internal +public final class DefaultDispatcher implements CelFunctionResolver { private final ImmutableMap overloads; + public Optional findOverload(String functionName) { + return Optional.ofNullable(overloads.get(functionName)); + } + @Override public Optional findOverloadMatchingArgs( String functionName, List overloadIds, Object[] args) throws CelEvaluationException { @@ -101,24 +112,26 @@ Optional findSingleNonStrictOverload(List overloadI return Optional.empty(); } - static Builder newBuilder() { + public static Builder newBuilder() { return new AutoBuilder_DefaultDispatcher_Builder(); } + /** Builder for {@link DefaultDispatcher}. */ @AutoBuilder(ofClass = DefaultDispatcher.class) - abstract static class Builder { + public abstract static class Builder { abstract ImmutableMap overloads(); abstract ImmutableMap.Builder overloadsBuilder(); @CanIgnoreReturnValue - Builder addOverload( + public Builder addOverload( String overloadId, List> argTypes, boolean isStrict, CelFunctionOverload overload) { checkNotNull(overloadId); + checkArgument(!overloadId.isEmpty(), "Overload ID cannot be empty."); checkNotNull(argTypes); checkNotNull(overload); @@ -127,7 +140,7 @@ Builder addOverload( return this; } - abstract DefaultDispatcher build(); + public abstract DefaultDispatcher build(); } DefaultDispatcher(ImmutableMap overloads) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index a0423310c..ee8987886 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -19,6 +19,9 @@ java_library( ":eval_create_list", ":eval_create_map", ":eval_create_struct", + ":eval_unary", + ":eval_var_args_call", + ":eval_zero_arity", ":planned_program", "//:auto_value", "//common:cel_ast", @@ -28,10 +31,12 @@ java_library( "//common/types", "//common/types:type_providers", "//common/values:cel_value_provider", + "//runtime:dispatcher", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", "//runtime:interpretable", "//runtime:program", + "//runtime:resolved_overload", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -45,6 +50,7 @@ java_library( "//:auto_value", "//runtime:activation", "//runtime:evaluation_exception", + "//runtime:evaluation_exception_builder", "//runtime:function_resolver", "//runtime:interpretable", "//runtime:program", @@ -95,6 +101,42 @@ java_library( ], ) +java_library( + name = "eval_zero_arity", + srcs = ["EvalZeroArity.java"], + deps = [ + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:resolved_overload", + ], +) + +java_library( + name = "eval_unary", + srcs = ["EvalUnary.java"], + deps = [ + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:resolved_overload", + ], +) + +java_library( + name = "eval_var_args_call", + srcs = ["EvalVarArgsCall.java"], + deps = [ + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "//runtime:resolved_overload", + ], +) + java_library( name = "eval_create_struct", srcs = ["EvalCreateStruct.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java new file mode 100644 index 000000000..c6daff4b2 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -0,0 +1,66 @@ +// Copyright 2025 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.runtime.planner; + +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +final class EvalUnary implements Interpretable { + + private final CelResolvedOverload resolvedOverload; + private final Interpretable arg; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + Object argVal = arg.eval(resolver); + Object[] arguments = new Object[] {argVal}; + + return resolvedOverload.getDefinition().apply(arguments); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalUnary create(CelResolvedOverload resolvedOverload, Interpretable arg) { + return new EvalUnary(resolvedOverload, arg); + } + + private EvalUnary(CelResolvedOverload resolvedOverload, Interpretable arg) { + this.resolvedOverload = resolvedOverload; + this.arg = arg; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java new file mode 100644 index 000000000..48fc7ba04 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -0,0 +1,70 @@ +// Copyright 2025 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.runtime.planner; + +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +@SuppressWarnings("Immutable") +final class EvalVarArgsCall implements Interpretable { + + private final CelResolvedOverload resolvedOverload; + private final Interpretable[] args; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + Object[] argVals = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + Interpretable arg = args[i]; + argVals[i] = arg.eval(resolver); + } + + return resolvedOverload.getDefinition().apply(argVals); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalVarArgsCall create(CelResolvedOverload resolvedOverload, Interpretable[] args) { + return new EvalVarArgsCall(resolvedOverload, args); + } + + private EvalVarArgsCall(CelResolvedOverload resolvedOverload, Interpretable[] args) { + this.resolvedOverload = resolvedOverload; + this.args = args; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java new file mode 100644 index 000000000..813b84629 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -0,0 +1,63 @@ +// Copyright 2025 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.runtime.planner; + +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +final class EvalZeroArity implements Interpretable { + + private static final Object[] EMPTY_ARRAY = new Object[0]; + + private final CelResolvedOverload resolvedOverload; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + return resolvedOverload.getDefinition().apply(EMPTY_ARRAY); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalZeroArity create(CelResolvedOverload resolvedOverload) { + return new EvalZeroArity(resolvedOverload); + } + + private EvalZeroArity(CelResolvedOverload resolvedOverload) { + this.resolvedOverload = resolvedOverload; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index c8aa1bc0d..34aac58a9 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -18,6 +18,7 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.runtime.Activation; import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; import dev.cel.runtime.Interpretable; @@ -31,12 +32,12 @@ abstract class PlannedProgram implements Program { @Override public Object eval() throws CelEvaluationException { - return interpretable().eval(GlobalResolver.EMPTY); + return evalOrThrow(interpretable(), GlobalResolver.EMPTY); } @Override public Object eval(Map mapValue) throws CelEvaluationException { - return interpretable().eval(Activation.copyOf(mapValue)); + return evalOrThrow(interpretable(), Activation.copyOf(mapValue)); } @Override @@ -45,6 +46,19 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio throw new UnsupportedOperationException("Late bound functions not supported yet"); } + private Object evalOrThrow(Interpretable interpretable, GlobalResolver resolver) + throws CelEvaluationException { + try { + return interpretable.eval(resolver); + } catch (RuntimeException e) { + throw newCelEvaluationException(e); + } + } + + private static CelEvaluationException newCelEvaluationException(Exception e) { + return CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e).build(); + } + static Program create(Interpretable interpretable) { return new AutoValue_PlannedProgram(interpretable); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index ec71155d6..805be193c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -17,12 +17,14 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CheckReturnValue; import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelExpr.CelCall; import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelExpr.CelMap; import dev.cel.common.ast.CelExpr.CelStruct; @@ -36,9 +38,12 @@ import dev.cel.common.values.CelValueProvider; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.DefaultDispatcher; import dev.cel.runtime.Interpretable; import dev.cel.runtime.Program; import java.util.NoSuchElementException; +import java.util.Optional; /** * {@code ProgramPlanner} resolves functions, types, and identifiers at plan time given a @@ -50,6 +55,7 @@ public final class ProgramPlanner { private final CelTypeProvider typeProvider; private final CelValueProvider valueProvider; + private final DefaultDispatcher dispatcher; private final AttributeFactory attributeFactory; /** @@ -73,6 +79,8 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { return planConstant(celExpr.constant()); case IDENT: return planIdent(celExpr, ctx); + case CALL: + return planCall(celExpr, ctx); case LIST: return planCreateList(celExpr, ctx); case STRUCT: @@ -138,6 +146,53 @@ private Interpretable planCheckedIdent( return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name())); } + private Interpretable planCall(CelExpr expr, PlannerContext ctx) { + ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap()); + CelExpr target = resolvedFunction.target().orElse(null); + int argCount = expr.call().args().size(); + if (target != null) { + argCount++; + } + + Interpretable[] evaluatedArgs = new Interpretable[argCount]; + + int offset = 0; + if (target != null) { + evaluatedArgs[0] = plan(target, ctx); + offset++; + } + + ImmutableList args = expr.call().args(); + for (int argIndex = 0; argIndex < args.size(); argIndex++) { + evaluatedArgs[argIndex + offset] = plan(args.get(argIndex), ctx); + } + + // TODO: Handle all specialized calls (logical operators, conditionals, equals etc) + String functionName = resolvedFunction.functionName(); + + CelResolvedOverload resolvedOverload = null; + if (resolvedFunction.overloadId().isPresent()) { + resolvedOverload = dispatcher.findOverload(resolvedFunction.overloadId().get()).orElse(null); + } + + if (resolvedOverload == null) { + // Parsed-only function dispatch + resolvedOverload = + dispatcher + .findOverload(functionName) + .orElseThrow(() -> new NoSuchElementException("Overload not found: " + functionName)); + } + + switch (argCount) { + case 0: + return EvalZeroArity.create(resolvedOverload); + case 1: + return EvalUnary.create(resolvedOverload, evaluatedArgs[0]); + default: + return EvalVarArgsCall.create(resolvedOverload, evaluatedArgs); + } + } + private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { CelStruct struct = celExpr.struct(); CelType structType = @@ -193,6 +248,69 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { return EvalCreateMap.create(keys, values); } + /** + * resolveFunction determines the call target, function name, and overload name (when unambiguous) + * from the given call expr. + */ + private ResolvedFunction resolveFunction( + CelExpr expr, ImmutableMap referenceMap) { + CelCall call = expr.call(); + Optional target = call.target(); + String functionName = call.function(); + + CelReference reference = referenceMap.get(expr.id()); + if (reference != null) { + // Checked expression + if (reference.overloadIds().size() == 1) { + ResolvedFunction.Builder builder = + ResolvedFunction.newBuilder() + .setFunctionName(functionName) + .setOverloadId(reference.overloadIds().get(0)); + + target.ifPresent(builder::setTarget); + + return builder.build(); + } + } + + // Parsed-only. + // TODO: Handle containers. + if (!target.isPresent()) { + return ResolvedFunction.newBuilder().setFunctionName(functionName).build(); + } else { + return ResolvedFunction.newBuilder() + .setFunctionName(functionName) + .setTarget(target.get()) + .build(); + } + } + + @AutoValue + abstract static class ResolvedFunction { + + abstract String functionName(); + + abstract Optional target(); + + abstract Optional overloadId(); + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setFunctionName(String functionName); + + abstract Builder setTarget(CelExpr target); + + abstract Builder setOverloadId(String overloadId); + + @CheckReturnValue + abstract ResolvedFunction build(); + } + + private static Builder newBuilder() { + return new AutoValue_ProgramPlanner_ResolvedFunction.Builder(); + } + } + @AutoValue abstract static class PlannerContext { @@ -206,14 +324,16 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) { } public static ProgramPlanner newPlanner( - CelTypeProvider typeProvider, CelValueProvider valueProvider) { - return new ProgramPlanner(typeProvider, valueProvider); + CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher) { + return new ProgramPlanner(typeProvider, valueProvider, dispatcher); } - private ProgramPlanner(CelTypeProvider typeProvider, CelValueProvider valueProvider) { + private ProgramPlanner( + CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; // TODO: Container support + this.dispatcher = dispatcher; this.attributeFactory = AttributeFactory.newAttributeFactory(CelContainer.newBuilder().build(), typeProvider); } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 0b36e20ac..94a427685 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -17,6 +17,8 @@ java_library( "//common:cel_ast", "//common:cel_descriptor_util", "//common:cel_source", + "//common:compiler_common", + "//common:operator", "//common:options", "//common/ast", "//common/internal:cel_descriptor_pools", @@ -34,8 +36,23 @@ java_library( "//compiler:compiler_builder", "//extensions", "//runtime", + "//runtime:dispatcher", + "//runtime:function_binding", "//runtime:program", + "//runtime:resolved_overload", + "//runtime:runtime_equality", + "//runtime:runtime_helpers", "//runtime/planner:program_planner", + "//runtime/standard:add", + "//runtime/standard:divide", + "//runtime/standard:equals", + "//runtime/standard:greater", + "//runtime/standard:greater_equals", + "//runtime/standard:index", + "//runtime/standard:less", + "//runtime/standard:logical_not", + "//runtime/standard:not_strictly_false", + "//runtime/standard:standard_function", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_testparameterinjector_test_parameter_injector", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 32651d73d..56bc1810a 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -15,12 +15,17 @@ package dev.cel.runtime.planner; import static com.google.common.truth.Truth.assertThat; +import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration; +import static dev.cel.common.CelOverloadDecl.newGlobalOverload; +import static dev.cel.common.CelOverloadDecl.newMemberOverload; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; @@ -28,6 +33,7 @@ import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelOptions; import dev.cel.common.CelSource; +import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; @@ -53,17 +59,36 @@ import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.extensions.CelExtensions; import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelFunctionOverload; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.DefaultDispatcher; import dev.cel.runtime.Program; +import dev.cel.runtime.RuntimeEquality; +import dev.cel.runtime.RuntimeHelpers; +import dev.cel.runtime.standard.AddOperator; +import dev.cel.runtime.standard.CelStandardFunction; +import dev.cel.runtime.standard.DivideOperator; +import dev.cel.runtime.standard.EqualsOperator; +import dev.cel.runtime.standard.GreaterEqualsOperator; +import dev.cel.runtime.standard.GreaterOperator; +import dev.cel.runtime.standard.IndexOperator; +import dev.cel.runtime.standard.LessOperator; +import dev.cel.runtime.standard.LogicalNotOperator; +import dev.cel.runtime.standard.NotStrictlyFalseFunction; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(TestParameterInjector.class) public final class ProgramPlannerTest { // Note that the following deps will be built from top-level builder APIs + private static final CelOptions CEL_OPTIONS = CelOptions.current().build(); private static final CelTypeProvider TYPE_PROVIDER = new CombinedCelTypeProvider( DefaultTypeProvider.getInstance(), new ProtoMessageTypeProvider(ImmutableSet.of(TestAllTypes.getDescriptor()))); + private static final RuntimeEquality RUNTIME_EQUALITY = + RuntimeEquality.create(RuntimeHelpers.create(), CEL_OPTIONS); private static final CelDescriptorPool DESCRIPTOR_POOL = DefaultDescriptorPool.create( CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( @@ -74,14 +99,160 @@ public final class ProgramPlannerTest { ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); private static final ProgramPlanner PLANNER = - ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER); + ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher()); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() + .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) .addVar("int_var", SimpleType.INT) + .addVar("dyn_var", SimpleType.DYN) + .addFunctionDeclarations( + newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), + newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)), + newFunctionDeclaration( + "neg", + newGlobalOverload("neg_int", SimpleType.INT, SimpleType.INT), + newGlobalOverload("neg_double", SimpleType.DOUBLE, SimpleType.DOUBLE)), + newFunctionDeclaration( + "concat", + newGlobalOverload( + "concat_bytes_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES), + newMemberOverload( + "bytes_concat_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES))) .addMessageTypes(TestAllTypes.getDescriptor()) .addLibraries(CelExtensions.optional()) .build(); + /** + * Configure dispatcher for testing purposes. This is done manually here, but this should be + * driven by the top-level runtime APIs in the future + */ + private static DefaultDispatcher newDispatcher() { + DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); + + // Subsetted StdLib + addBindings( + builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); + addBindings( + builder, + Operator.LOGICAL_NOT.getFunction(), + fromStandardFunction(LogicalNotOperator.create())); + addBindings(builder, Operator.ADD.getFunction(), fromStandardFunction(AddOperator.create())); + addBindings( + builder, Operator.GREATER.getFunction(), fromStandardFunction(GreaterOperator.create())); + addBindings( + builder, + Operator.GREATER_EQUALS.getFunction(), + fromStandardFunction(GreaterEqualsOperator.create())); + addBindings(builder, Operator.LESS.getFunction(), fromStandardFunction(LessOperator.create())); + addBindings( + builder, Operator.DIVIDE.getFunction(), fromStandardFunction(DivideOperator.create())); + addBindings( + builder, Operator.EQUALS.getFunction(), fromStandardFunction(EqualsOperator.create())); + addBindings( + builder, + Operator.NOT_STRICTLY_FALSE.getFunction(), + fromStandardFunction(NotStrictlyFalseFunction.create())); + + // Custom functions + addBindings( + builder, + "zero", + CelFunctionBinding.from("zero_overload", ImmutableList.of(), (unused) -> 0L)); + addBindings( + builder, + "error", + CelFunctionBinding.from( + "error_overload", + ImmutableList.of(), + (unused) -> { + throw new IllegalArgumentException("Intentional error"); + })); + addBindings( + builder, + "neg", + CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), + CelFunctionBinding.from("neg_double", Double.class, arg -> -arg)); + addBindings( + builder, + "concat", + CelFunctionBinding.from( + "concat_bytes_bytes", + CelByteString.class, + CelByteString.class, + ProgramPlannerTest::concatenateByteArrays), + CelFunctionBinding.from( + "bytes_concat_bytes", + CelByteString.class, + CelByteString.class, + ProgramPlannerTest::concatenateByteArrays)); + + return builder.build(); + } + + private static void addBindings( + DefaultDispatcher.Builder builder, + String functionName, + CelFunctionBinding... functionBindings) { + addBindings(builder, functionName, ImmutableSet.copyOf(functionBindings)); + } + + private static void addBindings( + DefaultDispatcher.Builder builder, + String functionName, + ImmutableCollection overloadBindings) { + if (overloadBindings.isEmpty()) { + throw new IllegalArgumentException("Invalid bindings"); + } + // TODO: Runtime top-level APIs currently does not allow grouping overloads with + // the function name. This capability will have to be added. + if (overloadBindings.size() == 1) { + CelFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); + builder.addOverload( + functionName, + singleBinding.getArgTypes(), + singleBinding.isStrict(), + args -> guardedOp(functionName, args, singleBinding)); + } else { + overloadBindings.forEach( + overload -> + builder.addOverload( + overload.getOverloadId(), + overload.getArgTypes(), + overload.isStrict(), + args -> guardedOp(functionName, args, overload))); + + // Setup dynamic dispatch + CelFunctionOverload dynamicDispatchDef = + args -> { + for (CelFunctionBinding overload : overloadBindings) { + if (CelResolvedOverload.canHandle( + args, overload.getArgTypes(), overload.isStrict())) { + return overload.getDefinition().apply(args); + } + } + + throw new IllegalArgumentException( + "No matching overload for function: " + functionName); + }; + + boolean allOverloadsStrict = overloadBindings.stream().allMatch(CelFunctionBinding::isStrict); + builder.addOverload( + functionName, ImmutableList.of(), /* isStrict= */ allOverloadsStrict, dynamicDispatchDef); + } + } + + /** Creates an invocation guard around the overload definition. */ + private static Object guardedOp( + String functionName, Object[] args, CelFunctionBinding singleBinding) + throws CelEvaluationException { + if (!CelResolvedOverload.canHandle( + args, singleBinding.getArgTypes(), singleBinding.isStrict())) { + throw new IllegalArgumentException("No matching overload for function: " + functionName); + } + + return singleBinding.getDefinition().apply(args); + } + @TestParameter boolean isParseOnly; @Test @@ -206,6 +377,89 @@ public void planCreateStruct_withFields() throws Exception { .isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").setSingleBool(true).build()); } + @Test + public void plan_call_zeroArgs() throws Exception { + CelAbstractSyntaxTree ast = compile("zero()"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(0L); + } + + @Test + public void plan_call_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("error()"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e).hasMessageThat().contains("evaluation error: Intentional error"); + assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void plan_call_oneArg_int() throws Exception { + CelAbstractSyntaxTree ast = compile("neg(1)"); + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(-1L); + } + + @Test + public void plan_call_oneArg_double() throws Exception { + CelAbstractSyntaxTree ast = compile("neg(2.5)"); + Program program = PLANNER.plan(ast); + + Double result = (Double) program.eval(); + + assertThat(result).isEqualTo(-2.5d); + } + + @Test + public void plan_call_twoArgs_global() throws Exception { + CelAbstractSyntaxTree ast = compile("concat(b'abc', b'def')"); + Program program = PLANNER.plan(ast); + + CelByteString result = (CelByteString) program.eval(); + + assertThat(result).isEqualTo(CelByteString.of("abcdef".getBytes(UTF_8))); + } + + @Test + public void plan_call_twoArgs_receiver() throws Exception { + CelAbstractSyntaxTree ast = compile("b'abc'.concat(b'def')"); + Program program = PLANNER.plan(ast); + + CelByteString result = (CelByteString) program.eval(); + + assertThat(result).isEqualTo(CelByteString.of("abcdef".getBytes(UTF_8))); + } + + @Test + public void plan_call_mapIndex() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var['key'][1]"); + Program program = PLANNER.plan(ast); + ImmutableMap mapVarPayload = ImmutableMap.of("key", ImmutableList.of(1L, 2L)); + + Long result = (Long) program.eval(ImmutableMap.of("map_var", mapVarPayload)); + + assertThat(result).isEqualTo(2L); + } + + @Test + public void plan_call_noMatchingOverload_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("concat(b'abc', dyn_var)"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("dyn_var", "Impossible Overload"))); + assertThat(e).hasMessageThat().contains("No matching overload for function: concat"); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { @@ -215,6 +469,23 @@ private CelAbstractSyntaxTree compile(String expression) throws Exception { return CEL_COMPILER.check(ast).getAst(); } + private static CelByteString concatenateByteArrays(CelByteString bytes1, CelByteString bytes2) { + if (bytes1.isEmpty()) { + return bytes2; + } + + if (bytes2.isEmpty()) { + return bytes1; + } + + return bytes1.concat(bytes2); + } + + private static ImmutableSet fromStandardFunction( + CelStandardFunction standardFunction) { + return standardFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY); + } + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum ConstantTestCase { NULL("null", NullValue.NULL_VALUE), From 33a4cb09139fede067d1f4c221e8b0482f67e1a4 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 26 Nov 2025 12:27:59 -0800 Subject: [PATCH 029/100] Plan specialized calls for Logical OR/AND PiperOrigin-RevId: 837218578 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 40 +++++++++ .../java/dev/cel/runtime/planner/EvalAnd.java | 86 +++++++++++++++++++ .../dev/cel/runtime/planner/EvalHelpers.java | 32 +++++++ .../java/dev/cel/runtime/planner/EvalOr.java | 86 +++++++++++++++++++ .../cel/runtime/planner/PlannedProgram.java | 20 ++++- .../cel/runtime/planner/ProgramPlanner.java | 12 +++ .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/ProgramPlannerTest.java | 66 ++++++++++++++ 8 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index ee8987886..110fe468c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -14,11 +14,13 @@ java_library( ], deps = [ ":attribute", + ":eval_and", ":eval_attribute", ":eval_const", ":eval_create_list", ":eval_create_map", ":eval_create_struct", + ":eval_or", ":eval_unary", ":eval_var_args_call", ":eval_zero_arity", @@ -26,6 +28,7 @@ java_library( "//:auto_value", "//common:cel_ast", "//common:container", + "//common:operator", "//common/annotations", "//common/ast", "//common/types", @@ -48,6 +51,8 @@ java_library( srcs = ["PlannedProgram.java"], deps = [ "//:auto_value", + "//common:runtime_exception", + "//common/values", "//runtime:activation", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", @@ -137,6 +142,32 @@ java_library( ], ) +java_library( + name = "eval_or", + srcs = ["EvalOr.java"], + deps = [ + ":eval_helpers", + "//common/values", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_and", + srcs = ["EvalAnd.java"], + deps = [ + ":eval_helpers", + "//common/values", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "eval_create_struct", srcs = ["EvalCreateStruct.java"], @@ -178,3 +209,12 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_library( + name = "eval_helpers", + srcs = ["EvalHelpers.java"], + deps = [ + "//common/values", + "//runtime:interpretable", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java new file mode 100644 index 000000000..4bf9af517 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java @@ -0,0 +1,86 @@ +// Copyright 2025 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.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; + +import com.google.common.base.Preconditions; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +final class EvalAnd implements Interpretable { + + @SuppressWarnings("Immutable") + private final Interpretable[] args; + + @Override + public Object eval(GlobalResolver resolver) { + ErrorValue errorValue = null; + for (Interpretable arg : args) { + Object argVal = evalNonstrictly(arg, resolver); + if (argVal instanceof Boolean) { + // Short-circuit on false + if (!((boolean) argVal)) { + return false; + } + } else if (argVal instanceof ErrorValue) { + errorValue = (ErrorValue) argVal; + } else { + // TODO: Handle unknowns + throw new IllegalArgumentException( + String.format("Expected boolean value, found: %s", argVal)); + } + } + + if (errorValue != null) { + return errorValue; + } + + return true; + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalAnd create(Interpretable[] args) { + return new EvalAnd(args); + } + + private EvalAnd(Interpretable[] args) { + Preconditions.checkArgument(args.length == 2); + this.args = args; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java new file mode 100644 index 000000000..82bd6124a --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -0,0 +1,32 @@ +// Copyright 2025 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.runtime.planner; + +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +final class EvalHelpers { + + static Object evalNonstrictly(Interpretable interpretable, GlobalResolver resolver) { + try { + return interpretable.eval(resolver); + } catch (Exception e) { + return ErrorValue.create(e); + } + } + + private EvalHelpers() {} +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java new file mode 100644 index 000000000..afa02dfb8 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java @@ -0,0 +1,86 @@ +// Copyright 2025 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.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; + +import com.google.common.base.Preconditions; +import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +final class EvalOr implements Interpretable { + + @SuppressWarnings("Immutable") + private final Interpretable[] args; + + @Override + public Object eval(GlobalResolver resolver) { + ErrorValue errorValue = null; + for (Interpretable arg : args) { + Object argVal = evalNonstrictly(arg, resolver); + if (argVal instanceof Boolean) { + // Short-circuit on true + if (((boolean) argVal)) { + return true; + } + } else if (argVal instanceof ErrorValue) { + errorValue = (ErrorValue) argVal; + } else { + // TODO: Handle unknowns + throw new IllegalArgumentException( + String.format("Expected boolean value, found: %s", argVal)); + } + } + + if (errorValue != null) { + return errorValue; + } + + return false; + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalOr create(Interpretable[] args) { + return new EvalOr(args); + } + + private EvalOr(Interpretable[] args) { + Preconditions.checkArgument(args.length == 2); + this.args = args; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index 34aac58a9..2c0d402c2 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -16,6 +16,8 @@ import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.values.ErrorValue; import dev.cel.runtime.Activation; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; @@ -49,14 +51,28 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio private Object evalOrThrow(Interpretable interpretable, GlobalResolver resolver) throws CelEvaluationException { try { - return interpretable.eval(resolver); + Object evalResult = interpretable.eval(resolver); + if (evalResult instanceof ErrorValue) { + ErrorValue errorValue = (ErrorValue) evalResult; + throw newCelEvaluationException(errorValue.value()); + } + + return evalResult; } catch (RuntimeException e) { throw newCelEvaluationException(e); } } private static CelEvaluationException newCelEvaluationException(Exception e) { - return CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e).build(); + CelEvaluationExceptionBuilder builder; + if (e instanceof CelRuntimeException) { + // Preserve detailed error, including error codes if one exists. + builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e); + } else { + builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e); + } + + return builder.build(); } static Program create(Interpretable interpretable) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 805be193c..09b79617c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -21,6 +21,7 @@ import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; +import dev.cel.common.Operator; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; @@ -169,6 +170,17 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) { // TODO: Handle all specialized calls (logical operators, conditionals, equals etc) String functionName = resolvedFunction.functionName(); + Operator operator = Operator.findReverse(functionName).orElse(null); + if (operator != null) { + switch (operator) { + case LOGICAL_OR: + return EvalOr.create(evaluatedArgs); + case LOGICAL_AND: + return EvalAnd.create(evaluatedArgs); + default: + // fall-through + } + } CelResolvedOverload resolvedOverload = null; if (resolvedFunction.overloadId().isPresent()) { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 94a427685..8c8c66369 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -18,6 +18,7 @@ java_library( "//common:cel_descriptor_util", "//common:cel_source", "//common:compiler_common", + "//common:error_codes", "//common:operator", "//common:options", "//common/ast", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 56bc1810a..3681583d8 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -29,8 +29,10 @@ import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelSource; import dev.cel.common.Operator; @@ -460,6 +462,70 @@ public void plan_call_noMatchingOverload_throws() throws Exception { assertThat(e).hasMessageThat().contains("No matching overload for function: concat"); } + @Test + @TestParameters("{expression: 'true || true', expectedResult: true}") + @TestParameters("{expression: 'true || false', expectedResult: true}") + @TestParameters("{expression: 'false || true', expectedResult: true}") + @TestParameters("{expression: 'false || false', expectedResult: false}") + @TestParameters("{expression: 'true || (1 / 0 > 2)', expectedResult: true}") + @TestParameters("{expression: '(1 / 0 > 2) || true', expectedResult: true}") + public void plan_call_logicalOr_shortCircuit(String expression, boolean expectedResult) + throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: '(1 / 0 > 2) || (1 / 0 > 2)'}") + @TestParameters("{expression: 'false || (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) || false'}") + public void plan_call_logicalOr_throws(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + // TODO: Tag metadata (source loc) + assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); + } + + @Test + @TestParameters("{expression: 'true && true', expectedResult: true}") + @TestParameters("{expression: 'true && false', expectedResult: false}") + @TestParameters("{expression: 'false && true', expectedResult: false}") + @TestParameters("{expression: 'false && false', expectedResult: false}") + @TestParameters("{expression: 'false && (1 / 0 > 2)', expectedResult: false}") + @TestParameters("{expression: '(1 / 0 > 2) && false', expectedResult: false}") + public void plan_call_logicalAnd_shortCircuit(String expression, boolean expectedResult) + throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: '(1 / 0 > 2) && (1 / 0 > 2)'}") + @TestParameters("{expression: 'true && (1 / 0 > 2)'}") + @TestParameters("{expression: '(1 / 0 > 2) && true'}") + public void plan_call_logicalAnd_throws(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + // TODO: Tag metadata (source loc) + assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { From 5a75f0f1a9d530f1d0d1044e14c9f3581bf4ac3e Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 2 Dec 2025 13:16:09 -0800 Subject: [PATCH 030/100] Plan specialized call for Conditional PiperOrigin-RevId: 839399795 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 17 +++- .../cel/runtime/planner/EvalConditional.java | 78 +++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 2 + .../runtime/planner/ProgramPlannerTest.java | 29 +++++++ 4 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 110fe468c..3ad0c0173 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -16,6 +16,7 @@ java_library( ":attribute", ":eval_and", ":eval_attribute", + ":eval_conditional", ":eval_const", ":eval_create_list", ":eval_create_map", @@ -168,6 +169,18 @@ java_library( ], ) +java_library( + name = "eval_conditional", + srcs = ["EvalConditional.java"], + deps = [ + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "eval_create_struct", srcs = ["EvalCreateStruct.java"], @@ -188,7 +201,7 @@ java_library( name = "eval_create_list", srcs = ["EvalCreateList.java"], deps = [ - "//runtime", + "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", "//runtime:interpretable", @@ -201,7 +214,7 @@ java_library( name = "eval_create_map", srcs = ["EvalCreateMap.java"], deps = [ - "//runtime", + "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", "//runtime:interpretable", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java new file mode 100644 index 000000000..ca32b405c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java @@ -0,0 +1,78 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.base.Preconditions; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; +import dev.cel.runtime.Interpretable; + +final class EvalConditional implements Interpretable { + + @SuppressWarnings("Immutable") + private final Interpretable[] args; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + Interpretable condition = args[0]; + Interpretable truthy = args[1]; + Interpretable falsy = args[2]; + // TODO: Handle unknowns + Object condResult = condition.eval(resolver); + if (!(condResult instanceof Boolean)) { + throw new IllegalArgumentException( + String.format("Expected boolean value, found :%s", condResult)); + } + + // TODO: Handle exhaustive eval + if ((boolean) condResult) { + return truthy.eval(resolver); + } + + return falsy.eval(resolver); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + static EvalConditional create(Interpretable[] args) { + return new EvalConditional(args); + } + + private EvalConditional(Interpretable[] args) { + Preconditions.checkArgument(args.length == 3); + this.args = args; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 09b79617c..e3f00a405 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -177,6 +177,8 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) { return EvalOr.create(evaluatedArgs); case LOGICAL_AND: return EvalAnd.create(evaluatedArgs); + case CONDITIONAL: + return EvalConditional.create(evaluatedArgs); default: // fall-through } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 3681583d8..5001e3559 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -526,6 +526,35 @@ public void plan_call_logicalAnd_throws(String expression) throws Exception { assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } + @Test + @TestParameters("{expression: 'false ? (1 / 0) > 2 : false', expectedResult: false}") + @TestParameters("{expression: 'false ? (1 / 0) > 2 : true', expectedResult: true}") + @TestParameters("{expression: 'true ? false : (1 / 0) > 2', expectedResult: false}") + @TestParameters("{expression: 'true ? true : (1 / 0) > 2', expectedResult: true}") + public void plan_call_conditional_shortCircuit(String expression, boolean expectedResult) + throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isEqualTo(expectedResult); + } + + @Test + @TestParameters("{expression: '(1 / 0) > 2 ? true : true'}") + @TestParameters("{expression: 'true ? (1 / 0) > 2 : true'}") + @TestParameters("{expression: 'false ? true : (1 / 0) > 2'}") + public void plan_call_conditional_throws(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { From 741ad142f99700578be59479c27998fab12d9fb7 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 2 Dec 2025 13:43:54 -0800 Subject: [PATCH 031/100] Support container resolution for calls and struct creation in planner PiperOrigin-RevId: 839411411 --- .../cel/runtime/planner/ProgramPlanner.java | 120 ++++++++++++++---- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/ProgramPlannerTest.java | 43 ++++++- 3 files changed, 135 insertions(+), 29 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index e3f00a405..bf7729c0f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -28,6 +28,7 @@ import dev.cel.common.ast.CelExpr.CelCall; import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelExpr.CelMap; +import dev.cel.common.ast.CelExpr.CelSelect; import dev.cel.common.ast.CelExpr.CelStruct; import dev.cel.common.ast.CelExpr.CelStruct.Entry; import dev.cel.common.ast.CelReference; @@ -58,6 +59,7 @@ public final class ProgramPlanner { private final CelValueProvider valueProvider; private final DefaultDispatcher dispatcher; private final AttributeFactory attributeFactory; + private final CelContainer container; /** * Plans a {@link Program} from the provided parsed-only or type-checked {@link @@ -168,7 +170,6 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) { evaluatedArgs[argIndex + offset] = plan(args.get(argIndex), ctx); } - // TODO: Handle all specialized calls (logical operators, conditionals, equals etc) String functionName = resolvedFunction.functionName(); Operator operator = Operator.findReverse(functionName).orElse(null); if (operator != null) { @@ -209,16 +210,7 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) { private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { CelStruct struct = celExpr.struct(); - CelType structType = - typeProvider - .findType(struct.messageName()) - .orElseThrow( - () -> new IllegalArgumentException("Undefined type name: " + struct.messageName())); - if (!structType.kind().equals(CelKind.STRUCT)) { - throw new IllegalArgumentException( - String.format( - "Expected struct type for %s, got %s", structType.name(), structType.kind())); - } + StructType structType = resolveStructType(struct); ImmutableList entries = struct.entries(); String[] keys = new String[entries.size()]; @@ -230,7 +222,7 @@ private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { values[i] = plan(entry.value(), ctx); } - return EvalCreateStruct.create(valueProvider, (StructType) structType, keys, values); + return EvalCreateStruct.create(valueProvider, structType, keys, values); } private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { @@ -269,7 +261,7 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { private ResolvedFunction resolveFunction( CelExpr expr, ImmutableMap referenceMap) { CelCall call = expr.call(); - Optional target = call.target(); + Optional maybeTarget = call.target(); String functionName = call.function(); CelReference reference = referenceMap.get(expr.id()); @@ -281,22 +273,89 @@ private ResolvedFunction resolveFunction( .setFunctionName(functionName) .setOverloadId(reference.overloadIds().get(0)); - target.ifPresent(builder::setTarget); + maybeTarget.ifPresent(builder::setTarget); return builder.build(); } } - // Parsed-only. - // TODO: Handle containers. - if (!target.isPresent()) { + // Parsed-only function resolution. + // + // There are two distinct cases we must handle: + // + // 1. Non-qualified function calls. This will resolve into either: + // - A simple global call foo() + // - A fully qualified global call through normal container resolution foo.bar.qux() + // 2. Qualified function calls: + // - A member call on an identifier foo.bar() + // - A fully qualified global call, through normal container resolution or abbreviations + // foo.bar.qux() + if (!maybeTarget.isPresent()) { + for (String cand : container.resolveCandidateNames(functionName)) { + CelResolvedOverload overload = dispatcher.findOverload(cand).orElse(null); + if (overload != null) { + return ResolvedFunction.newBuilder().setFunctionName(cand).build(); + } + } + + // Normal global call return ResolvedFunction.newBuilder().setFunctionName(functionName).build(); - } else { - return ResolvedFunction.newBuilder() - .setFunctionName(functionName) - .setTarget(target.get()) - .build(); } + + CelExpr target = maybeTarget.get(); + String qualifiedPrefix = toQualifiedName(target).orElse(null); + if (qualifiedPrefix != null) { + String qualifiedName = qualifiedPrefix + "." + functionName; + for (String cand : container.resolveCandidateNames(qualifiedName)) { + CelResolvedOverload overload = dispatcher.findOverload(cand).orElse(null); + if (overload != null) { + return ResolvedFunction.newBuilder().setFunctionName(cand).build(); + } + } + } + + // Normal member call + return ResolvedFunction.newBuilder().setFunctionName(functionName).setTarget(target).build(); + } + + private StructType resolveStructType(CelStruct struct) { + String messageName = struct.messageName(); + for (String typeName : container.resolveCandidateNames(messageName)) { + CelType structType = typeProvider.findType(typeName).orElse(null); + if (structType == null) { + continue; + } + + if (!structType.kind().equals(CelKind.STRUCT)) { + throw new IllegalArgumentException( + String.format( + "Expected struct type for %s, got %s", structType.name(), structType.kind())); + } + + return (StructType) structType; + } + + throw new IllegalArgumentException("Undefined type name: " + messageName); + } + + /** Converts a given expression into a qualified name, if possible. */ + private Optional toQualifiedName(CelExpr operand) { + switch (operand.getKind()) { + case IDENT: + return Optional.of(operand.ident().name()); + case SELECT: + CelSelect select = operand.select(); + String maybeQualified = toQualifiedName(select.operand()).orElse(null); + if (maybeQualified != null) { + return Optional.of(maybeQualified + "." + select.field()); + } + + break; + default: + // fall-through + } + + return Optional.empty(); } @AutoValue @@ -338,17 +397,22 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) { } public static ProgramPlanner newPlanner( - CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher) { - return new ProgramPlanner(typeProvider, valueProvider, dispatcher); + CelTypeProvider typeProvider, + CelValueProvider valueProvider, + DefaultDispatcher dispatcher, + CelContainer container) { + return new ProgramPlanner(typeProvider, valueProvider, dispatcher, container); } private ProgramPlanner( - CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher) { + CelTypeProvider typeProvider, + CelValueProvider valueProvider, + DefaultDispatcher dispatcher, + CelContainer container) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; - // TODO: Container support this.dispatcher = dispatcher; - this.attributeFactory = - AttributeFactory.newAttributeFactory(CelContainer.newBuilder().build(), typeProvider); + this.container = container; + this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider); } } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 8c8c66369..9e3a79ed9 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -18,6 +18,7 @@ java_library( "//common:cel_descriptor_util", "//common:cel_source", "//common:compiler_common", + "//common:container", "//common:error_codes", "//common:operator", "//common:options", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 5001e3559..205e2ef8b 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -31,6 +31,7 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; @@ -99,9 +100,11 @@ public final class ProgramPlannerTest { DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)); private static final CelValueProvider VALUE_PROVIDER = ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); + private static final CelContainer CEL_CONTAINER = + CelContainer.newBuilder().setName("cel.expr.conformance.proto3").build(); private static final ProgramPlanner PLANNER = - ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher()); + ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_CONTAINER); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) @@ -114,6 +117,10 @@ public final class ProgramPlannerTest { "neg", newGlobalOverload("neg_int", SimpleType.INT, SimpleType.INT), newGlobalOverload("neg_double", SimpleType.DOUBLE, SimpleType.DOUBLE)), + newFunctionDeclaration( + "cel.expr.conformance.proto3.power", + newGlobalOverload( + "power_int_int", SimpleType.INT, SimpleType.INT, SimpleType.INT)), newFunctionDeclaration( "concat", newGlobalOverload( @@ -122,6 +129,7 @@ public final class ProgramPlannerTest { "bytes_concat_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES))) .addMessageTypes(TestAllTypes.getDescriptor()) .addLibraries(CelExtensions.optional()) + .setContainer(CEL_CONTAINER) .build(); /** @@ -174,6 +182,14 @@ private static DefaultDispatcher newDispatcher() { "neg", CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), CelFunctionBinding.from("neg_double", Double.class, arg -> -arg)); + addBindings( + builder, + "cel.expr.conformance.proto3.power", + CelFunctionBinding.from( + "power_int_int", + Long.class, + Long.class, + (value, power) -> (long) Math.pow(value, power))); addBindings( builder, "concat", @@ -379,6 +395,16 @@ public void planCreateStruct_withFields() throws Exception { .isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").setSingleBool(true).build()); } + @Test + public void plan_createStruct_withContainer() throws Exception { + CelAbstractSyntaxTree ast = compile("TestAllTypes{}"); + Program program = PLANNER.plan(ast); + + TestAllTypes result = (TestAllTypes) program.eval(); + + assertThat(result).isEqualTo(TestAllTypes.getDefaultInstance()); + } + @Test public void plan_call_zeroArgs() throws Exception { CelAbstractSyntaxTree ast = compile("zero()"); @@ -555,6 +581,21 @@ public void plan_call_conditional_throws(String expression) throws Exception { assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } + @Test + @TestParameters("{expression: 'power(2,3)'}") + @TestParameters("{expression: 'proto3.power(2,3)'}") + @TestParameters("{expression: 'conformance.proto3.power(2,3)'}") + @TestParameters("{expression: 'expr.conformance.proto3.power(2,3)'}") + @TestParameters("{expression: 'cel.expr.conformance.proto3.power(2,3)'}") + public void plan_call_withContainer(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); // invokes cel.expr.conformance.proto3.power + Program program = PLANNER.plan(ast); + + Long result = (Long) program.eval(); + + assertThat(result).isEqualTo(8); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { From 307269343fee75d77bb72f08cee111dfd500fca2 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 4 Dec 2025 21:14:14 -0800 Subject: [PATCH 032/100] Persist lazily bound variables in the correct scoped resolver PiperOrigin-RevId: 840535220 --- .../extensions/CelBindingsExtensionsTest.java | 73 ++++ .../optimizers/SubexpressionOptimizer.java | 118 +++++- .../SubexpressionOptimizerTest.java | 32 +- ...ssion_ast_block_recursion_depth_4.baseline | 360 +++++++++--------- ...ssion_ast_block_recursion_depth_5.baseline | 360 +++++++++--------- ...ssion_ast_block_recursion_depth_6.baseline | 360 +++++++++--------- .../resources/subexpression_unparsed.baseline | 12 +- .../dev/cel/runtime/DefaultInterpreter.java | 23 +- .../cel/runtime/RuntimeUnknownResolver.java | 29 +- 9 files changed, 825 insertions(+), 542 deletions(-) diff --git a/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java index 33dd9db39..bc98c9816 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelBindingsExtensionsTest.java @@ -38,6 +38,7 @@ import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeFactory; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.junit.runner.RunWith; @@ -243,4 +244,76 @@ public void lazyBinding_withNestedBinds() throws Exception { assertThat(result).isTrue(); assertThat(invocation.get()).isEqualTo(2); } + + @Test + @SuppressWarnings({"Immutable", "unchecked"}) // Test only + public void lazyBinding_boundAttributeInComprehension() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.MAP) + .addLibraries(CelExtensions.bindings()) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "get_true", + CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) + .build(); + AtomicInteger invocation = new AtomicInteger(); + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addFunctionBindings( + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + })) + .build(); + + CelAbstractSyntaxTree ast = + celCompiler.compile("cel.bind(x, get_true(), [1,2,3].map(y, y < 0 || x))").getAst(); + + List result = (List) celRuntime.createProgram(ast).eval(); + + assertThat(result).containsExactly(true, true, true); + assertThat(invocation.get()).isEqualTo(1); + } + + @Test + @SuppressWarnings({"Immutable"}) // Test only + public void lazyBinding_boundAttributeInNestedComprehension() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.EXISTS) + .addLibraries(CelExtensions.bindings()) + .addFunctionDeclarations( + CelFunctionDecl.newFunctionDeclaration( + "get_true", + CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) + .build(); + AtomicInteger invocation = new AtomicInteger(); + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addFunctionBindings( + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + })) + .build(); + + CelAbstractSyntaxTree ast = + celCompiler + .compile( + "cel.bind(x, get_true(), [1,2,3].exists(unused, x && " + + "['a','b','c'].exists(unused_2, x)))") + .getAst(); + + boolean result = (boolean) celRuntime.createProgram(ast).eval(); + + assertThat(result).isTrue(); + assertThat(invocation.get()).isEqualTo(1); + } } diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java index eceb0bbe1..46a063a8d 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java @@ -16,11 +16,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static java.util.stream.Collectors.toCollection; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -41,8 +43,11 @@ import dev.cel.common.CelVarDecl; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; +import dev.cel.common.ast.CelExpr.CelComprehension; +import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelExpr.ExprKind.Kind; import dev.cel.common.ast.CelMutableExpr; +import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension; import dev.cel.common.ast.CelMutableExprConverter; import dev.cel.common.navigation.CelNavigableExpr; import dev.cel.common.navigation.CelNavigableMutableAst; @@ -60,6 +65,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Stream; /** * Performs Common Subexpression Elimination. @@ -90,14 +96,15 @@ public class SubexpressionOptimizer implements CelAstOptimizer { private static final SubexpressionOptimizer INSTANCE = new SubexpressionOptimizer(SubexpressionOptimizerOptions.newBuilder().build()); private static final String BIND_IDENTIFIER_PREFIX = "@r"; - private static final String MANGLED_COMPREHENSION_ITER_VAR_PREFIX = "@it"; - private static final String MANGLED_COMPREHENSION_ITER_VAR2_PREFIX = "@it2"; - private static final String MANGLED_COMPREHENSION_ACCU_VAR_PREFIX = "@ac"; private static final String CEL_BLOCK_FUNCTION = "cel.@block"; private static final String BLOCK_INDEX_PREFIX = "@index"; private static final Extension CEL_BLOCK_AST_EXTENSION_TAG = Extension.create("cel_block", Version.of(1L, 1L), Component.COMPONENT_RUNTIME); + @VisibleForTesting static final String MANGLED_COMPREHENSION_ITER_VAR_PREFIX = "@it"; + @VisibleForTesting static final String MANGLED_COMPREHENSION_ITER_VAR2_PREFIX = "@it2"; + @VisibleForTesting static final String MANGLED_COMPREHENSION_ACCU_VAR_PREFIX = "@ac"; + private final SubexpressionOptimizerOptions cseOptions; private final AstMutator astMutator; private final ImmutableSet cseEliminableFunctions; @@ -269,6 +276,8 @@ static void verifyOptimizedAstCorrectness(CelAbstractSyntaxTree ast) { Verify.verify( resultHasAtLeastOneBlockIndex, "Expected at least one reference of index in cel.block result"); + + verifyNoInvalidScopedMangledVariables(celBlockExpr); } private static void verifyBlockIndex(CelExpr celExpr, int maxIndexValue) { @@ -289,6 +298,67 @@ private static void verifyBlockIndex(CelExpr celExpr, int maxIndexValue) { celExpr); } + private static void verifyNoInvalidScopedMangledVariables(CelExpr celExpr) { + CelCall celBlockCall = celExpr.call(); + CelExpr blockBody = celBlockCall.args().get(1); + + ImmutableSet allMangledVariablesInBlockBody = + CelNavigableExpr.fromExpr(blockBody) + .allNodes() + .map(CelNavigableExpr::expr) + .flatMap(SubexpressionOptimizer::extractMangledNames) + .collect(toImmutableSet()); + + CelList blockIndices = celBlockCall.args().get(0).list(); + for (CelExpr blockIndex : blockIndices.elements()) { + ImmutableSet indexDeclaredCompVariables = + CelNavigableExpr.fromExpr(blockIndex) + .allNodes() + .map(CelNavigableExpr::expr) + .filter(expr -> expr.getKind() == Kind.COMPREHENSION) + .map(CelExpr::comprehension) + .flatMap(comp -> Stream.of(comp.iterVar(), comp.iterVar2())) + .filter(iter -> !Strings.isNullOrEmpty(iter)) + .collect(toImmutableSet()); + + boolean containsIllegalDeclaration = + CelNavigableExpr.fromExpr(blockIndex) + .allNodes() + .map(CelNavigableExpr::expr) + .filter(expr -> expr.getKind() == Kind.IDENT) + .map(expr -> expr.ident().name()) + .filter(SubexpressionOptimizer::isMangled) + .anyMatch( + ident -> + !indexDeclaredCompVariables.contains(ident) + && allMangledVariablesInBlockBody.contains(ident)); + + Verify.verify( + !containsIllegalDeclaration, + "Illegal declared reference to a comprehension variable found in block indices. Expr: %s", + celExpr); + } + } + + private static Stream extractMangledNames(CelExpr expr) { + if (expr.getKind().equals(Kind.IDENT)) { + String name = expr.ident().name(); + return isMangled(name) ? Stream.of(name) : Stream.empty(); + } + if (expr.getKind().equals(Kind.COMPREHENSION)) { + CelComprehension comp = expr.comprehension(); + return Stream.of(comp.iterVar(), comp.iterVar2(), comp.accuVar()) + .filter(x -> !Strings.isNullOrEmpty(x)) + .filter(SubexpressionOptimizer::isMangled); + } + return Stream.empty(); + } + + private static boolean isMangled(String name) { + return name.startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) + || name.startsWith(MANGLED_COMPREHENSION_ITER_VAR2_PREFIX); + } + private static CelAbstractSyntaxTree tagAstExtension(CelAbstractSyntaxTree ast) { // Tag the extension CelSource.Builder celSourceBuilder = @@ -355,8 +425,8 @@ private List getCseCandidatesWithRecursionDepth( navAst .getRoot() .descendants(TraversalOrder.PRE_ORDER) - .filter(node -> canEliminate(node, ineligibleExprs)) .filter(node -> node.height() <= recursionLimit) + .filter(node -> canEliminate(node, ineligibleExprs)) .sorted(Comparator.comparingInt(CelNavigableMutableExpr::height).reversed()) .collect(toImmutableList()); if (descendants.isEmpty()) { @@ -441,7 +511,45 @@ private boolean canEliminate( && navigableExpr.expr().list().elements().isEmpty()) && containsEliminableFunctionOnly(navigableExpr) && !ineligibleExprs.contains(navigableExpr.expr()) - && containsComprehensionIdentInSubexpr(navigableExpr); + && containsComprehensionIdentInSubexpr(navigableExpr) + && containsProperScopedComprehensionIdents(navigableExpr); + } + + private boolean containsProperScopedComprehensionIdents(CelNavigableMutableExpr navExpr) { + if (!navExpr.getKind().equals(Kind.COMPREHENSION)) { + return true; + } + + // For nested comprehensions of form [1].exists(x, [2].exists(y, x == y)), the inner + // comprehension [2].exists(y, x == y) + // should not be extracted out into a block index, as it causes issues with scoping. + ImmutableSet mangledIterVars = + navExpr + .descendants() + .filter(x -> x.getKind().equals(Kind.IDENT)) + .map(x -> x.expr().ident().name()) + .filter( + name -> + name.startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) + || name.startsWith(MANGLED_COMPREHENSION_ITER_VAR2_PREFIX)) + .collect(toImmutableSet()); + + CelNavigableMutableExpr parent = navExpr.parent().orElse(null); + while (parent != null) { + if (parent.getKind().equals(Kind.COMPREHENSION)) { + CelMutableComprehension comp = parent.expr().comprehension(); + boolean containsParentIterReferences = + mangledIterVars.contains(comp.iterVar()) || mangledIterVars.contains(comp.iterVar2()); + + if (containsParentIterReferences) { + return false; + } + } + + parent = parent.parent().orElse(null); + } + + return true; } private boolean containsComprehensionIdentInSubexpr(CelNavigableMutableExpr navExpr) { diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java index c6999e46b..96bdf719e 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java @@ -55,6 +55,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeFactory; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.junit.runner.RunWith; @@ -381,6 +382,31 @@ public void lazyEval_blockIndexEvaluatedOnlyOnce() throws Exception { assertThat(invocation.get()).isEqualTo(1); } + @Test + @SuppressWarnings({"Immutable", "unchecked"}) // Test only + public void lazyEval_withinComprehension_blockIndexEvaluatedOnlyOnce() throws Exception { + AtomicInteger invocation = new AtomicInteger(); + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .addFunctionBindings( + CelFunctionBinding.from( + "get_true_overload", + ImmutableList.of(), + arg -> { + invocation.getAndIncrement(); + return true; + })) + .build(); + CelAbstractSyntaxTree ast = + compileUsingInternalFunctions("cel.block([get_true()], [1,2,3].map(x, x < 0 || index0))"); + + List result = (List) celRuntime.createProgram(ast).eval(); + + assertThat(result).containsExactly(true, true, true); + assertThat(invocation.get()).isEqualTo(1); + } + @Test @SuppressWarnings("Immutable") // Test only public void lazyEval_multipleBlockIndices_inResultExpr() throws Exception { @@ -452,9 +478,9 @@ public void lazyEval_nestedComprehension_indexReferencedInNestedScopes() throws // Equivalent of [true, false, true].map(c0, [c0].map(c1, [c0, c1, true])) CelAbstractSyntaxTree ast = compileUsingInternalFunctions( - "cel.block([c0, c1, get_true()], [index2, false, index2].map(c0, [c0].map(c1, [index0," - + " index1, index2]))) == [[[true, true, true]], [[false, false, true]], [[true," - + " true, true]]]"); + "cel.block([true, false, get_true()], [index2, false, index2].map(c0, [c0].map(c1, [c0," + + " c1, index2]))) == [[[true, true, true]], [[false, false, true]], [[true, true," + + " true]]]"); boolean result = (boolean) celRuntime.createProgram(ast).eval(); diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline index c3de5d723..2159ae348 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline @@ -2280,105 +2280,122 @@ CALL [1] { args: { LIST [2] { elements: { - COMPREHENSION [3] { - iter_var: @it:1:0 - iter_range: { + LIST [3] { + elements: { LIST [4] { elements: { CONSTANT [5] { value: 1 } - CONSTANT [6] { value: 2 } - CONSTANT [7] { value: 3 } } } - } - accu_var: @ac:1:0 - accu_init: { - LIST [8] { + LIST [6] { elements: { + CONSTANT [7] { value: 2 } } } } - loop_condition: { - CONSTANT [9] { value: true } - } - loop_step: { - CALL [10] { - function: _?_:_ - args: { - CALL [11] { - function: _==_ - args: { - IDENT [12] { - name: @it:1:0 - } - IDENT [13] { - name: @it:0:0 - } - } - } - CALL [14] { - function: _+_ - args: { - IDENT [15] { - name: @ac:1:0 - } - LIST [16] { - elements: { - IDENT [17] { - name: @it:1:0 - } - } - } - } - } - IDENT [18] { - name: @ac:1:0 - } - } - } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } } - result: { - IDENT [19] { - name: @ac:1:0 - } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } } } } } - CALL [20] { + CALL [15] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [16] { iter_var: @it:0:0 iter_range: { - LIST [22] { - elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } - } + IDENT [17] { + name: @index1 } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [18] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [19] { value: true } } loop_step: { - CALL [27] { + CALL [20] { function: _+_ args: { - IDENT [28] { + IDENT [21] { name: @ac:0:0 } - LIST [29] { + LIST [22] { elements: { - IDENT [30] { - name: @index0 + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _==_ + args: { + IDENT [29] { + name: @it:1:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:1:0 + } + LIST [33] { + elements: { + IDENT [34] { + name: @it:1:0 + } + } + } + } + } + IDENT [35] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [36] { + name: @ac:1:0 + } + } } } } @@ -2386,24 +2403,13 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [37] { name: @ac:0:0 } } } - LIST [32] { - elements: { - LIST [33] { - elements: { - CONSTANT [34] { value: 1 } - } - } - LIST [35] { - elements: { - CONSTANT [36] { value: 2 } - } - } - } + IDENT [38] { + name: @index0 } } } @@ -2557,122 +2563,139 @@ CALL [1] { args: { LIST [2] { elements: { - COMPREHENSION [3] { - iter_var: @it:1:0 - iter_range: { + LIST [3] { + elements: { LIST [4] { elements: { CONSTANT [5] { value: 1 } - CONSTANT [6] { value: 2 } - CONSTANT [7] { value: 3 } } } - } - accu_var: @ac:1:0 - accu_init: { - LIST [8] { + LIST [6] { elements: { + CONSTANT [7] { value: 2 } } } } - loop_condition: { - CONSTANT [9] { value: true } - } - loop_step: { - CALL [10] { - function: _?_:_ - args: { - CALL [11] { - function: _&&_ - args: { - CALL [12] { - function: _==_ - args: { - IDENT [13] { - name: @it:1:0 - } - IDENT [14] { - name: @it2:0:0 - } - } - } - CALL [15] { - function: _<_ - args: { - IDENT [16] { - name: @it:0:0 - } - IDENT [17] { - name: @it2:0:0 - } - } - } - } - } - CALL [18] { - function: _+_ - args: { - IDENT [19] { - name: @ac:1:0 - } - LIST [20] { - elements: { - IDENT [21] { - name: @it:1:0 - } - } - } - } - } - IDENT [22] { - name: @ac:1:0 - } - } - } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } } - result: { - IDENT [23] { - name: @ac:1:0 - } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } } } } } - CALL [24] { + CALL [15] { function: _==_ args: { - COMPREHENSION [25] { + COMPREHENSION [16] { iter_var: @it:0:0 iter_var2: @it2:0:0 iter_range: { - LIST [26] { - elements: { - CONSTANT [27] { value: 1 } - CONSTANT [28] { value: 2 } - } + IDENT [17] { + name: @index1 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { + LIST [18] { elements: { } } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [19] { value: true } } loop_step: { - CALL [31] { + CALL [20] { function: _+_ args: { - IDENT [32] { + IDENT [21] { name: @ac:0:0 } - LIST [33] { + LIST [22] { elements: { - IDENT [34] { - name: @index0 + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _&&_ + args: { + CALL [29] { + function: _==_ + args: { + IDENT [30] { + name: @it:1:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CALL [32] { + function: _<_ + args: { + IDENT [33] { + name: @it:0:0 + } + IDENT [34] { + name: @it2:0:0 + } + } + } + } + } + CALL [35] { + function: _+_ + args: { + IDENT [36] { + name: @ac:1:0 + } + LIST [37] { + elements: { + IDENT [38] { + name: @it:1:0 + } + } + } + } + } + IDENT [39] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [40] { + name: @ac:1:0 + } + } } } } @@ -2680,24 +2703,13 @@ CALL [1] { } } result: { - IDENT [35] { + IDENT [41] { name: @ac:0:0 } } } - LIST [36] { - elements: { - LIST [37] { - elements: { - CONSTANT [38] { value: 1 } - } - } - LIST [39] { - elements: { - CONSTANT [40] { value: 2 } - } - } - } + IDENT [42] { + name: @index0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline index e9797057a..37a3d60a8 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline @@ -2262,105 +2262,122 @@ CALL [1] { args: { LIST [2] { elements: { - COMPREHENSION [3] { - iter_var: @it:1:0 - iter_range: { + LIST [3] { + elements: { LIST [4] { elements: { CONSTANT [5] { value: 1 } - CONSTANT [6] { value: 2 } - CONSTANT [7] { value: 3 } } } - } - accu_var: @ac:1:0 - accu_init: { - LIST [8] { + LIST [6] { elements: { + CONSTANT [7] { value: 2 } } } } - loop_condition: { - CONSTANT [9] { value: true } - } - loop_step: { - CALL [10] { - function: _?_:_ - args: { - CALL [11] { - function: _==_ - args: { - IDENT [12] { - name: @it:1:0 - } - IDENT [13] { - name: @it:0:0 - } - } - } - CALL [14] { - function: _+_ - args: { - IDENT [15] { - name: @ac:1:0 - } - LIST [16] { - elements: { - IDENT [17] { - name: @it:1:0 - } - } - } - } - } - IDENT [18] { - name: @ac:1:0 - } - } - } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } } - result: { - IDENT [19] { - name: @ac:1:0 - } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } } } } } - CALL [20] { + CALL [15] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [16] { iter_var: @it:0:0 iter_range: { - LIST [22] { - elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } - } + IDENT [17] { + name: @index1 } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [18] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [19] { value: true } } loop_step: { - CALL [27] { + CALL [20] { function: _+_ args: { - IDENT [28] { + IDENT [21] { name: @ac:0:0 } - LIST [29] { + LIST [22] { elements: { - IDENT [30] { - name: @index0 + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _==_ + args: { + IDENT [29] { + name: @it:1:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:1:0 + } + LIST [33] { + elements: { + IDENT [34] { + name: @it:1:0 + } + } + } + } + } + IDENT [35] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [36] { + name: @ac:1:0 + } + } } } } @@ -2368,24 +2385,13 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [37] { name: @ac:0:0 } } } - LIST [32] { - elements: { - LIST [33] { - elements: { - CONSTANT [34] { value: 1 } - } - } - LIST [35] { - elements: { - CONSTANT [36] { value: 2 } - } - } - } + IDENT [38] { + name: @index0 } } } @@ -2539,122 +2545,139 @@ CALL [1] { args: { LIST [2] { elements: { - COMPREHENSION [3] { - iter_var: @it:1:0 - iter_range: { + LIST [3] { + elements: { LIST [4] { elements: { CONSTANT [5] { value: 1 } - CONSTANT [6] { value: 2 } - CONSTANT [7] { value: 3 } } } - } - accu_var: @ac:1:0 - accu_init: { - LIST [8] { + LIST [6] { elements: { + CONSTANT [7] { value: 2 } } } } - loop_condition: { - CONSTANT [9] { value: true } - } - loop_step: { - CALL [10] { - function: _?_:_ - args: { - CALL [11] { - function: _&&_ - args: { - CALL [12] { - function: _==_ - args: { - IDENT [13] { - name: @it:1:0 - } - IDENT [14] { - name: @it2:0:0 - } - } - } - CALL [15] { - function: _<_ - args: { - IDENT [16] { - name: @it:0:0 - } - IDENT [17] { - name: @it2:0:0 - } - } - } - } - } - CALL [18] { - function: _+_ - args: { - IDENT [19] { - name: @ac:1:0 - } - LIST [20] { - elements: { - IDENT [21] { - name: @it:1:0 - } - } - } - } - } - IDENT [22] { - name: @ac:1:0 - } - } - } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } } - result: { - IDENT [23] { - name: @ac:1:0 - } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } } } } } - CALL [24] { + CALL [15] { function: _==_ args: { - COMPREHENSION [25] { + COMPREHENSION [16] { iter_var: @it:0:0 iter_var2: @it2:0:0 iter_range: { - LIST [26] { - elements: { - CONSTANT [27] { value: 1 } - CONSTANT [28] { value: 2 } - } + IDENT [17] { + name: @index1 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { + LIST [18] { elements: { } } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [19] { value: true } } loop_step: { - CALL [31] { + CALL [20] { function: _+_ args: { - IDENT [32] { + IDENT [21] { name: @ac:0:0 } - LIST [33] { + LIST [22] { elements: { - IDENT [34] { - name: @index0 + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _&&_ + args: { + CALL [29] { + function: _==_ + args: { + IDENT [30] { + name: @it:1:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CALL [32] { + function: _<_ + args: { + IDENT [33] { + name: @it:0:0 + } + IDENT [34] { + name: @it2:0:0 + } + } + } + } + } + CALL [35] { + function: _+_ + args: { + IDENT [36] { + name: @ac:1:0 + } + LIST [37] { + elements: { + IDENT [38] { + name: @it:1:0 + } + } + } + } + } + IDENT [39] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [40] { + name: @ac:1:0 + } + } } } } @@ -2662,24 +2685,13 @@ CALL [1] { } } result: { - IDENT [35] { + IDENT [41] { name: @ac:0:0 } } } - LIST [36] { - elements: { - LIST [37] { - elements: { - CONSTANT [38] { value: 1 } - } - } - LIST [39] { - elements: { - CONSTANT [40] { value: 2 } - } - } - } + IDENT [42] { + name: @index0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline index d220cfff4..420c6d01b 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline @@ -2256,105 +2256,122 @@ CALL [1] { args: { LIST [2] { elements: { - COMPREHENSION [3] { - iter_var: @it:1:0 - iter_range: { + LIST [3] { + elements: { LIST [4] { elements: { CONSTANT [5] { value: 1 } - CONSTANT [6] { value: 2 } - CONSTANT [7] { value: 3 } } } - } - accu_var: @ac:1:0 - accu_init: { - LIST [8] { + LIST [6] { elements: { + CONSTANT [7] { value: 2 } } } } - loop_condition: { - CONSTANT [9] { value: true } - } - loop_step: { - CALL [10] { - function: _?_:_ - args: { - CALL [11] { - function: _==_ - args: { - IDENT [12] { - name: @it:1:0 - } - IDENT [13] { - name: @it:0:0 - } - } - } - CALL [14] { - function: _+_ - args: { - IDENT [15] { - name: @ac:1:0 - } - LIST [16] { - elements: { - IDENT [17] { - name: @it:1:0 - } - } - } - } - } - IDENT [18] { - name: @ac:1:0 - } - } - } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } } - result: { - IDENT [19] { - name: @ac:1:0 - } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } } } } } - CALL [20] { + CALL [15] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [16] { iter_var: @it:0:0 iter_range: { - LIST [22] { - elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } - } + IDENT [17] { + name: @index1 } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [18] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [19] { value: true } } loop_step: { - CALL [27] { + CALL [20] { function: _+_ args: { - IDENT [28] { + IDENT [21] { name: @ac:0:0 } - LIST [29] { + LIST [22] { elements: { - IDENT [30] { - name: @index0 + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _==_ + args: { + IDENT [29] { + name: @it:1:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:1:0 + } + LIST [33] { + elements: { + IDENT [34] { + name: @it:1:0 + } + } + } + } + } + IDENT [35] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [36] { + name: @ac:1:0 + } + } } } } @@ -2362,24 +2379,13 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [37] { name: @ac:0:0 } } } - LIST [32] { - elements: { - LIST [33] { - elements: { - CONSTANT [34] { value: 1 } - } - } - LIST [35] { - elements: { - CONSTANT [36] { value: 2 } - } - } - } + IDENT [38] { + name: @index0 } } } @@ -2533,122 +2539,139 @@ CALL [1] { args: { LIST [2] { elements: { - COMPREHENSION [3] { - iter_var: @it:1:0 - iter_range: { + LIST [3] { + elements: { LIST [4] { elements: { CONSTANT [5] { value: 1 } - CONSTANT [6] { value: 2 } - CONSTANT [7] { value: 3 } } } - } - accu_var: @ac:1:0 - accu_init: { - LIST [8] { + LIST [6] { elements: { + CONSTANT [7] { value: 2 } } } } - loop_condition: { - CONSTANT [9] { value: true } - } - loop_step: { - CALL [10] { - function: _?_:_ - args: { - CALL [11] { - function: _&&_ - args: { - CALL [12] { - function: _==_ - args: { - IDENT [13] { - name: @it:1:0 - } - IDENT [14] { - name: @it2:0:0 - } - } - } - CALL [15] { - function: _<_ - args: { - IDENT [16] { - name: @it:0:0 - } - IDENT [17] { - name: @it2:0:0 - } - } - } - } - } - CALL [18] { - function: _+_ - args: { - IDENT [19] { - name: @ac:1:0 - } - LIST [20] { - elements: { - IDENT [21] { - name: @it:1:0 - } - } - } - } - } - IDENT [22] { - name: @ac:1:0 - } - } - } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } } - result: { - IDENT [23] { - name: @ac:1:0 - } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } } } } } - CALL [24] { + CALL [15] { function: _==_ args: { - COMPREHENSION [25] { + COMPREHENSION [16] { iter_var: @it:0:0 iter_var2: @it2:0:0 iter_range: { - LIST [26] { - elements: { - CONSTANT [27] { value: 1 } - CONSTANT [28] { value: 2 } - } + IDENT [17] { + name: @index1 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { + LIST [18] { elements: { } } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [19] { value: true } } loop_step: { - CALL [31] { + CALL [20] { function: _+_ args: { - IDENT [32] { + IDENT [21] { name: @ac:0:0 } - LIST [33] { + LIST [22] { elements: { - IDENT [34] { - name: @index0 + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index2 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _?_:_ + args: { + CALL [28] { + function: _&&_ + args: { + CALL [29] { + function: _==_ + args: { + IDENT [30] { + name: @it:1:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CALL [32] { + function: _<_ + args: { + IDENT [33] { + name: @it:0:0 + } + IDENT [34] { + name: @it2:0:0 + } + } + } + } + } + CALL [35] { + function: _+_ + args: { + IDENT [36] { + name: @ac:1:0 + } + LIST [37] { + elements: { + IDENT [38] { + name: @it:1:0 + } + } + } + } + } + IDENT [39] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [40] { + name: @ac:1:0 + } + } } } } @@ -2656,24 +2679,13 @@ CALL [1] { } } result: { - IDENT [35] { + IDENT [41] { name: @ac:0:0 } } } - LIST [36] { - elements: { - LIST [37] { - elements: { - CONSTANT [38] { value: 1 } - } - } - LIST [39] { - elements: { - CONSTANT [40] { value: 2 } - } - } - } + IDENT [42] { + name: @index0 } } } diff --git a/optimizer/src/test/resources/subexpression_unparsed.baseline b/optimizer/src/test/resources/subexpression_unparsed.baseline index 684c0ccb5..892a6fbe5 100644 --- a/optimizer/src/test/resources/subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/subexpression_unparsed.baseline @@ -351,9 +351,9 @@ Result: true [BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [1, 2, 3], [1], [2], [@index2, @index3]], @index0.map(@it:0:0, @index1.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index4) [BLOCK_RECURSION_DEPTH_2]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) [BLOCK_RECURSION_DEPTH_3]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it:0:0)], [1, 2].map(@it:0:0, @index0) == [[1], [2]]) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it:0:0)], [1, 2].map(@it:0:0, @index0) == [[1], [2]]) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it:0:0)], [1, 2].map(@it:0:0, @index0) == [[1], [2]]) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) [BLOCK_RECURSION_DEPTH_7]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_8]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_9]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] @@ -381,9 +381,9 @@ Result: true [BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [1, 2, 3], [1], [2], [@index2, @index3]], @index0.transformList(@it:0:0, @it2:0:0, @index1.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index4) [BLOCK_RECURSION_DEPTH_2]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) [BLOCK_RECURSION_DEPTH_3]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)], [1, 2].transformList(@it:0:0, @it2:0:0, @index0) == [[1], [2]]) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)], [1, 2].transformList(@it:0:0, @it2:0:0, @index0) == [[1], [2]]) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)], [1, 2].transformList(@it:0:0, @it2:0:0, @index0) == [[1], [2]]) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) [BLOCK_RECURSION_DEPTH_7]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_8]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_9]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index 546290a4e..c0a21cc3f 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -982,7 +982,8 @@ private IntermediateResult evalComprehension( .build(); } IntermediateResult accuValue; - if (LazyExpression.isLazilyEvaluable(compre)) { + boolean isLazilyEvaluable = LazyExpression.isLazilyEvaluable(compre); + if (isLazilyEvaluable) { accuValue = IntermediateResult.create(new LazyExpression(compre.accuInit())); } else { accuValue = evalNonstrictly(frame, compre.accuInit()); @@ -1035,7 +1036,13 @@ private IntermediateResult evalComprehension( accuValue = maybeAdaptViewToList(accuValue); - frame.pushScope(Collections.singletonMap(accuVar, accuValue)); + Map scopedAttributes = + Collections.singletonMap(accuVar, accuValue); + if (isLazilyEvaluable) { + frame.pushLazyScope(scopedAttributes); + } else { + frame.pushScope(scopedAttributes); + } IntermediateResult result; try { result = evalInternal(frame, compre.result()); @@ -1051,11 +1058,12 @@ private IntermediateResult evalCelBlock( Map blockList = new HashMap<>(); for (int index = 0; index < exprList.elements().size(); index++) { // Register the block indices as lazily evaluated expressions stored as unique identifiers. + String indexKey = "@index" + index; blockList.put( - "@index" + index, + indexKey, IntermediateResult.create(new LazyExpression(exprList.elements().get(index)))); } - frame.pushScope(Collections.unmodifiableMap(blockList)); + frame.pushLazyScope(Collections.unmodifiableMap(blockList)); return evalInternal(frame, blockCall.args().get(1)); } @@ -1167,6 +1175,13 @@ private void cacheLazilyEvaluatedResult( currentResolver.cacheLazilyEvaluatedResult(name, result); } + private void pushLazyScope(Map scope) { + pushScope(scope); + for (String lazyAttribute : scope.keySet()) { + currentResolver.declareLazyAttribute(lazyAttribute); + } + } + /** Note: we utilize a HashMap instead of ImmutableMap to make lookups faster on string keys. */ private void pushScope(Map scope) { scopeLevel++; diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java b/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java index e9fb9d052..b28729417 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java @@ -116,7 +116,13 @@ DefaultInterpreter.IntermediateResult resolveSimpleName(String name, Long exprId } void cacheLazilyEvaluatedResult(String name, DefaultInterpreter.IntermediateResult result) { - // no-op. Caching is handled in ScopedResolver. + throw new IllegalStateException( + "Internal error: Lazy attributes can only be cached in ScopedResolver."); + } + + void declareLazyAttribute(String attrName) { + throw new IllegalStateException( + "Internal error: Lazy attributes can only be declared in ScopedResolver."); } /** @@ -161,7 +167,26 @@ DefaultInterpreter.IntermediateResult resolveSimpleName(String name, Long exprId @Override void cacheLazilyEvaluatedResult(String name, DefaultInterpreter.IntermediateResult result) { - lazyEvalResultCache.put(name, copyIfMutable(result)); + // Ensure that lazily evaluated result is stored at the proper scope. + // A lazily attribute is first declared when a new cel.bind/cel.block expr is encountered. + // + // If this attribute isn't found in the current scope, we need to walk up the parent scopes + // until we find this declaration. + // + // For example: cel.bind(x, get_true(), ['foo','bar'].map(unused, x && x)) + // + // Here, `x` would be evaluated in map macro's scope, but the result should be stored in + // cel.bind's scope. + if (!lazyEvalResultCache.containsKey(name)) { + parent.cacheLazilyEvaluatedResult(name, result); + } else { + lazyEvalResultCache.put(name, copyIfMutable(result)); + } + } + + @Override + void declareLazyAttribute(String attrName) { + lazyEvalResultCache.put(attrName, null); } /** From 8b4c57954babb51cef0760aded09fd4d93062de4 Mon Sep 17 00:00:00 2001 From: Mike Kruskal Date: Thu, 4 Dec 2025 21:27:28 -0800 Subject: [PATCH 033/100] Migrate to new GeneratorNames library for java codegen naming. This avoid duplicating the complex generator logic, and also adds support for the new edition 2024 features. PiperOrigin-RevId: 840539140 --- .../internal/ProtoJavaQualifiedNames.java | 116 +----------------- 1 file changed, 3 insertions(+), 113 deletions(-) diff --git a/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java b/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java index cf393455b..f27181a50 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoJavaQualifiedNames.java @@ -14,18 +14,10 @@ package dev.cel.common.internal; -import com.google.common.base.CaseFormat; -import com.google.common.base.Joiner; -import com.google.common.base.Strings; -import com.google.common.io.Files; -import com.google.protobuf.DescriptorProtos.FileOptions; import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.EnumDescriptor; import com.google.protobuf.Descriptors.FileDescriptor; -import com.google.protobuf.Descriptors.GenericDescriptor; -import com.google.protobuf.Descriptors.ServiceDescriptor; +import com.google.protobuf.GeneratorNames; import dev.cel.common.annotations.Internal; -import java.util.ArrayDeque; /** * Helper class for constructing a fully qualified Java class name from a protobuf descriptor. @@ -34,10 +26,6 @@ */ @Internal public final class ProtoJavaQualifiedNames { - // Controls how many times we should recursively inspect a nested message for building fully - // qualified java class name before aborting. - private static final int SAFE_RECURSE_LIMIT = 50; - /** * Retrieves the full Java class name from the given descriptor * @@ -48,44 +36,7 @@ public final class ProtoJavaQualifiedNames { * (Nested class with java multiple files disabled) */ public static String getFullyQualifiedJavaClassName(Descriptor descriptor) { - return getFullyQualifiedJavaClassNameImpl(descriptor); - } - - private static String getFullyQualifiedJavaClassNameImpl(GenericDescriptor descriptor) { - StringBuilder fullClassName = new StringBuilder(); - - fullClassName.append(getJavaPackageName(descriptor.getFile())).append("."); - - String javaOuterClass = getJavaOuterClassName(descriptor.getFile()); - if (!Strings.isNullOrEmpty(javaOuterClass)) { - fullClassName.append(javaOuterClass).append("$"); - } - - // Recursively build the target class name in case if the message is nested. - ArrayDeque classNames = new ArrayDeque<>(); - GenericDescriptor d = descriptor; - - int recurseCount = 0; - while (d != null) { - classNames.push(d.getName()); - - if (d instanceof EnumDescriptor) { - d = ((EnumDescriptor) d).getContainingType(); - } else { - d = ((Descriptor) d).getContainingType(); - } - recurseCount++; - if (recurseCount >= SAFE_RECURSE_LIMIT) { - throw new IllegalStateException( - String.format( - "Recursion limit of %d hit while inspecting descriptor: %s", - SAFE_RECURSE_LIMIT, descriptor.getFullName())); - } - } - - Joiner.on("$").appendTo(fullClassName, classNames); - - return fullClassName.toString(); + return GeneratorNames.getBytecodeClassName(descriptor); } /** @@ -94,68 +45,7 @@ private static String getFullyQualifiedJavaClassNameImpl(GenericDescriptor descr * on package name generation */ public static String getJavaPackageName(FileDescriptor fileDescriptor) { - FileOptions options = fileDescriptor.getFile().getOptions(); - StringBuilder javaPackageName = new StringBuilder(); - if (options.hasJavaPackage()) { - javaPackageName.append(options.getJavaPackage()); - } else { - javaPackageName - // CEL-Internal-1 - .append(fileDescriptor.getPackage()); - } - - // CEL-Internal-2 - - return javaPackageName.toString(); - } - - /** - * Gets a wrapping outer class name from the descriptor. The outer class name differs depending on - * the proto options set. See - * https://developers.google.com/protocol-buffers/docs/reference/java-generated#invocation - */ - private static String getJavaOuterClassName(FileDescriptor descriptor) { - FileOptions options = descriptor.getOptions(); - - if (options.getJavaMultipleFiles()) { - // If java_multiple_files is enabled, protoc does not generate a wrapper outer class - return ""; - } - - if (options.hasJavaOuterClassname()) { - return options.getJavaOuterClassname(); - } else { - // If an outer class name is not explicitly set, the name is converted into - // Pascal case based on the snake cased file name - // Ex: messages_proto.proto becomes MessagesProto - String protoFileNameWithoutExtension = - Files.getNameWithoutExtension(descriptor.getFile().getFullName()); - String outerClassName = - CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, protoFileNameWithoutExtension); - if (hasConflictingClassName(descriptor.getFile(), outerClassName)) { - outerClassName += "OuterClass"; - } - return outerClassName; - } - } - - private static boolean hasConflictingClassName(FileDescriptor file, String name) { - for (EnumDescriptor enumDesc : file.getEnumTypes()) { - if (name.equals(enumDesc.getName())) { - return true; - } - } - for (ServiceDescriptor serviceDesc : file.getServices()) { - if (name.equals(serviceDesc.getName())) { - return true; - } - } - for (Descriptor messageDesc : file.getMessageTypes()) { - if (name.equals(messageDesc.getName())) { - return true; - } - } - return false; + return GeneratorNames.getFileJavaPackage(fileDescriptor.toProto()); } private ProtoJavaQualifiedNames() {} From 18a12b6c0f03e66a593a8dc468472803257d5161 Mon Sep 17 00:00:00 2001 From: Stephen Roberts Date: Thu, 11 Dec 2025 05:39:23 -0800 Subject: [PATCH 034/100] Support constant folding in chained SELECT expressions. PiperOrigin-RevId: 843180459 --- .../cel/optimizer/optimizers/ConstantFoldingOptimizer.java | 1 + .../optimizer/optimizers/ConstantFoldingOptimizerTest.java | 5 +++++ 2 files changed, 6 insertions(+) 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 9d61fb19c..ca1744c88 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -256,6 +256,7 @@ private static boolean areChildrenArgConstant(CelNavigableMutableExpr expr) { if (expr.getKind().equals(Kind.CALL) || expr.getKind().equals(Kind.LIST) || expr.getKind().equals(Kind.MAP) + || expr.getKind().equals(Kind.SELECT) || expr.getKind().equals(Kind.STRUCT)) { return expr.children().allMatch(ConstantFoldingOptimizer::areChildrenArgConstant); } diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index 9b3810b99..1a3fac852 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -170,6 +170,11 @@ public class ConstantFoldingOptimizerTest { + " optional.of(4), ?single_uint64: optional.ofNonZeroValue(x)}', expected:" + " 'cel.expr.conformance.proto3.TestAllTypes{single_int64: 1, single_int32: 4," + " ?single_uint64: optional.ofNonZeroValue(x)}'}") + @TestParameters( + "{source: 'TestAllTypes{single_nested_message: TestAllTypes.NestedMessage{bb:" + + " 42}}.single_nested_message.bb', expected: '42'}") + @TestParameters("{source: '{\"a\": 1}[\"a\"]', expected: '1'}") + @TestParameters("{source: '{\"a\": {\"b\": 2}}[\"a\"][\"b\"]', expected: '2'}") @TestParameters("{source: '{\"hello\": \"world\"}.hello == x', expected: '\"world\" == x'}") @TestParameters("{source: '{\"hello\": \"world\"}[\"hello\"] == x', expected: '\"world\" == x'}") @TestParameters("{source: '{\"hello\": \"world\"}.?hello', expected: 'optional.of(\"world\")'}") From b3612d4cbfed981b5195e47d2291bd1e610059cc Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Sun, 14 Dec 2025 17:40:22 -0800 Subject: [PATCH 035/100] Introduce CEL exception classes that correspond to canonical error codes PiperOrigin-RevId: 844506576 --- common/exceptions/BUILD.bazel | 42 +++++++++ .../src/main/java/dev/cel/common/BUILD.bazel | 7 +- .../java/dev/cel/common/CelException.java | 5 -- .../dev/cel/common/CelRuntimeException.java | 10 ++- .../dev/cel/common/exceptions/BUILD.bazel | 87 +++++++++++++++++++ .../CelAttributeNotFoundException.java | 57 ++++++++++++ .../exceptions/CelBadFormatException.java | 32 +++++++ .../exceptions/CelDivideByZeroException.java | 32 +++++++ .../CelIndexOutOfBoundsException.java | 28 ++++++ .../CelInvalidArgumentException.java | 28 ++++++ .../CelNumericOverflowException.java | 35 ++++++++ 11 files changed, 348 insertions(+), 15 deletions(-) create mode 100644 common/exceptions/BUILD.bazel create mode 100644 common/src/main/java/dev/cel/common/exceptions/BUILD.bazel create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java diff --git a/common/exceptions/BUILD.bazel b/common/exceptions/BUILD.bazel new file mode 100644 index 000000000..6ffb2dcb9 --- /dev/null +++ b/common/exceptions/BUILD.bazel @@ -0,0 +1,42 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//:internal"], +) + +java_library( + name = "attribute_not_found", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:attribute_not_found"], +) + +java_library( + name = "divide_by_zero", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:divide_by_zero"], +) + +java_library( + name = "index_out_of_bounds", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:index_out_of_bounds"], +) + +java_library( + name = "bad_format", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:bad_format"], +) + +java_library( + name = "numeric_overflow", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:numeric_overflow"], +) + +java_library( + name = "invalid_argument", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:invalid_argument"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 7cd10f392..a5fffe049 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -205,7 +205,6 @@ java_library( ], deps = [ ":cel_source", - "//:auto_value", "//common/ast:mutable_expr", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -249,7 +248,6 @@ java_library( ":source", ":source_location", "//:auto_value", - "//common/annotations", "//common/ast", "//common/internal", "@maven//:com_google_errorprone_error_prone_annotations", @@ -360,8 +358,5 @@ java_library( srcs = ["Operator.java"], tags = [ ], - deps = [ - "//common/ast", - "@maven//:com_google_guava_guava", - ], + deps = ["@maven//:com_google_guava_guava"], ) diff --git a/common/src/main/java/dev/cel/common/CelException.java b/common/src/main/java/dev/cel/common/CelException.java index 9d80a9ba1..55c8623a4 100644 --- a/common/src/main/java/dev/cel/common/CelException.java +++ b/common/src/main/java/dev/cel/common/CelException.java @@ -27,11 +27,6 @@ public CelException(String message, Throwable cause) { super(message, cause); } - public CelException(String message, CelErrorCode errorCode) { - super(message); - this.errorCode = errorCode; - } - public CelException(String message, Throwable cause, CelErrorCode errorCode) { super(message, cause); this.errorCode = errorCode; diff --git a/common/src/main/java/dev/cel/common/CelRuntimeException.java b/common/src/main/java/dev/cel/common/CelRuntimeException.java index 6f194c474..3856d77f5 100644 --- a/common/src/main/java/dev/cel/common/CelRuntimeException.java +++ b/common/src/main/java/dev/cel/common/CelRuntimeException.java @@ -20,15 +20,17 @@ * Wrapper for an unchecked runtime exception with a CelErrorCode supplied. * *

    Note: This is not to be confused with the notion of CEL Runtime. Use {@code - * CelEvaluationException} instead to signify an evaluation error. - * - *

    TODO: Make this class abstract and define specific exception classes that - * corresponds to the CelErrorCode. + * CelEvaluationException} instead to signify an evaluation error. corresponds to the CelErrorCode. */ @Internal public class CelRuntimeException extends RuntimeException { private final CelErrorCode errorCode; + public CelRuntimeException(String errorMessage, CelErrorCode errorCode) { + super(errorMessage); + this.errorCode = errorCode; + } + public CelRuntimeException(Throwable cause, CelErrorCode errorCode) { super(cause); this.errorCode = errorCode; diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel new file mode 100644 index 000000000..6bd1ad9ca --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel @@ -0,0 +1,87 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//common/exceptions:__pkg__", + "//publish:__pkg__", + ], +) + +java_library( + name = "attribute_not_found", + srcs = ["CelAttributeNotFoundException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + ], +) + +java_library( + name = "divide_by_zero", + srcs = ["CelDivideByZeroException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + ], +) + +java_library( + name = "index_out_of_bounds", + srcs = ["CelIndexOutOfBoundsException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + ], +) + +java_library( + name = "bad_format", + srcs = ["CelBadFormatException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + ], +) + +java_library( + name = "numeric_overflow", + srcs = ["CelNumericOverflowException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + ], +) + +java_library( + name = "invalid_argument", + srcs = ["CelInvalidArgumentException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + ], +) diff --git a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java new file mode 100644 index 000000000..4aa43693d --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java @@ -0,0 +1,57 @@ +// Copyright 2025 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; +import java.util.Arrays; +import java.util.Collection; + +/** Indicates an attempt to access a map or object using an invalid attribute or key. */ +@Internal +public final class CelAttributeNotFoundException extends CelRuntimeException { + + public static CelAttributeNotFoundException of(String message) { + return new CelAttributeNotFoundException(message); + } + + public static CelAttributeNotFoundException forMissingMapKey(String key) { + return new CelAttributeNotFoundException(String.format("key '%s' is not present in map.", key)); + } + + public static CelAttributeNotFoundException forFieldResolution(String... fields) { + return forFieldResolution(Arrays.asList(fields)); + } + + public static CelAttributeNotFoundException forFieldResolution(Collection fields) { + return new CelAttributeNotFoundException(formatErrorMessage(fields)); + } + + private static String formatErrorMessage(Collection fields) { + String maybePlural = ""; + if (fields.size() > 1) { + maybePlural = "s"; + } + + return String.format( + "Error resolving field%s '%s'. Field selections must be performed on messages or maps.", + maybePlural, String.join(", ", fields)); + } + + private CelAttributeNotFoundException(String message) { + super(message, CelErrorCode.ATTRIBUTE_NOT_FOUND); + } +} diff --git a/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java new file mode 100644 index 000000000..92dde0101 --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java @@ -0,0 +1,32 @@ +// Copyright 2025 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; + +/** Indicates that a data conversion failed due to a mismatch in the format specification. */ +@Internal +public final class CelBadFormatException extends CelRuntimeException { + + public CelBadFormatException(Throwable cause) { + super(cause, CelErrorCode.BAD_FORMAT); + } + + public CelBadFormatException(String errorMessage) { + super(errorMessage, CelErrorCode.BAD_FORMAT); + } +} diff --git a/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java new file mode 100644 index 000000000..d433d804c --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java @@ -0,0 +1,32 @@ +// Copyright 2025 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; + +/** Indicates that a division by zero occurred. */ +@Internal +public final class CelDivideByZeroException extends CelRuntimeException { + + public CelDivideByZeroException() { + super("/ by zero", CelErrorCode.DIVIDE_BY_ZERO); + } + + public CelDivideByZeroException(Throwable cause) { + super(cause, CelErrorCode.DIVIDE_BY_ZERO); + } +} diff --git a/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java new file mode 100644 index 000000000..3c2d0d03a --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java @@ -0,0 +1,28 @@ +// Copyright 2025 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; + +/** Indicates that a list index access was attempted using an index that is out of bounds. */ +@Internal +public final class CelIndexOutOfBoundsException extends CelRuntimeException { + + public CelIndexOutOfBoundsException(Object index) { + super("Index out of bounds: " + index, CelErrorCode.INDEX_OUT_OF_BOUNDS); + } +} diff --git a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java new file mode 100644 index 000000000..5bbaf6cab --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java @@ -0,0 +1,28 @@ +// Copyright 2025 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; + +/** Indicates that an invalid argument was supplied to a function. */ +@Internal +public final class CelInvalidArgumentException extends CelRuntimeException { + + public CelInvalidArgumentException(Throwable cause) { + super(cause, CelErrorCode.INVALID_ARGUMENT); + } +} diff --git a/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java new file mode 100644 index 000000000..439d1348f --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java @@ -0,0 +1,35 @@ +// Copyright 2025 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; + +/** + * Indicates that a numeric overflow occurred due to arithmetic operations or conversions resulting + * in a value outside the representable range. + */ +@Internal +public final class CelNumericOverflowException extends CelRuntimeException { + + public CelNumericOverflowException(String message) { + super(message, CelErrorCode.NUMERIC_OVERFLOW); + } + + public CelNumericOverflowException(Throwable cause) { + super(cause, CelErrorCode.NUMERIC_OVERFLOW); + } +} From c26e26dbcd09be023607db3695bd17d8db2be6d5 Mon Sep 17 00:00:00 2001 From: Stephen Roberts Date: Sun, 14 Dec 2025 17:48:04 -0800 Subject: [PATCH 036/100] Simplify expression numbering logic in CelExprFactory PiperOrigin-RevId: 844507936 --- .../src/main/java/dev/cel/common/ast/CelExprFactory.java | 7 ------- parser/src/main/java/dev/cel/parser/Parser.java | 8 +------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/common/src/main/java/dev/cel/common/ast/CelExprFactory.java b/common/src/main/java/dev/cel/common/ast/CelExprFactory.java index 9ad23952e..b69dab05c 100644 --- a/common/src/main/java/dev/cel/common/ast/CelExprFactory.java +++ b/common/src/main/java/dev/cel/common/ast/CelExprFactory.java @@ -264,12 +264,5 @@ protected long nextExprId() { return ++exprId; } - /** Attempts to decrement the next expr ID if possible. */ - protected void maybeDeleteId(long id) { - if (id == exprId - 1) { - exprId--; - } - } - protected CelExprFactory() {} } diff --git a/parser/src/main/java/dev/cel/parser/Parser.java b/parser/src/main/java/dev/cel/parser/Parser.java index a0bef54bf..af860e936 100644 --- a/parser/src/main/java/dev/cel/parser/Parser.java +++ b/parser/src/main/java/dev/cel/parser/Parser.java @@ -686,7 +686,7 @@ private Optional visitMacro( CelExpr.newBuilder().setCall(callExpr.build()).build()); } - exprFactory.maybeDeleteId(expr.id()); + sourceInfo.removePositions(expr.id()); return expandedMacro; } @@ -1165,12 +1165,6 @@ private long nextExprId(int position) { return exprId; } - @Override - protected void maybeDeleteId(long id) { - sourceInfo.removePositions(id); - super.maybeDeleteId(id); - } - @Override public long copyExprId(long id) { return nextExprId(getPosition(id)); From d240eb966d14f05bdced5d34321bfc8590e326f3 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 15 Dec 2025 19:11:11 -0800 Subject: [PATCH 037/100] Reject block indices that leads to cycles PiperOrigin-RevId: 845021315 --- .../SubexpressionOptimizerTest.java | 23 ++++++++++ .../dev/cel/runtime/DefaultInterpreter.java | 45 ++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java index 96bdf719e..735cd24f0 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerTest.java @@ -52,6 +52,7 @@ import dev.cel.parser.CelStandardMacro; import dev.cel.parser.CelUnparser; import dev.cel.parser.CelUnparserFactory; +import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeFactory; @@ -579,6 +580,28 @@ public void verifyOptimizedAstCorrectness_indexIsNotForwardReferencing_throws(St .contains("Illegal block index found. The index value must be less than"); } + @Test + public void block_containsCycle_throws() throws Exception { + CelAbstractSyntaxTree ast = compileUsingInternalFunctions("cel.block([index1,index0],index0)"); + + CelEvaluationException e = + assertThrows(CelEvaluationException.class, () -> CEL.createProgram(ast).eval()); + assertThat(e).hasMessageThat().contains("Cycle detected: @index0"); + } + + @Test + public void block_lazyEvaluationContainsError_cleansUpCycleState() throws Exception { + CelAbstractSyntaxTree ast = + compileUsingInternalFunctions( + "cel.block([1/0 > 0], (index0 && false) || (index0 && true))"); + + CelEvaluationException e = + assertThrows(CelEvaluationException.class, () -> CEL.createProgram(ast).eval()); + + assertThat(e).hasMessageThat().contains("/ by zero"); + assertThat(e).hasMessageThat().doesNotContain("Cycle detected"); + } + /** * Converts AST containing cel.block related test functions to internal functions (e.g: cel.block * -> cel.@block) diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index c0a21cc3f..47e3d3b13 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; /** Default implementation of the CEL interpreter. */ @ThreadSafe @@ -343,7 +344,13 @@ private IntermediateResult resolveIdent(ExecutionFrame frame, CelExpr expr, Stri Object value = rawResult.value(); boolean isLazyExpression = value instanceof LazyExpression; if (isLazyExpression) { - value = evalInternal(frame, ((LazyExpression) value).celExpr).value(); + frame.markLazyEvaluationOrThrow(name); + + try { + value = evalInternal(frame, ((LazyExpression) value).celExpr).value(); + } finally { + frame.endLazyEvaluation(name); + } } // Value resolved from Binding, it could be Message, PartialMessage or unbound(null) @@ -1063,9 +1070,14 @@ private IntermediateResult evalCelBlock( indexKey, IntermediateResult.create(new LazyExpression(exprList.elements().get(index)))); } + frame.setRequireCycleCheck(true); frame.pushLazyScope(Collections.unmodifiableMap(blockList)); - return evalInternal(frame, blockCall.args().get(1)); + try { + return evalInternal(frame, blockCall.args().get(1)); + } finally { + frame.popScope(); + } } private CelType getCheckedTypeOrThrow(CelExpr expr) throws CelEvaluationException { @@ -1115,8 +1127,10 @@ static class ExecutionFrame { private final int maxIterations; private final ArrayDeque resolvers; private final Optional lateBoundFunctionResolver; + private final Set activeLazyAttributes = new HashSet<>(); private RuntimeUnknownResolver currentResolver; private int iterations; + private boolean requireCycleCheck; @VisibleForTesting int scopeLevel; private ExecutionFrame( @@ -1132,6 +1146,25 @@ private ExecutionFrame( this.maxIterations = maxIterations; } + private void markLazyEvaluationOrThrow(String name) { + if (!requireCycleCheck) { + return; + } + + boolean added = activeLazyAttributes.add(name); + if (!added) { + throw new IllegalStateException(String.format("Cycle detected: %s", name)); + } + } + + private void endLazyEvaluation(String name) { + if (!requireCycleCheck) { + return; + } + + activeLazyAttributes.remove(name); + } + private Optional getEvaluationListener() { return evaluationListener; } @@ -1175,6 +1208,14 @@ private void cacheLazilyEvaluatedResult( currentResolver.cacheLazilyEvaluatedResult(name, result); } + /** + * If set, interpreter will check for potential cycles for lazily evaluable attributes. This + * only applies for cel.@block indices. + */ + private void setRequireCycleCheck(boolean requireCycleCheck) { + this.requireCycleCheck = requireCycleCheck; + } + private void pushLazyScope(Map scope) { pushScope(scope); for (String lazyAttribute : scope.keySet()) { From 298c3863422c6b472888a00d1a1b863850a11873 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 15 Dec 2025 19:17:27 -0800 Subject: [PATCH 038/100] Track source of evaluation errors in planner, surface error location PiperOrigin-RevId: 845024050 --- .../cel/common/values/CelValueConverter.java | 2 - .../dev/cel/common/values/ErrorValue.java | 6 ++- .../common/values/CelValueConverterTest.java | 9 ---- .../dev/cel/common/values/ErrorValueTest.java | 6 +-- runtime/BUILD.bazel | 8 ++- .../java/dev/cel/runtime/planner/BUILD.bazel | 50 +++++++++++++++++ .../cel/runtime/planner/ErrorMetadata.java | 52 ++++++++++++++++++ .../java/dev/cel/runtime/planner/EvalAnd.java | 14 ++--- .../cel/runtime/planner/EvalAttribute.java | 11 ++-- .../cel/runtime/planner/EvalConditional.java | 9 ++-- .../dev/cel/runtime/planner/EvalConstant.java | 4 +- .../cel/runtime/planner/EvalCreateList.java | 13 +++-- .../cel/runtime/planner/EvalCreateMap.java | 14 ++--- .../cel/runtime/planner/EvalCreateStruct.java | 7 ++- .../dev/cel/runtime/planner/EvalHelpers.java | 22 ++++++-- .../java/dev/cel/runtime/planner/EvalOr.java | 14 ++--- .../dev/cel/runtime/planner/EvalUnary.java | 19 ++++--- .../cel/runtime/planner/EvalVarArgsCall.java | 24 ++++++--- .../cel/runtime/planner/EvalZeroArity.java | 11 ++-- .../runtime/planner/PlannedInterpretable.java | 31 +++++++++++ .../cel/runtime/planner/PlannedProgram.java | 23 ++++---- .../cel/runtime/planner/ProgramPlanner.java | 53 ++++++++++--------- .../runtime/planner/StrictErrorException.java | 42 +++++++++++++++ .../runtime/planner/ProgramPlannerTest.java | 13 ++--- 24 files changed, 336 insertions(+), 121 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index 3ab68c80e..b58790eaf 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -72,8 +72,6 @@ public Object toRuntimeValue(Object value) { .map(this::toRuntimeValue) .map(OptionalValue::create) .orElse(OptionalValue.EMPTY); - } else if (value instanceof Exception) { - return ErrorValue.create((Exception) value); } return normalizePrimitive(value); diff --git a/common/src/main/java/dev/cel/common/values/ErrorValue.java b/common/src/main/java/dev/cel/common/values/ErrorValue.java index 818f86850..6bc04cda4 100644 --- a/common/src/main/java/dev/cel/common/values/ErrorValue.java +++ b/common/src/main/java/dev/cel/common/values/ErrorValue.java @@ -33,6 +33,8 @@ "Immutable") // Exception is technically not immutable as the stacktrace is malleable. public abstract class ErrorValue extends CelValue { + public abstract long exprId(); + @Override public abstract Exception value(); @@ -46,7 +48,7 @@ public CelType celType() { return SimpleType.ERROR; } - public static ErrorValue create(Exception value) { - return new AutoValue_ErrorValue(value); + public static ErrorValue create(long exprId, Exception value) { + return new AutoValue_ErrorValue(exprId, value); } } diff --git a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java index e4d767ef4..308d7b510 100644 --- a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java @@ -34,15 +34,6 @@ public void toRuntimeValue_optionalValue() { assertThat(optionalValue).isEqualTo(OptionalValue.create("test")); } - @Test - public void toRuntimeValue_errorValue() { - IllegalArgumentException e = new IllegalArgumentException("error"); - - ErrorValue errorValue = (ErrorValue) CEL_VALUE_CONVERTER.toRuntimeValue(e); - - assertThat(errorValue.value()).isEqualTo(e); - } - @Test @SuppressWarnings("unchecked") // Test only public void unwrap_optionalValue() { diff --git a/common/src/test/java/dev/cel/common/values/ErrorValueTest.java b/common/src/test/java/dev/cel/common/values/ErrorValueTest.java index 0db711f72..a6a4edb66 100644 --- a/common/src/test/java/dev/cel/common/values/ErrorValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ErrorValueTest.java @@ -27,7 +27,7 @@ public class ErrorValueTest { @Test public void errorValue_construct() { IllegalArgumentException exception = new IllegalArgumentException("test"); - ErrorValue opaqueValue = ErrorValue.create(exception); + ErrorValue opaqueValue = ErrorValue.create(0L, exception); assertThat(opaqueValue.value()).isEqualTo(exception); assertThat(opaqueValue.isZeroValue()).isFalse(); @@ -35,12 +35,12 @@ public void errorValue_construct() { @Test public void create_nullValue_throws() { - assertThrows(NullPointerException.class, () -> ErrorValue.create(null)); + assertThrows(NullPointerException.class, () -> ErrorValue.create(0L, null)); } @Test public void celTypeTest() { - ErrorValue value = ErrorValue.create(new IllegalArgumentException("test")); + ErrorValue value = ErrorValue.create(0L, new IllegalArgumentException("test")); assertThat(value.celType()).isEqualTo(SimpleType.ERROR); } diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index f3b60d4d7..7760d96b8 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -11,10 +11,10 @@ java_library( exports = [ ":evaluation_exception", ":late_function_binding", + ":metadata", "//runtime/src/main/java/dev/cel/runtime", "//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider", "//runtime/src/main/java/dev/cel/runtime:function_overload", - "//runtime/src/main/java/dev/cel/runtime:metadata", "//runtime/src/main/java/dev/cel/runtime:runtime_type_provider", ], ) @@ -249,3 +249,9 @@ cel_android_library( name = "program_android", exports = ["//runtime/src/main/java/dev/cel/runtime:program_android"], ) + +java_library( + name = "metadata", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 3ad0c0173..e7d7b8bdd 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -14,6 +14,7 @@ java_library( ], deps = [ ":attribute", + ":error_metadata", ":eval_and", ":eval_attribute", ":eval_conditional", @@ -25,6 +26,7 @@ java_library( ":eval_unary", ":eval_var_args_call", ":eval_zero_arity", + ":planned_interpretable", ":planned_program", "//:auto_value", "//common:cel_ast", @@ -51,6 +53,9 @@ java_library( name = "planned_program", srcs = ["PlannedProgram.java"], deps = [ + ":error_metadata", + ":planned_interpretable", + ":strict_error_exception", "//:auto_value", "//common:runtime_exception", "//common/values", @@ -68,6 +73,7 @@ java_library( name = "eval_const", srcs = ["EvalConstant.java"], deps = [ + ":planned_interpretable", "//common/values", "//common/values:cel_byte_string", "//runtime:evaluation_listener", @@ -99,6 +105,7 @@ java_library( srcs = ["EvalAttribute.java"], deps = [ ":attribute", + ":planned_interpretable", "//runtime:evaluation_listener", "//runtime:function_resolver", "//runtime:interpretable", @@ -111,6 +118,7 @@ java_library( name = "eval_zero_arity", srcs = ["EvalZeroArity.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -123,6 +131,8 @@ java_library( name = "eval_unary", srcs = ["EvalUnary.java"], deps = [ + ":eval_helpers", + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -135,6 +145,8 @@ java_library( name = "eval_var_args_call", srcs = ["EvalVarArgsCall.java"], deps = [ + ":eval_helpers", + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -148,6 +160,7 @@ java_library( srcs = ["EvalOr.java"], deps = [ ":eval_helpers", + ":planned_interpretable", "//common/values", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -161,6 +174,7 @@ java_library( srcs = ["EvalAnd.java"], deps = [ ":eval_helpers", + ":planned_interpretable", "//common/values", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -173,6 +187,7 @@ java_library( name = "eval_conditional", srcs = ["EvalConditional.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -185,6 +200,7 @@ java_library( name = "eval_create_struct", srcs = ["EvalCreateStruct.java"], deps = [ + ":planned_interpretable", "//common/types", "//common/values", "//common/values:cel_value_provider", @@ -201,6 +217,7 @@ java_library( name = "eval_create_list", srcs = ["EvalCreateList.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -214,6 +231,7 @@ java_library( name = "eval_create_map", srcs = ["EvalCreateMap.java"], deps = [ + ":planned_interpretable", "//runtime:evaluation_exception", "//runtime:evaluation_listener", "//runtime:function_resolver", @@ -227,7 +245,39 @@ java_library( name = "eval_helpers", srcs = ["EvalHelpers.java"], deps = [ + ":planned_interpretable", + ":strict_error_exception", + "//common:error_codes", + "//common:runtime_exception", "//common/values", "//runtime:interpretable", ], ) + +java_library( + name = "strict_error_exception", + srcs = ["StrictErrorException.java"], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + ], +) + +java_library( + name = "error_metadata", + srcs = ["ErrorMetadata.java"], + deps = [ + "//runtime:metadata", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "planned_interpretable", + srcs = ["PlannedInterpretable.java"], + deps = [ + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java b/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java new file mode 100644 index 000000000..2358bd0f9 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/ErrorMetadata.java @@ -0,0 +1,52 @@ +// Copyright 2025 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.runtime.planner; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.Metadata; + +@Immutable +final class ErrorMetadata implements Metadata { + + private final ImmutableMap exprIdToPositionMap; + private final String location; + + @Override + public String getLocation() { + return location; + } + + @Override + public int getPosition(long exprId) { + return exprIdToPositionMap.getOrDefault(exprId, 0); + } + + @Override + public boolean hasPosition(long exprId) { + return exprIdToPositionMap.containsKey(exprId); + } + + static ErrorMetadata create(ImmutableMap exprIdToPositionMap, String location) { + return new ErrorMetadata(exprIdToPositionMap, location); + } + + private ErrorMetadata(ImmutableMap exprIdToPositionMap, String location) { + this.exprIdToPositionMap = checkNotNull(exprIdToPositionMap); + this.location = checkNotNull(location); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java index 4bf9af517..a3a39ce8a 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java @@ -21,17 +21,16 @@ import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; -final class EvalAnd implements Interpretable { +final class EvalAnd extends PlannedInterpretable { @SuppressWarnings("Immutable") - private final Interpretable[] args; + private final PlannedInterpretable[] args; @Override public Object eval(GlobalResolver resolver) { ErrorValue errorValue = null; - for (Interpretable arg : args) { + for (PlannedInterpretable arg : args) { Object argVal = evalNonstrictly(arg, resolver); if (argVal instanceof Boolean) { // Short-circuit on false @@ -75,11 +74,12 @@ public Object eval( throw new UnsupportedOperationException("Not yet supported"); } - static EvalAnd create(Interpretable[] args) { - return new EvalAnd(args); + static EvalAnd create(long exprId, PlannedInterpretable[] args) { + return new EvalAnd(exprId, args); } - private EvalAnd(Interpretable[] args) { + private EvalAnd(long exprId, PlannedInterpretable[] args) { + super(exprId); Preconditions.checkArgument(args.length == 2); this.args = args; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java index 23b7fa63c..6e20d4f59 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -18,10 +18,9 @@ import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; @Immutable -final class EvalAttribute implements Interpretable { +final class EvalAttribute extends PlannedInterpretable { private final Attribute attr; @@ -47,15 +46,15 @@ public Object eval( GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver, CelEvaluationListener listener) { - // TODO: Implement support throw new UnsupportedOperationException("Not yet supported"); } - static EvalAttribute create(Attribute attr) { - return new EvalAttribute(attr); + static EvalAttribute create(long exprId, Attribute attr) { + return new EvalAttribute(exprId, attr); } - private EvalAttribute(Attribute attr) { + private EvalAttribute(long exprId, Attribute attr) { + super(exprId); this.attr = attr; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java index ca32b405c..4445d3e71 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java @@ -21,7 +21,7 @@ import dev.cel.runtime.GlobalResolver; import dev.cel.runtime.Interpretable; -final class EvalConditional implements Interpretable { +final class EvalConditional extends PlannedInterpretable { @SuppressWarnings("Immutable") private final Interpretable[] args; @@ -67,11 +67,12 @@ public Object eval( throw new UnsupportedOperationException("Not yet supported"); } - static EvalConditional create(Interpretable[] args) { - return new EvalConditional(args); + static EvalConditional create(long exprId, Interpretable[] args) { + return new EvalConditional(exprId, args); } - private EvalConditional(Interpretable[] args) { + private EvalConditional(long exprId, Interpretable[] args) { + super(exprId); Preconditions.checkArgument(args.length == 3); this.args = args; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java index a1a2dc998..408d04046 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -21,10 +21,9 @@ import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; @Immutable -final class EvalConstant implements Interpretable { +final class EvalConstant extends PlannedInterpretable { // Pre-allocation of common constants private static final EvalConstant NULL_VALUE = new EvalConstant(NullValue.NULL_VALUE); @@ -115,6 +114,7 @@ static EvalConstant create(Object value) { } private EvalConstant(Object constant) { + super(/* exprId= */ -1); // It's not possible to throw while evaluating a constant this.constant = constant; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java index 6a1917475..4ec275eef 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -23,7 +23,7 @@ import dev.cel.runtime.Interpretable; @Immutable -final class EvalCreateList implements Interpretable { +final class EvalCreateList extends PlannedInterpretable { // Array contents are not mutated @SuppressWarnings("Immutable") @@ -51,17 +51,20 @@ public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctio } @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver, + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, CelEvaluationListener listener) { // TODO: Implement support throw new UnsupportedOperationException("Not yet supported"); } - static EvalCreateList create(Interpretable[] values) { - return new EvalCreateList(values); + static EvalCreateList create(long exprId, Interpretable[] values) { + return new EvalCreateList(exprId, values); } - private EvalCreateList(Interpretable[] values) { + private EvalCreateList(long exprId, Interpretable[] values) { + super(exprId); this.values = values; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java index a3fb2cb85..38d690303 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -24,7 +24,7 @@ import dev.cel.runtime.Interpretable; @Immutable -final class EvalCreateMap implements Interpretable { +final class EvalCreateMap extends PlannedInterpretable { // Array contents are not mutated @SuppressWarnings("Immutable") @@ -58,18 +58,20 @@ public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctio } @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver, + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, CelEvaluationListener listener) { // TODO: Implement support throw new UnsupportedOperationException("Not yet supported"); } - - static EvalCreateMap create(Interpretable[] keys, Interpretable[] values) { - return new EvalCreateMap(keys, values); + static EvalCreateMap create(long exprId, Interpretable[] keys, Interpretable[] values) { + return new EvalCreateMap(exprId, keys, values); } - private EvalCreateMap(Interpretable[] keys, Interpretable[] values) { + private EvalCreateMap(long exprId, Interpretable[] keys, Interpretable[] values) { + super(exprId); Preconditions.checkArgument(keys.length == values.length); this.keys = keys; this.values = values; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java index 5c1f1d77b..7553add80 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -28,7 +28,7 @@ import java.util.Map; @Immutable -final class EvalCreateStruct implements Interpretable { +final class EvalCreateStruct extends PlannedInterpretable { private final CelValueProvider valueProvider; private final StructType structType; @@ -84,18 +84,21 @@ public Object eval( } static EvalCreateStruct create( + long exprId, CelValueProvider valueProvider, StructType structType, String[] keys, Interpretable[] values) { - return new EvalCreateStruct(valueProvider, structType, keys, values); + return new EvalCreateStruct(exprId, valueProvider, structType, keys, values); } private EvalCreateStruct( + long exprId, CelValueProvider valueProvider, StructType structType, String[] keys, Interpretable[] values) { + super(exprId); this.valueProvider = valueProvider; this.structType = structType; this.keys = keys; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index 82bd6124a..3b5bda1bc 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -14,17 +14,33 @@ package dev.cel.runtime.planner; +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; import dev.cel.common.values.ErrorValue; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; final class EvalHelpers { - static Object evalNonstrictly(Interpretable interpretable, GlobalResolver resolver) { + static Object evalNonstrictly(PlannedInterpretable interpretable, GlobalResolver resolver) { try { return interpretable.eval(resolver); + } catch (StrictErrorException e) { + // Intercept the strict exception to get a more localized expr ID for error reporting purposes + // Example: foo [1] && strict_err [2] -> ID 2 is propagated. + return ErrorValue.create(e.exprId(), e); } catch (Exception e) { - return ErrorValue.create(e); + return ErrorValue.create(interpretable.exprId(), e); + } + } + + static Object evalStrictly(PlannedInterpretable interpretable, GlobalResolver resolver) { + try { + return interpretable.eval(resolver); + } catch (CelRuntimeException e) { + throw new StrictErrorException(e, interpretable.exprId()); + } catch (Exception e) { + throw new StrictErrorException( + e.getCause(), CelErrorCode.INTERNAL_ERROR, interpretable.exprId()); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java index afa02dfb8..f287bdd59 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java @@ -21,17 +21,16 @@ import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; -final class EvalOr implements Interpretable { +final class EvalOr extends PlannedInterpretable { @SuppressWarnings("Immutable") - private final Interpretable[] args; + private final PlannedInterpretable[] args; @Override public Object eval(GlobalResolver resolver) { ErrorValue errorValue = null; - for (Interpretable arg : args) { + for (PlannedInterpretable arg : args) { Object argVal = evalNonstrictly(arg, resolver); if (argVal instanceof Boolean) { // Short-circuit on true @@ -75,11 +74,12 @@ public Object eval( throw new UnsupportedOperationException("Not yet supported"); } - static EvalOr create(Interpretable[] args) { - return new EvalOr(args); + static EvalOr create(long exprId, PlannedInterpretable[] args) { + return new EvalOr(exprId, args); } - private EvalOr(Interpretable[] args) { + private EvalOr(long exprId, PlannedInterpretable[] args) { + super(exprId); Preconditions.checkArgument(args.length == 2); this.args = args; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java index c6daff4b2..13b59d11e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -14,21 +14,24 @@ package dev.cel.runtime.planner; +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; +import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; + import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; -final class EvalUnary implements Interpretable { +final class EvalUnary extends PlannedInterpretable { private final CelResolvedOverload resolvedOverload; - private final Interpretable arg; + private final PlannedInterpretable arg; @Override public Object eval(GlobalResolver resolver) throws CelEvaluationException { - Object argVal = arg.eval(resolver); + Object argVal = + resolvedOverload.isStrict() ? evalStrictly(arg, resolver) : evalNonstrictly(arg, resolver); Object[] arguments = new Object[] {argVal}; return resolvedOverload.getDefinition().apply(arguments); @@ -55,11 +58,13 @@ public Object eval( throw new UnsupportedOperationException("Not yet supported"); } - static EvalUnary create(CelResolvedOverload resolvedOverload, Interpretable arg) { - return new EvalUnary(resolvedOverload, arg); + static EvalUnary create( + long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) { + return new EvalUnary(exprId, resolvedOverload, arg); } - private EvalUnary(CelResolvedOverload resolvedOverload, Interpretable arg) { + private EvalUnary(long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) { + super(exprId); this.resolvedOverload = resolvedOverload; this.arg = arg; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index 48fc7ba04..a2a4c0acc 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -14,25 +14,30 @@ package dev.cel.runtime.planner; +import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; +import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; + import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationListener; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; @SuppressWarnings("Immutable") -final class EvalVarArgsCall implements Interpretable { +final class EvalVarArgsCall extends PlannedInterpretable { private final CelResolvedOverload resolvedOverload; - private final Interpretable[] args; + private final PlannedInterpretable[] args; @Override public Object eval(GlobalResolver resolver) throws CelEvaluationException { Object[] argVals = new Object[args.length]; for (int i = 0; i < args.length; i++) { - Interpretable arg = args[i]; - argVals[i] = arg.eval(resolver); + PlannedInterpretable arg = args[i]; + argVals[i] = + resolvedOverload.isStrict() + ? evalStrictly(arg, resolver) + : evalNonstrictly(arg, resolver); } return resolvedOverload.getDefinition().apply(argVals); @@ -59,11 +64,14 @@ public Object eval( throw new UnsupportedOperationException("Not yet supported"); } - static EvalVarArgsCall create(CelResolvedOverload resolvedOverload, Interpretable[] args) { - return new EvalVarArgsCall(resolvedOverload, args); + static EvalVarArgsCall create( + long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) { + return new EvalVarArgsCall(exprId, resolvedOverload, args); } - private EvalVarArgsCall(CelResolvedOverload resolvedOverload, Interpretable[] args) { + private EvalVarArgsCall( + long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) { + super(exprId); this.resolvedOverload = resolvedOverload; this.args = args; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java index 813b84629..628e4a70f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -19,10 +19,8 @@ import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; - -final class EvalZeroArity implements Interpretable { +final class EvalZeroArity extends PlannedInterpretable { private static final Object[] EMPTY_ARRAY = new Object[0]; private final CelResolvedOverload resolvedOverload; @@ -53,11 +51,12 @@ public Object eval( throw new UnsupportedOperationException("Not yet supported"); } - static EvalZeroArity create(CelResolvedOverload resolvedOverload) { - return new EvalZeroArity(resolvedOverload); + static EvalZeroArity create(long exprId, CelResolvedOverload resolvedOverload) { + return new EvalZeroArity(exprId, resolvedOverload); } - private EvalZeroArity(CelResolvedOverload resolvedOverload) { + private EvalZeroArity(long exprId, CelResolvedOverload resolvedOverload) { + super(exprId); this.resolvedOverload = resolvedOverload; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java new file mode 100644 index 000000000..87a1a7dc4 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java @@ -0,0 +1,31 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.Interpretable; + +@Immutable +abstract class PlannedInterpretable implements Interpretable { + private final long exprId; + + long exprId() { + return exprId; + } + + PlannedInterpretable(long exprId) { + this.exprId = exprId; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index 2c0d402c2..d1214fab0 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -23,14 +23,15 @@ import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; import dev.cel.runtime.Program; import java.util.Map; @Immutable @AutoValue abstract class PlannedProgram implements Program { - abstract Interpretable interpretable(); + abstract PlannedInterpretable interpretable(); + + abstract ErrorMetadata metadata(); @Override public Object eval() throws CelEvaluationException { @@ -48,34 +49,36 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio throw new UnsupportedOperationException("Late bound functions not supported yet"); } - private Object evalOrThrow(Interpretable interpretable, GlobalResolver resolver) + private Object evalOrThrow(PlannedInterpretable interpretable, GlobalResolver resolver) throws CelEvaluationException { try { Object evalResult = interpretable.eval(resolver); if (evalResult instanceof ErrorValue) { ErrorValue errorValue = (ErrorValue) evalResult; - throw newCelEvaluationException(errorValue.value()); + throw newCelEvaluationException(errorValue.exprId(), errorValue.value()); } return evalResult; } catch (RuntimeException e) { - throw newCelEvaluationException(e); + throw newCelEvaluationException(interpretable.exprId(), e); } } - private static CelEvaluationException newCelEvaluationException(Exception e) { + private CelEvaluationException newCelEvaluationException(long exprId, Exception e) { CelEvaluationExceptionBuilder builder; - if (e instanceof CelRuntimeException) { + if (e instanceof StrictErrorException) { // Preserve detailed error, including error codes if one exists. + builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e.getCause()); + } else if (e instanceof CelRuntimeException) { builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e); } else { builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e); } - return builder.build(); + return builder.setMetadata(metadata(), exprId).build(); } - static Program create(Interpretable interpretable) { - return new AutoValue_PlannedProgram(interpretable); + static Program create(PlannedInterpretable interpretable, ErrorMetadata metadata) { + return new AutoValue_PlannedProgram(interpretable, metadata); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index bf7729c0f..252695ed7 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -66,17 +66,19 @@ public final class ProgramPlanner { * CelAbstractSyntaxTree}. */ public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException { - Interpretable plannedInterpretable; + PlannedInterpretable plannedInterpretable; try { plannedInterpretable = plan(ast.getExpr(), PlannerContext.create(ast)); } catch (RuntimeException e) { throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e).build(); } - return PlannedProgram.create(plannedInterpretable); + ErrorMetadata errorMetadata = + ErrorMetadata.create(ast.getSource().getPositionsMap(), ast.getSource().getDescription()); + return PlannedProgram.create(plannedInterpretable, errorMetadata); } - private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { + private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) { switch (celExpr.getKind()) { case CONSTANT: return planConstant(celExpr.constant()); @@ -97,7 +99,7 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) { } } - private Interpretable planConstant(CelConstant celConstant) { + private PlannedInterpretable planConstant(CelConstant celConstant) { switch (celConstant.getKind()) { case NULL_VALUE: return EvalConstant.create(celConstant.nullValue()); @@ -118,16 +120,17 @@ private Interpretable planConstant(CelConstant celConstant) { } } - private Interpretable planIdent(CelExpr celExpr, PlannerContext ctx) { + private PlannedInterpretable planIdent(CelExpr celExpr, PlannerContext ctx) { CelReference ref = ctx.referenceMap().get(celExpr.id()); if (ref != null) { return planCheckedIdent(celExpr.id(), ref, ctx.typeMap()); } - return EvalAttribute.create(attributeFactory.newMaybeAttribute(celExpr.ident().name())); + return EvalAttribute.create( + celExpr.id(), attributeFactory.newMaybeAttribute(celExpr.ident().name())); } - private Interpretable planCheckedIdent( + private PlannedInterpretable planCheckedIdent( long id, CelReference identRef, ImmutableMap typeMap) { if (identRef.value().isPresent()) { return planConstant(identRef.value().get()); @@ -146,10 +149,10 @@ private Interpretable planCheckedIdent( return EvalConstant.create(identType); } - return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name())); + return EvalAttribute.create(id, attributeFactory.newAbsoluteAttribute(identRef.name())); } - private Interpretable planCall(CelExpr expr, PlannerContext ctx) { + private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) { ResolvedFunction resolvedFunction = resolveFunction(expr, ctx.referenceMap()); CelExpr target = resolvedFunction.target().orElse(null); int argCount = expr.call().args().size(); @@ -157,7 +160,7 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) { argCount++; } - Interpretable[] evaluatedArgs = new Interpretable[argCount]; + PlannedInterpretable[] evaluatedArgs = new PlannedInterpretable[argCount]; int offset = 0; if (target != null) { @@ -175,11 +178,11 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) { if (operator != null) { switch (operator) { case LOGICAL_OR: - return EvalOr.create(evaluatedArgs); + return EvalOr.create(expr.id(), evaluatedArgs); case LOGICAL_AND: - return EvalAnd.create(evaluatedArgs); + return EvalAnd.create(expr.id(), evaluatedArgs); case CONDITIONAL: - return EvalConditional.create(evaluatedArgs); + return EvalConditional.create(expr.id(), evaluatedArgs); default: // fall-through } @@ -200,15 +203,15 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) { switch (argCount) { case 0: - return EvalZeroArity.create(resolvedOverload); + return EvalZeroArity.create(expr.id(), resolvedOverload); case 1: - return EvalUnary.create(resolvedOverload, evaluatedArgs[0]); + return EvalUnary.create(expr.id(), resolvedOverload, evaluatedArgs[0]); default: - return EvalVarArgsCall.create(resolvedOverload, evaluatedArgs); + return EvalVarArgsCall.create(expr.id(), resolvedOverload, evaluatedArgs); } } - private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { + private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { CelStruct struct = celExpr.struct(); StructType structType = resolveStructType(struct); @@ -222,28 +225,28 @@ private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { values[i] = plan(entry.value(), ctx); } - return EvalCreateStruct.create(valueProvider, structType, keys, values); + return EvalCreateStruct.create(celExpr.id(), valueProvider, structType, keys, values); } - private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { + private PlannedInterpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { CelList list = celExpr.list(); ImmutableList elements = list.elements(); - Interpretable[] values = new Interpretable[elements.size()]; + PlannedInterpretable[] values = new PlannedInterpretable[elements.size()]; for (int i = 0; i < elements.size(); i++) { values[i] = plan(elements.get(i), ctx); } - return EvalCreateList.create(values); + return EvalCreateList.create(celExpr.id(), values); } - private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { + private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { CelMap map = celExpr.map(); ImmutableList entries = map.entries(); - Interpretable[] keys = new Interpretable[entries.size()]; - Interpretable[] values = new Interpretable[entries.size()]; + PlannedInterpretable[] keys = new PlannedInterpretable[entries.size()]; + PlannedInterpretable[] values = new PlannedInterpretable[entries.size()]; for (int i = 0; i < entries.size(); i++) { CelMap.Entry entry = entries.get(i); @@ -251,7 +254,7 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { values[i] = plan(entry.value(), ctx); } - return EvalCreateMap.create(keys, values); + return EvalCreateMap.create(celExpr.id(), keys, values); } /** diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java new file mode 100644 index 000000000..3acd6ff27 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java @@ -0,0 +1,42 @@ +// Copyright 2025 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.runtime.planner; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; + +/** + * An exception that's raised when a strict call failed to invoke, which includes the source of + * expression ID, along with canonical CelErrorCode. + * + *

    Note that StrictErrorException should not be surfaced directly back to the user. + */ +final class StrictErrorException extends CelRuntimeException { + + private final long exprId; + + long exprId() { + return exprId; + } + + StrictErrorException(CelRuntimeException cause, long exprId) { + this(cause, cause.getErrorCode(), exprId); + } + + StrictErrorException(Throwable cause, CelErrorCode errorCode, long exprId) { + super(cause, errorCode); + this.exprId = exprId; + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 205e2ef8b..bb580cbb3 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -421,7 +421,7 @@ public void plan_call_throws() throws Exception { Program program = PLANNER.plan(ast); CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - assertThat(e).hasMessageThat().contains("evaluation error: Intentional error"); + assertThat(e).hasMessageThat().contains("evaluation error at :5: Intentional error"); assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); } @@ -514,8 +514,8 @@ public void plan_call_logicalOr_throws(String expression) throws Exception { Program program = PLANNER.plan(ast); CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - // TODO: Tag metadata (source loc) - assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasMessageThat().startsWith("evaluation error at :"); + assertThat(e).hasMessageThat().endsWith("/ by zero"); assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } @@ -546,8 +546,8 @@ public void plan_call_logicalAnd_throws(String expression) throws Exception { Program program = PLANNER.plan(ast); CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - // TODO: Tag metadata (source loc) - assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasMessageThat().startsWith("evaluation error at :"); + assertThat(e).hasMessageThat().endsWith("/ by zero"); assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } @@ -576,7 +576,8 @@ public void plan_call_conditional_throws(String expression) throws Exception { Program program = PLANNER.plan(ast); CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - assertThat(e).hasMessageThat().isEqualTo("evaluation error: / by zero"); + assertThat(e).hasMessageThat().startsWith("evaluation error at :"); + assertThat(e).hasMessageThat().endsWith("/ by zero"); assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } From f8aa2c2dd5f4d1d6494a9e5db9ea6c72dbbceb1b Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti <13schishti@gmail.com> Date: Tue, 16 Dec 2025 14:26:42 +0000 Subject: [PATCH 039/100] Upgrade GitHub Actions for Node 24 compatibility --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 6f61e9e22..358cc718c 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -23,7 +23,7 @@ jobs: - run: echo "🐧 Job is running on a ${{ runner.os }} server!" - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." - name: Check out repository code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Bazel uses: bazel-contrib/setup-bazel@0.14.0 with: From 506c2b6861080f64bcbbb4d909291d8a252dc32b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 16 Dec 2025 18:52:17 -0800 Subject: [PATCH 040/100] Restore the behavior for protobuf timestamp/duration field values to reside outside RFC3339 range Related: https://github.com/google/cel-java/issues/890 PiperOrigin-RevId: 845523949 --- .../java/dev/cel/common/internal/BUILD.bazel | 1 + .../cel/common/internal/ProtoTimeUtils.java | 24 ++++--- .../java/dev/cel/common/internal/BUILD.bazel | 1 + .../common/internal/ProtoTimeUtilsTest.java | 72 +++++++++++++++++++ runtime/src/test/resources/wrappers.baseline | 7 +- .../dev/cel/testing/BaseInterpreterTest.java | 6 ++ 6 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 common/src/test/java/dev/cel/common/internal/ProtoTimeUtilsTest.java diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index 690b1cc75..ca8f6375e 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -429,6 +429,7 @@ cel_android_library( deps = [ "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], diff --git a/common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java b/common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java index cc705fccd..36671842d 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoTimeUtils.java @@ -18,6 +18,7 @@ import static com.google.common.math.LongMath.checkedMultiply; import static com.google.common.math.LongMath.checkedSubtract; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.protobuf.Duration; @@ -49,12 +50,16 @@ public final class ProtoTimeUtils { // Timestamp for "0001-01-01T00:00:00Z" - private static final long TIMESTAMP_SECONDS_MIN = -62135596800L; - + @VisibleForTesting + static final long TIMESTAMP_SECONDS_MIN = -62135596800L; // Timestamp for "9999-12-31T23:59:59Z" - private static final long TIMESTAMP_SECONDS_MAX = 253402300799L; - private static final long DURATION_SECONDS_MIN = -315576000000L; - private static final long DURATION_SECONDS_MAX = 315576000000L; + @VisibleForTesting + static final long TIMESTAMP_SECONDS_MAX = 253402300799L; + @VisibleForTesting + static final long DURATION_SECONDS_MIN = -315576000000L; + @VisibleForTesting + static final long DURATION_SECONDS_MAX = 315576000000L; + private static final int MILLIS_PER_SECOND = 1000; private static final int NANOS_PER_SECOND = 1000000000; @@ -344,7 +349,8 @@ public static Timestamp parse(String value) throws ParseException { } } try { - return normalizedTimestamp(seconds, nanos); + Timestamp timestamp = normalizedTimestamp(seconds, nanos); + return checkValid(timestamp); } catch (IllegalArgumentException e) { ParseException ex = new ParseException( @@ -532,8 +538,7 @@ private static Timestamp normalizedTimestamp(long seconds, int nanos) { nanos = nanos + NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) seconds = checkedSubtract(seconds, 1); } - Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); - return checkValid(timestamp); + return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build(); } private static Duration normalizedDuration(long seconds, int nanos) { @@ -549,8 +554,7 @@ private static Duration normalizedDuration(long seconds, int nanos) { nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting) seconds++; // no overflow since seconds is negative (and we're incrementing) } - Duration duration = Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); - return checkValid(duration); + return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build(); } private static String formatNanos(int nanos) { diff --git a/common/src/test/java/dev/cel/common/internal/BUILD.bazel b/common/src/test/java/dev/cel/common/internal/BUILD.bazel index 295c48a6e..33127ce16 100644 --- a/common/src/test/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/internal/BUILD.bazel @@ -28,6 +28,7 @@ java_library( "//common/internal:errors", "//common/internal:proto_equality", "//common/internal:proto_message_factory", + "//common/internal:proto_time_utils", "//common/internal:well_known_proto", "//common/src/test/resources:default_instance_message_test_protos_java_proto", "//common/src/test/resources:service_conflicting_name_java_proto", diff --git a/common/src/test/java/dev/cel/common/internal/ProtoTimeUtilsTest.java b/common/src/test/java/dev/cel/common/internal/ProtoTimeUtilsTest.java new file mode 100644 index 000000000..31ee9a9eb --- /dev/null +++ b/common/src/test/java/dev/cel/common/internal/ProtoTimeUtilsTest.java @@ -0,0 +1,72 @@ +// Copyright 2025 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.common.internal; + +import static com.google.common.truth.Truth.assertThat; +import static dev.cel.common.internal.ProtoTimeUtils.DURATION_SECONDS_MAX; +import static dev.cel.common.internal.ProtoTimeUtils.DURATION_SECONDS_MIN; + +import com.google.protobuf.Duration; +import com.google.protobuf.Timestamp; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import java.time.Instant; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class ProtoTimeUtilsTest { + + @Test + public void toJavaInstant_overRfc3339Range() { + Timestamp ts = + Timestamp.newBuilder().setSeconds(ProtoTimeUtils.TIMESTAMP_SECONDS_MAX + 1).build(); + + Instant instant = ProtoTimeUtils.toJavaInstant(ts); + + assertThat(instant).isEqualTo(Instant.ofEpochSecond(ProtoTimeUtils.TIMESTAMP_SECONDS_MAX + 1)); + } + + @Test + public void toJavaInstant_underRfc3339Range() { + Timestamp ts = + Timestamp.newBuilder().setSeconds(ProtoTimeUtils.TIMESTAMP_SECONDS_MIN - 1).build(); + + Instant instant = ProtoTimeUtils.toJavaInstant(ts); + + assertThat(instant).isEqualTo(Instant.ofEpochSecond(ProtoTimeUtils.TIMESTAMP_SECONDS_MIN - 1)); + } + + @Test + public void toJavaDuration_overRfc3339Range() { + Duration d = Duration.newBuilder() + .setSeconds(DURATION_SECONDS_MAX + 1) + .build(); + + java.time.Duration duration = ProtoTimeUtils.toJavaDuration(d); + + assertThat(duration).isEqualTo(java.time.Duration.ofSeconds(DURATION_SECONDS_MAX + 1)); + } + + @Test + public void toJavaDuration_underRfc3339Range() { + Duration d = Duration.newBuilder() + .setSeconds(DURATION_SECONDS_MIN - 1) + .build(); + + java.time.Duration duration = ProtoTimeUtils.toJavaDuration(d); + + assertThat(duration).isEqualTo(java.time.Duration.ofSeconds(DURATION_SECONDS_MIN - 1)); + } +} diff --git a/runtime/src/test/resources/wrappers.baseline b/runtime/src/test/resources/wrappers.baseline index 4a188e484..fc5d93654 100644 --- a/runtime/src/test/resources/wrappers.baseline +++ b/runtime/src/test/resources/wrappers.baseline @@ -83,4 +83,9 @@ declare dyn_var { } =====> bindings: {dyn_var=NULL_VALUE} -result: NULL_VALUE \ No newline at end of file +result: NULL_VALUE + +Source: google.protobuf.Timestamp{ seconds: 253402300800 } +=====> +bindings: {} +result: +10000-01-01T00:00:00Z \ No newline at end of file diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 096114757..b69e4fb7e 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -2005,6 +2005,12 @@ public void wrappers() throws Exception { declareVariable("dyn_var", SimpleType.DYN); source = "dyn_var"; runTest(ImmutableMap.of("dyn_var", NullValue.NULL_VALUE)); + + clearAllDeclarations(); + // Currently allowed, but will be an error + // See https://github.com/google/cel-spec/pull/501 + source = "google.protobuf.Timestamp{ seconds: 253402300800 }"; + runTest(); } @Test From 52b859c4d3f65396acf8ec02fad4e0107dfce619 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 16 Dec 2025 23:43:10 -0800 Subject: [PATCH 041/100] Enforce bounds on explicit epoch to timestamp conversions PiperOrigin-RevId: 845617691 --- .../main/java/dev/cel/common/internal/DateTimeHelpers.java | 2 +- .../java/dev/cel/runtime/standard/TimestampFunction.java | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java index 9abe39b4b..1d11caf63 100644 --- a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java +++ b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java @@ -205,7 +205,7 @@ private static ZoneId timeZone(String tz) { } /** Throws an {@link IllegalArgumentException} if the given {@link Timestamp} is not valid. */ - private static void checkValid(Instant instant) { + public static void checkValid(Instant instant) { long seconds = instant.getEpochSecond(); if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 34d3a1dc0..34fb15768 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -81,7 +81,11 @@ public enum TimestampOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> { if (celOptions.evaluateCanonicalTypesToNativeValues()) { return CelFunctionBinding.from( - "int64_to_timestamp", Long.class, Instant::ofEpochSecond); + "int64_to_timestamp", Long.class, epochSecond -> { + Instant instant = Instant.ofEpochSecond(epochSecond); + DateTimeHelpers.checkValid(instant); + return instant; + }); } else { return CelFunctionBinding.from( "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp); From 0d980b068e09e821f7fb91191e285355e3ab4035 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 22 Dec 2025 21:09:10 -0800 Subject: [PATCH 042/100] Support attribute qualification, plan select, allow parsed-only ident resolution on enums PiperOrigin-RevId: 847999545 --- .../cel/common/values/CelValueConverter.java | 2 +- .../CelEvaluationExceptionBuilder.java | 12 +- .../dev/cel/runtime/planner/Attribute.java | 55 +----- .../cel/runtime/planner/AttributeFactory.java | 33 ++-- .../java/dev/cel/runtime/planner/BUILD.bazel | 46 ++++- .../cel/runtime/planner/EvalAttribute.java | 16 +- .../planner/InterpretableAttribute.java | 27 +++ .../cel/runtime/planner/MaybeAttribute.java | 81 ++++++++ .../cel/runtime/planner/MissingAttribute.java | 47 +++++ .../runtime/planner/NamespacedAttribute.java | 126 +++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 35 +++- .../dev/cel/runtime/planner/Qualifier.java | 28 +++ .../runtime/planner/RelativeAttribute.java | 70 +++++++ .../cel/runtime/planner/StringQualifier.java | 61 ++++++ .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/ProgramPlannerTest.java | 175 ++++++++++++++++-- 16 files changed, 724 insertions(+), 91 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index b58790eaf..c3f3727a1 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -33,7 +33,7 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -abstract class CelValueConverter { +public abstract class CelValueConverter { /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object unwrap(CelValue celValue) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java index 6aaed4da7..6adbcb672 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java @@ -83,10 +83,16 @@ public static CelEvaluationExceptionBuilder newBuilder(String message, Object... */ @Internal public static CelEvaluationExceptionBuilder newBuilder(CelRuntimeException celRuntimeException) { + // TODO: Temporary until migration is complete. Throwable cause = celRuntimeException.getCause(); - return new CelEvaluationExceptionBuilder(cause.getMessage()) - .setCause(cause) - .setErrorCode(celRuntimeException.getErrorCode()); + String message = + cause == null + ? celRuntimeException.getMessage() + : celRuntimeException.getCause().getMessage(); + + return new CelEvaluationExceptionBuilder(message) + .setErrorCode(celRuntimeException.getErrorCode()) + .setCause(cause); } private CelEvaluationExceptionBuilder(String message) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java index 0ce487ceb..f20c9aadd 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -14,64 +14,13 @@ package dev.cel.runtime.planner; - -import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.CelTypeProvider; -import dev.cel.common.types.TypeType; import dev.cel.runtime.GlobalResolver; +/** Represents a resolvable symbol or path (such as a variable or a field selection). */ @Immutable interface Attribute { Object resolve(GlobalResolver ctx); - final class MaybeAttribute implements Attribute { - private final ImmutableList attributes; - - @Override - public Object resolve(GlobalResolver ctx) { - for (Attribute attr : attributes) { - Object value = attr.resolve(ctx); - if (value != null) { - return value; - } - } - - // TODO: Handle unknowns - throw new UnsupportedOperationException("Unknown attributes is not supported yet"); - } - - MaybeAttribute(ImmutableList attributes) { - this.attributes = attributes; - } - } - - final class NamespacedAttribute implements Attribute { - private final ImmutableList namespacedNames; - private final CelTypeProvider typeProvider; - - @Override - public Object resolve(GlobalResolver ctx) { - for (String name : namespacedNames) { - Object value = ctx.resolve(name); - if (value != null) { - // TODO: apply qualifiers - return value; - } - - TypeType type = typeProvider.findType(name).map(TypeType::create).orElse(null); - if (type != null) { - return type; - } - } - - // TODO: Handle unknowns - throw new UnsupportedOperationException("Unknown attributes is not supported yet"); - } - - NamespacedAttribute(CelTypeProvider typeProvider, ImmutableList namespacedNames) { - this.typeProvider = typeProvider; - this.namespacedNames = namespacedNames; - } - } + Attribute addQualifier(Qualifier qualifier); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java index bda7c46a6..632c6cd91 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java @@ -15,35 +15,46 @@ package dev.cel.runtime.planner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelContainer; import dev.cel.common.types.CelTypeProvider; -import dev.cel.runtime.planner.Attribute.MaybeAttribute; -import dev.cel.runtime.planner.Attribute.NamespacedAttribute; +import dev.cel.common.values.CelValueConverter; @Immutable final class AttributeFactory { - private final CelContainer unusedContainer; + private final CelContainer container; private final CelTypeProvider typeProvider; + private final CelValueConverter celValueConverter; NamespacedAttribute newAbsoluteAttribute(String... names) { - return new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)); + return new NamespacedAttribute(typeProvider, celValueConverter, ImmutableSet.copyOf(names)); } - MaybeAttribute newMaybeAttribute(String... names) { - // TODO: Resolve container names + RelativeAttribute newRelativeAttribute(PlannedInterpretable operand) { + return new RelativeAttribute(operand, celValueConverter); + } + + MaybeAttribute newMaybeAttribute(String name) { return new MaybeAttribute( - ImmutableList.of(new NamespacedAttribute(typeProvider, ImmutableList.copyOf(names)))); + this, + ImmutableList.of( + new NamespacedAttribute( + typeProvider, celValueConverter, container.resolveCandidateNames(name)))); } static AttributeFactory newAttributeFactory( - CelContainer celContainer, CelTypeProvider typeProvider) { - return new AttributeFactory(celContainer, typeProvider); + CelContainer celContainer, + CelTypeProvider typeProvider, + CelValueConverter celValueConverter) { + return new AttributeFactory(celContainer, typeProvider, celValueConverter); } - private AttributeFactory(CelContainer container, CelTypeProvider typeProvider) { - this.unusedContainer = container; + private AttributeFactory( + CelContainer container, CelTypeProvider typeProvider, CelValueConverter celValueConverter) { + this.container = container; this.typeProvider = typeProvider; + this.celValueConverter = celValueConverter; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index e7d7b8bdd..45936203a 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -26,8 +26,11 @@ java_library( ":eval_unary", ":eval_var_args_call", ":eval_zero_arity", + ":interpretable_attribute", ":planned_interpretable", ":planned_program", + ":qualifier", + ":string_qualifier", "//:auto_value", "//common:cel_ast", "//common:container", @@ -36,11 +39,11 @@ java_library( "//common/ast", "//common/types", "//common/types:type_providers", + "//common/values", "//common/values:cel_value_provider", "//runtime:dispatcher", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", - "//runtime:interpretable", "//runtime:program", "//runtime:resolved_overload", "@maven//:com_google_code_findbugs_annotations", @@ -84,28 +87,67 @@ java_library( ], ) +java_library( + name = "interpretable_attribute", + srcs = ["InterpretableAttribute.java"], + deps = [ + ":planned_interpretable", + ":qualifier", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "attribute", srcs = [ "Attribute.java", "AttributeFactory.java", + "MaybeAttribute.java", + "MissingAttribute.java", + "NamespacedAttribute.java", + "RelativeAttribute.java", ], deps = [ + ":eval_helpers", + ":planned_interpretable", + ":qualifier", "//common:container", + "//common/exceptions:attribute_not_found", "//common/types", "//common/types:type_providers", + "//common/values", + "//common/values:cel_value", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) +java_library( + name = "qualifier", + srcs = ["Qualifier.java"], + deps = [ + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + +java_library( + name = "string_qualifier", + srcs = ["StringQualifier.java"], + deps = [ + ":qualifier", + "//common/exceptions:attribute_not_found", + "//common/values", + ], +) + java_library( name = "eval_attribute", srcs = ["EvalAttribute.java"], deps = [ ":attribute", - ":planned_interpretable", + ":interpretable_attribute", + ":qualifier", "//runtime:evaluation_listener", "//runtime:function_resolver", "//runtime:interpretable", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java index 6e20d4f59..826f7e1fa 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -20,13 +20,18 @@ import dev.cel.runtime.GlobalResolver; @Immutable -final class EvalAttribute extends PlannedInterpretable { +final class EvalAttribute extends InterpretableAttribute { private final Attribute attr; @Override public Object eval(GlobalResolver resolver) { - return attr.resolve(resolver); + Object resolved = attr.resolve(resolver); + if (resolved instanceof MissingAttribute) { + ((MissingAttribute) resolved).resolve(resolver); + } + + return resolved; } @Override @@ -46,9 +51,16 @@ public Object eval( GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver, CelEvaluationListener listener) { + // TODO: Implement support throw new UnsupportedOperationException("Not yet supported"); } + @Override + public EvalAttribute addQualifier(long exprId, Qualifier qualifier) { + Attribute newAttribute = attr.addQualifier(qualifier); + return create(exprId, newAttribute); + } + static EvalAttribute create(long exprId, Attribute attr) { return new EvalAttribute(exprId, attr); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java new file mode 100644 index 000000000..547380c11 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/InterpretableAttribute.java @@ -0,0 +1,27 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.errorprone.annotations.Immutable; + +@Immutable +abstract class InterpretableAttribute extends PlannedInterpretable { + + abstract InterpretableAttribute addQualifier(long exprId, Qualifier qualifier); + + InterpretableAttribute(long exprId) { + super(exprId); + } +} \ No newline at end of file diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java new file mode 100644 index 000000000..542067349 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java @@ -0,0 +1,81 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.GlobalResolver; + +/** + * An attribute that attempts to resolve a variable against a list of potential namespaced + * attributes. This is used during parsed-only evaluation. + */ +@Immutable +final class MaybeAttribute implements Attribute { + private final AttributeFactory attrFactory; + private final ImmutableList attributes; + + @Override + public Object resolve(GlobalResolver ctx) { + MissingAttribute maybeError = null; + for (NamespacedAttribute attr : attributes) { + Object value = attr.resolve(ctx); + if (value == null) { + continue; + } + + if (value instanceof MissingAttribute) { + maybeError = (MissingAttribute) value; + // When the variable is missing in a maybe attribute, defer erroring. + // The variable may exist in other namespaced attributes. + continue; + } + + return value; + } + + return maybeError; + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + Object strQualifier = qualifier.value(); + ImmutableList.Builder augmentedNamesBuilder = ImmutableList.builder(); + ImmutableList.Builder attributesBuilder = ImmutableList.builder(); + for (NamespacedAttribute attr : attributes) { + if (strQualifier instanceof String && attr.qualifiers().isEmpty()) { + for (String varName : attr.candidateVariableNames()) { + augmentedNamesBuilder.add(varName + "." + strQualifier); + } + } + + attributesBuilder.add(attr.addQualifier(qualifier)); + } + ImmutableList augmentedNames = augmentedNamesBuilder.build(); + ImmutableList.Builder namespacedAttributeBuilder = ImmutableList.builder(); + if (!augmentedNames.isEmpty()) { + namespacedAttributeBuilder.add( + attrFactory.newAbsoluteAttribute(augmentedNames.toArray(new String[0]))); + } + + namespacedAttributeBuilder.addAll(attributesBuilder.build()); + return new MaybeAttribute(attrFactory, namespacedAttributeBuilder.build()); + } + + MaybeAttribute(AttributeFactory attrFactory, ImmutableList attributes) { + this.attrFactory = attrFactory; + this.attributes = attributes; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java new file mode 100644 index 000000000..bbb4e0422 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java @@ -0,0 +1,47 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.collect.ImmutableSet; +import dev.cel.common.exceptions.CelAttributeNotFoundException; +import dev.cel.runtime.GlobalResolver; + +/** Represents a missing attribute that is surfaced while resolving a struct field or a map key. */ +final class MissingAttribute implements Attribute { + + private final ImmutableSet missingAttributes; + + @Override + public Object resolve(GlobalResolver ctx) { + throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + throw new UnsupportedOperationException("Unsupported operation"); + } + + static MissingAttribute newMissingAttribute(String... attributeNames) { + return newMissingAttribute(ImmutableSet.copyOf(attributeNames)); + } + + static MissingAttribute newMissingAttribute(ImmutableSet attributeNames) { + return new MissingAttribute(attributeNames); + } + + private MissingAttribute(ImmutableSet missingAttributes) { + this.missingAttributes = missingAttributes; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java new file mode 100644 index 000000000..b90ac0824 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -0,0 +1,126 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.EnumType; +import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.GlobalResolver; +import java.util.NoSuchElementException; + +@Immutable +final class NamespacedAttribute implements Attribute { + private final ImmutableSet namespacedNames; + private final ImmutableList qualifiers; + private final CelValueConverter celValueConverter; + private final CelTypeProvider typeProvider; + + @Override + public Object resolve(GlobalResolver ctx) { + for (String name : namespacedNames) { + Object value = ctx.resolve(name); + if (value != null) { + if (!qualifiers.isEmpty()) { + return applyQualifiers(value, celValueConverter, qualifiers); + } else { + return value; + } + } + + CelType type = typeProvider.findType(name).orElse(null); + if (type != null) { + if (qualifiers.isEmpty()) { + // Resolution of a fully qualified type name: foo.bar.baz + return TypeType.create(type); + } else { + // This is potentially a fully qualified reference to an enum value + if (type instanceof EnumType && qualifiers.size() == 1) { + EnumType enumType = (EnumType) type; + String strQualifier = (String) qualifiers.get(0).value(); + return enumType + .findNumberByName(strQualifier) + .orElseThrow( + () -> + new NoSuchElementException( + String.format( + "Field %s was not found on enum %s", + enumType.name(), strQualifier))); + } + } + + throw new IllegalStateException( + "Unexpected type resolution when there were remaining qualifiers: " + type.name()); + } + } + + return MissingAttribute.newMissingAttribute(namespacedNames); + } + + ImmutableList qualifiers() { + return qualifiers; + } + + ImmutableSet candidateVariableNames() { + return namespacedNames; + } + + @Override + public NamespacedAttribute addQualifier(Qualifier qualifier) { + return new NamespacedAttribute( + typeProvider, + celValueConverter, + namespacedNames, + ImmutableList.builder().addAll(qualifiers).add(qualifier).build()); + } + + private static Object applyQualifiers( + Object value, CelValueConverter celValueConverter, ImmutableList qualifiers) { + Object obj = celValueConverter.toRuntimeValue(value); + + for (Qualifier qualifier : qualifiers) { + obj = qualifier.qualify(obj); + } + + if (obj instanceof CelValue) { + obj = celValueConverter.unwrap((CelValue) obj); + } + + return obj; + } + + NamespacedAttribute( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter, + ImmutableSet namespacedNames) { + this(typeProvider, celValueConverter, namespacedNames, ImmutableList.of()); + } + + private NamespacedAttribute( + CelTypeProvider typeProvider, + CelValueConverter celValueConverter, + ImmutableSet namespacedNames, + ImmutableList qualifiers) { + this.typeProvider = typeProvider; + this.celValueConverter = celValueConverter; + this.namespacedNames = namespacedNames; + this.qualifiers = qualifiers; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 252695ed7..be197649f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -37,12 +37,12 @@ import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.StructType; import dev.cel.common.types.TypeType; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.DefaultDispatcher; -import dev.cel.runtime.Interpretable; import dev.cel.runtime.Program; import java.util.NoSuchElementException; import java.util.Optional; @@ -84,6 +84,8 @@ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) { return planConstant(celExpr.constant()); case IDENT: return planIdent(celExpr, ctx); + case SELECT: + return planSelect(celExpr, ctx); case CALL: return planCall(celExpr, ctx); case LIST: @@ -99,6 +101,27 @@ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) { } } + private PlannedInterpretable planSelect(CelExpr celExpr, PlannerContext ctx) { + CelSelect select = celExpr.select(); + PlannedInterpretable operand = plan(select.operand(), ctx); + + InterpretableAttribute attribute; + if (operand instanceof EvalAttribute) { + attribute = (EvalAttribute) operand; + } else { + attribute = + EvalAttribute.create(celExpr.id(), attributeFactory.newRelativeAttribute(operand)); + } + + if (select.testOnly()) { + throw new UnsupportedOperationException("Presence tests not supported yet"); + } + + Qualifier qualifier = StringQualifier.create(select.field()); + + return attribute.addQualifier(celExpr.id(), qualifier); + } + private PlannedInterpretable planConstant(CelConstant celConstant) { switch (celConstant.getKind()) { case NULL_VALUE: @@ -217,7 +240,7 @@ private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ct ImmutableList entries = struct.entries(); String[] keys = new String[entries.size()]; - Interpretable[] values = new Interpretable[entries.size()]; + PlannedInterpretable[] values = new PlannedInterpretable[entries.size()]; for (int i = 0; i < entries.size(); i++) { Entry entry = entries.get(i); @@ -403,19 +426,23 @@ public static ProgramPlanner newPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher, + CelValueConverter celValueConverter, CelContainer container) { - return new ProgramPlanner(typeProvider, valueProvider, dispatcher, container); + return new ProgramPlanner( + typeProvider, valueProvider, dispatcher, celValueConverter, container); } private ProgramPlanner( CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher, + CelValueConverter celValueConverter, CelContainer container) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; this.dispatcher = dispatcher; this.container = container; - this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider); + this.attributeFactory = + AttributeFactory.newAttributeFactory(container, typeProvider, celValueConverter); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java new file mode 100644 index 000000000..82e48e95a --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/Qualifier.java @@ -0,0 +1,28 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.errorprone.annotations.Immutable; + +/** + * Represents a qualification step (such as a field selection or map key lookup) applied to an + * intermediate value during attribute resolution. + */ +@Immutable +interface Qualifier { + Object value(); + + Object qualify(Object value); +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java new file mode 100644 index 000000000..7357d8147 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java @@ -0,0 +1,70 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.GlobalResolver; + +/** + * An attribute resolved relative to a base expression (operand) by applying a sequence of + * qualifiers. + */ +@Immutable +final class RelativeAttribute implements Attribute { + + private final PlannedInterpretable operand; + private final CelValueConverter celValueConverter; + private final ImmutableList qualifiers; + + @Override + public Object resolve(GlobalResolver ctx) { + Object obj = EvalHelpers.evalStrictly(operand, ctx); + obj = celValueConverter.toRuntimeValue(obj); + + for (Qualifier qualifier : qualifiers) { + obj = qualifier.qualify(obj); + } + + // TODO: Handle unknowns + + return obj; + } + + @Override + public Attribute addQualifier(Qualifier qualifier) { + return new RelativeAttribute( + this.operand, + celValueConverter, + ImmutableList.builderWithExpectedSize(qualifiers.size() + 1) + .addAll(this.qualifiers) + .add(qualifier) + .build()); + } + + RelativeAttribute(PlannedInterpretable operand, CelValueConverter celValueConverter) { + this(operand, celValueConverter, ImmutableList.of()); + } + + private RelativeAttribute( + PlannedInterpretable operand, + CelValueConverter celValueConverter, + ImmutableList qualifiers) { + this.operand = operand; + this.celValueConverter = celValueConverter; + this.qualifiers = qualifiers; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java new file mode 100644 index 000000000..4ceaa0e51 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java @@ -0,0 +1,61 @@ +// Copyright 2025 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.runtime.planner; + +import dev.cel.common.exceptions.CelAttributeNotFoundException; +import dev.cel.common.values.SelectableValue; +import java.util.Map; + +/** A qualifier that accesses fields or map keys using a string identifier. */ +final class StringQualifier implements Qualifier { + + private final String value; + + @Override + public String value() { + return value; + } + + @Override + @SuppressWarnings("unchecked") // Qualifications on maps/structs must be a string + public Object qualify(Object obj) { + if (obj instanceof SelectableValue) { + return ((SelectableValue) obj).select(value); + } else if (obj instanceof Map) { + Map map = (Map) obj; + if (!map.containsKey(value)) { + throw CelAttributeNotFoundException.forMissingMapKey(value); + } + + Object mapVal = map.get(value); + + if (mapVal == null) { + throw CelAttributeNotFoundException.of( + String.format("Map value cannot be null for key: %s", value)); + } + return map.get(value); + } + + throw CelAttributeNotFoundException.forFieldResolution(value); + } + + static StringQualifier create(String value) { + return new StringQualifier(value); + } + + private StringQualifier(String value) { + this.value = value; + } +} diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 9e3a79ed9..9e5855f54 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -33,6 +33,7 @@ java_library( "//common/values", "//common/values:cel_byte_string", "//common/values:cel_value_provider", + "//common/values:proto_message_value", "//common/values:proto_message_value_provider", "//compiler", "//compiler:compiler_builder", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index bb580cbb3..60b629f7b 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -51,15 +51,19 @@ import dev.cel.common.types.OptionalType; import dev.cel.common.types.ProtoMessageTypeProvider; import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.NullValue; +import dev.cel.common.values.ProtoCelValueConverter; import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.expr.conformance.proto3.GlobalEnum; import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage; import dev.cel.extensions.CelExtensions; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; @@ -99,17 +103,26 @@ public final class ProgramPlannerTest { private static final DynamicProto DYNAMIC_PROTO = DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL)); private static final CelValueProvider VALUE_PROVIDER = - ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); + ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO); + private static final CelValueConverter CEL_VALUE_CONVERTER = + ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, DYNAMIC_PROTO); private static final CelContainer CEL_CONTAINER = - CelContainer.newBuilder().setName("cel.expr.conformance.proto3").build(); + CelContainer.newBuilder() + .setName("cel.expr.conformance.proto3") + .addAbbreviations("really.long.abbr") + .build(); private static final ProgramPlanner PLANNER = - ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_CONTAINER); + ProgramPlanner.newPlanner( + TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER); + private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) .addVar("int_var", SimpleType.INT) .addVar("dyn_var", SimpleType.DYN) + .addVar("really.long.abbr.ident", SimpleType.DYN) .addFunctionDeclarations( newFunctionDeclaration("zero", newGlobalOverload("zero_overload", SimpleType.INT)), newFunctionDeclaration("error", newGlobalOverload("error_overload", SimpleType.INT)), @@ -296,10 +309,6 @@ public void plan_constant(@TestParameter ConstantTestCase testCase) throws Excep @Test public void plan_ident_enum() throws Exception { - if (isParseOnly) { - // TODO Skip for now, requires attribute qualification - return; - } CelAbstractSyntaxTree ast = compile(GlobalEnum.getDescriptor().getFullName() + "." + GlobalEnum.GAR); Program program = PLANNER.plan(ast); @@ -321,14 +330,6 @@ public void plan_ident_variable() throws Exception { @Test public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { - if (isParseOnly) { - if (testCase.equals(TypeLiteralTestCase.DURATION) - || testCase.equals(TypeLiteralTestCase.TIMESTAMP) - || testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) { - // TODO Skip for now, requires attribute qualification - return; - } - } CelAbstractSyntaxTree ast = compile(testCase.expression); Program program = PLANNER.plan(ast); @@ -337,6 +338,16 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } + @Test + public void plan_ident_withContainer() throws Exception { + CelAbstractSyntaxTree ast = compile("abbr.ident"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("really.long.abbr.ident", 1L)); + + assertThat(result).isEqualTo(1); + } + @Test @SuppressWarnings("unchecked") // test only public void plan_createList() throws Exception { @@ -597,6 +608,140 @@ public void plan_call_withContainer(String expression) throws Exception { assertThat(result).isEqualTo(8); } + @Test + public void plan_select_protoMessageField() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_string"); + Program program = PLANNER.plan(ast); + + String result = + (String) + program.eval( + ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleString("foo").build())); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void plan_select_nestedProtoMessage() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message"); + NestedMessage nestedMessage = NestedMessage.newBuilder().setBb(42).build(); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "msg", TestAllTypes.newBuilder().setSingleNestedMessage(nestedMessage).build())); + + assertThat(result).isEqualTo(nestedMessage); + } + + @Test + public void plan_select_nestedProtoMessageField() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb"); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "msg", + TestAllTypes.newBuilder() + .setSingleNestedMessage(NestedMessage.newBuilder().setBb(42)) + .build())); + + assertThat(result).isEqualTo(42); + } + + @Test + public void plan_select_safeTraversal() throws Exception { + CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("msg", TestAllTypes.newBuilder().build())); + + assertThat(result).isEqualTo(0L); + } + + @Test + public void plan_select_onCreateStruct() throws Exception { + CelAbstractSyntaxTree ast = + compile("cel.expr.conformance.proto3.TestAllTypes{ single_string: 'foo'}.single_string"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void plan_select_onCreateMap() throws Exception { + CelAbstractSyntaxTree ast = compile("{'foo':'bar'}.foo"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo("bar"); + } + + @Test + public void plan_select_onMapVariable() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + Object result = program.eval(ImmutableMap.of("map_var", ImmutableMap.of("foo", 42L))); + + assertThat(result).isEqualTo(42L); + } + + @Test + public void plan_select_mapVarInputMissing_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + String errorMessage = "evaluation error at :7: Error resolving "; + if (isParseOnly) { + errorMessage += + "fields 'cel.expr.conformance.proto3.map_var, cel.expr.conformance.map_var," + + " cel.expr.map_var, cel.map_var, map_var'"; + } else { + errorMessage += "field 'map_var'"; + } + + CelEvaluationException e = + assertThrows(CelEvaluationException.class, () -> program.eval(ImmutableMap.of())); + + assertThat(e).hasMessageThat().contains(errorMessage); + } + + @Test + public void plan_select_mapVarKeyMissing_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("map_var", ImmutableMap.of()))); + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :7: key 'foo' is not present in map"); + } + + @Test + public void plan_select_stringQualificationFail_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("map_var.foo"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> program.eval(ImmutableMap.of("map_var", "bogus string"))); + + assertThat(e) + .hasMessageThat() + .isEqualTo( + "evaluation error at :7: Error resolving field 'foo'. Field selections must be" + + " performed on messages or maps."); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { From 40d81de3826c1850fd1754960c093ecd04f025d1 Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Mon, 22 Dec 2025 21:58:53 -0800 Subject: [PATCH 043/100] No public description PiperOrigin-RevId: 848012551 --- testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel | 3 --- 1 file changed, 3 deletions(-) diff --git a/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel b/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel index 8ae4ea580..755ef732d 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel +++ b/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel @@ -6,9 +6,6 @@ load("//testing/testrunner:cel_java_test.bzl", "cel_java_test") package( default_applicable_licenses = ["//:license"], default_testonly = 1, - default_visibility = [ - "//testing/testrunner:__pkg__", - ], ) # Since the user test class is triggered by the cel_test rule, we should not add it to the From a0de42c6c7954b7b6922e9055149858a08cad697 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 22 Dec 2025 22:15:09 -0800 Subject: [PATCH 044/100] Plan PresenceTests PiperOrigin-RevId: 848018549 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 26 +++++++ .../dev/cel/runtime/planner/EvalTestOnly.java | 68 +++++++++++++++++++ .../planner/PresenceTestQualifier.java | 53 +++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 2 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 2 + .../runtime/planner/ProgramPlannerTest.java | 61 +++++++++++++++++ 6 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 45936203a..13a8d5759 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -23,6 +23,7 @@ java_library( ":eval_create_map", ":eval_create_struct", ":eval_or", + ":eval_test_only", ":eval_unary", ":eval_var_args_call", ":eval_zero_arity", @@ -131,6 +132,16 @@ java_library( ], ) +java_library( + name = "presence_test_qualifier", + srcs = ["PresenceTestQualifier.java"], + deps = [ + ":attribute", + ":qualifier", + "//common/values", + ], +) + java_library( name = "string_qualifier", srcs = ["StringQualifier.java"], @@ -156,6 +167,21 @@ java_library( ], ) +java_library( + name = "eval_test_only", + srcs = ["EvalTestOnly.java"], + deps = [ + ":interpretable_attribute", + ":presence_test_qualifier", + ":qualifier", + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + ], +) + java_library( name = "eval_zero_arity", srcs = ["EvalZeroArity.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java new file mode 100644 index 000000000..a48016537 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java @@ -0,0 +1,68 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.GlobalResolver; + +@Immutable +final class EvalTestOnly extends InterpretableAttribute { + + private final InterpretableAttribute attr; + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + return attr.eval(resolver); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public EvalTestOnly addQualifier(long exprId, Qualifier qualifier) { + PresenceTestQualifier presenceTestQualifier = PresenceTestQualifier.create(qualifier.value()); + return new EvalTestOnly(exprId(), attr.addQualifier(exprId, presenceTestQualifier)); + } + + static EvalTestOnly create(long exprId, InterpretableAttribute attr) { + return new EvalTestOnly(exprId, attr); + } + + private EvalTestOnly(long exprId, InterpretableAttribute attr) { + super(exprId); + this.attr = attr; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java new file mode 100644 index 000000000..973182b9b --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java @@ -0,0 +1,53 @@ +// Copyright 2025 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.runtime.planner; + +import static dev.cel.runtime.planner.MissingAttribute.newMissingAttribute; + +import dev.cel.common.values.SelectableValue; +import java.util.Map; + +/** A qualifier for presence testing a field or a map key. */ +final class PresenceTestQualifier implements Qualifier { + + @SuppressWarnings("Immutable") + private final Object value; + + @Override + public Object value() { + return value; + } + + @Override + @SuppressWarnings("unchecked") // SelectableValue cast is safe + public Object qualify(Object obj) { + if (obj instanceof SelectableValue) { + return ((SelectableValue) obj).find(value).isPresent(); + } else if (obj instanceof Map) { + Map map = (Map) obj; + return map.containsKey(value); + } + + return newMissingAttribute(value.toString()); + } + + static PresenceTestQualifier create(Object value) { + return new PresenceTestQualifier(value); + } + + private PresenceTestQualifier(Object value) { + this.value = value; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index be197649f..13d0d10ce 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -114,7 +114,7 @@ private PlannedInterpretable planSelect(CelExpr celExpr, PlannerContext ctx) { } if (select.testOnly()) { - throw new UnsupportedOperationException("Presence tests not supported yet"); + attribute = EvalTestOnly.create(celExpr.id(), attribute); } Qualifier qualifier = StringQualifier.create(select.field()); diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 9e5855f54..01df7c9ee 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -38,6 +38,7 @@ java_library( "//compiler", "//compiler:compiler_builder", "//extensions", + "//parser:macro", "//runtime", "//runtime:dispatcher", "//runtime:function_binding", @@ -48,6 +49,7 @@ java_library( "//runtime/planner:program_planner", "//runtime/standard:add", "//runtime/standard:divide", + "//runtime/standard:dyn", "//runtime/standard:equals", "//runtime/standard:greater", "//runtime/standard:greater_equals", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 60b629f7b..871b5aa95 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -65,6 +65,7 @@ import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage; import dev.cel.extensions.CelExtensions; +import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelFunctionOverload; @@ -76,6 +77,7 @@ import dev.cel.runtime.standard.AddOperator; import dev.cel.runtime.standard.CelStandardFunction; import dev.cel.runtime.standard.DivideOperator; +import dev.cel.runtime.standard.DynFunction; import dev.cel.runtime.standard.EqualsOperator; import dev.cel.runtime.standard.GreaterEqualsOperator; import dev.cel.runtime.standard.GreaterOperator; @@ -118,6 +120,7 @@ public final class ProgramPlannerTest { private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) .addVar("int_var", SimpleType.INT) @@ -175,6 +178,10 @@ private static DefaultDispatcher newDispatcher() { builder, Operator.NOT_STRICTLY_FALSE.getFunction(), fromStandardFunction(NotStrictlyFalseFunction.create())); + addBindings( + builder, + "dyn", + fromStandardFunction(DynFunction.create())); // Custom functions addBindings( @@ -742,6 +749,32 @@ public void plan_select_stringQualificationFail_throws() throws Exception { + " performed on messages or maps."); } + @Test + public void plan_select_presenceTest(@TestParameter PresenceTestCase testCase) throws Exception { + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + boolean result = + (boolean) + program.eval( + ImmutableMap.of("msg", testCase.inputParam, "map_var", testCase.inputParam)); + + assertThat(result).isEqualTo(testCase.expected); + } + + @Test + public void plan_select_badPresenceTest_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("has(dyn([]).invalid)"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e) + .hasMessageThat() + .contains( + "Error resolving field 'invalid'. Field selections must be performed on messages or" + + " maps."); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { @@ -814,4 +847,32 @@ private enum TypeLiteralTestCase { this.type = TypeType.create(type); } } + + + @SuppressWarnings("Immutable") // Test only + private enum PresenceTestCase { + PROTO_FIELD_PRESENT( + "has(msg.single_string)", TestAllTypes.newBuilder().setSingleString("foo").build(), true), + PROTO_FIELD_ABSENT("has(msg.single_string)", TestAllTypes.newBuilder().build(), false), + PROTO_NESTED_FIELD_PRESENT( + "has(msg.single_nested_message.bb)", + TestAllTypes.newBuilder() + .setSingleNestedMessage(NestedMessage.newBuilder().setBb(42).build()) + .build(), + true), + PROTO_NESTED_FIELD_ABSENT( + "has(msg.single_nested_message.bb)", TestAllTypes.newBuilder().build(), false), + PROTO_MAP_KEY_PRESENT("has(map_var.foo)", ImmutableMap.of("foo", "1"), true), + PROTO_MAP_KEY_ABSENT("has(map_var.bar)", ImmutableMap.of(), false); + + private final String expression; + private final Object inputParam; + private final Object expected; + + PresenceTestCase(String expression, Object inputParam, Object expected) { + this.expression = expression; + this.inputParam = inputParam; + this.expected = expected; + } + } } From a11aa1e01c53fbf3796a7a7eccd1c9b69db8aedf Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 22 Dec 2025 22:44:42 -0800 Subject: [PATCH 045/100] Plan Comprehensions PiperOrigin-RevId: 848026572 --- runtime/BUILD.bazel | 8 + .../src/main/java/dev/cel/runtime/BUILD.bazel | 4 +- .../dev/cel/runtime/ConcatenatedListView.java | 11 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 17 ++ .../dev/cel/runtime/planner/EvalFold.java | 204 ++++++++++++++++++ .../cel/runtime/planner/ProgramPlanner.java | 26 ++- .../runtime/planner/ProgramPlannerTest.java | 43 +++- 7 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 7760d96b8..07bfdebbc 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -255,3 +255,11 @@ java_library( visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"], ) + +java_library( + name = "concatenated_list_view", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:concatenated_list_view", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 847092cc3..b55aec00f 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -1142,7 +1142,9 @@ java_library( name = "concatenated_list_view", srcs = ["ConcatenatedListView.java"], # used_by_android - visibility = ["//visibility:private"], + tags = [ + ], + deps = ["//common/annotations"], ) java_library( diff --git a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java b/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java index ac7696751..c15e76f77 100644 --- a/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java +++ b/runtime/src/main/java/dev/cel/runtime/ConcatenatedListView.java @@ -2,7 +2,7 @@ // // 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 aj +// You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // @@ -14,6 +14,7 @@ package dev.cel.runtime; +import dev.cel.common.annotations.Internal; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; @@ -27,8 +28,12 @@ * comprehensions that dispatch `add_list` to concat N lists together). * *

    This does not support any of the standard list operations from {@link java.util.List}. + * + + *

    CEL Library Internals. Do Not Use. */ -final class ConcatenatedListView extends AbstractList { +@Internal +public final class ConcatenatedListView extends AbstractList { private final List> sourceLists; private int totalSize = 0; @@ -36,7 +41,7 @@ final class ConcatenatedListView extends AbstractList { this.sourceLists = new ArrayList<>(); } - ConcatenatedListView(Collection collection) { + public ConcatenatedListView(Collection collection) { this(); addAll(collection); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 13a8d5759..09a082e87 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -22,6 +22,7 @@ java_library( ":eval_create_list", ":eval_create_map", ":eval_create_struct", + ":eval_fold", ":eval_or", ":eval_test_only", ":eval_unary", @@ -309,6 +310,22 @@ java_library( ], ) +java_library( + name = "eval_fold", + srcs = ["EvalFold.java"], + deps = [ + ":planned_interpretable", + "//runtime:concatenated_list_view", + "//runtime:evaluation_exception", + "//runtime:evaluation_listener", + "//runtime:function_resolver", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "eval_helpers", srcs = ["EvalHelpers.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java new file mode 100644 index 000000000..2a8ba1603 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -0,0 +1,204 @@ +// Copyright 2025 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.runtime.planner; + +import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.Immutable; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.ConcatenatedListView; +import dev.cel.runtime.GlobalResolver; +import java.util.Collection; +import java.util.Map; +import org.jspecify.annotations.Nullable; + +@Immutable +final class EvalFold extends PlannedInterpretable { + + private final String accuVar; + private final PlannedInterpretable accuInit; + private final String iterVar; + private final String iterVar2; + private final PlannedInterpretable iterRange; + private final PlannedInterpretable condition; + private final PlannedInterpretable loopStep; + private final PlannedInterpretable result; + + static EvalFold create( + long exprId, + String accuVar, + PlannedInterpretable accuInit, + String iterVar, + String iterVar2, + PlannedInterpretable iterRange, + PlannedInterpretable loopCondition, + PlannedInterpretable loopStep, + PlannedInterpretable result) { + return new EvalFold( + exprId, accuVar, accuInit, iterVar, iterVar2, iterRange, loopCondition, loopStep, result); + } + + private EvalFold( + long exprId, + String accuVar, + PlannedInterpretable accuInit, + String iterVar, + String iterVar2, + PlannedInterpretable iterRange, + PlannedInterpretable condition, + PlannedInterpretable loopStep, + PlannedInterpretable result) { + super(exprId); + this.accuVar = accuVar; + this.accuInit = accuInit; + this.iterVar = iterVar; + this.iterVar2 = iterVar2; + this.iterRange = iterRange; + this.condition = condition; + this.loopStep = loopStep; + this.result = result; + } + + @Override + public Object eval(GlobalResolver resolver) throws CelEvaluationException { + Object iterRangeRaw = iterRange.eval(resolver); + Folder folder = new Folder(resolver, accuVar, iterVar, iterVar2); + folder.accuVal = maybeWrapAccumulator(accuInit.eval(folder)); + + Object result; + if (iterRangeRaw instanceof Map) { + result = evalMap((Map) iterRangeRaw, folder); + } else if (iterRangeRaw instanceof Collection) { + result = evalList((Collection) iterRangeRaw, folder); + } else { + throw new IllegalArgumentException("Unexpected iter_range type: " + iterRangeRaw.getClass()); + } + + return maybeUnwrapAccumulator(result); + } + + @Override + public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + @Override + public Object eval( + GlobalResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) { + // TODO: Implement support + throw new UnsupportedOperationException("Not yet supported"); + } + + private Object evalMap(Map iterRange, Folder folder) throws CelEvaluationException { + for (Map.Entry entry : iterRange.entrySet()) { + folder.iterVarVal = entry.getKey(); + if (!iterVar2.isEmpty()) { + folder.iterVar2Val = entry.getValue(); + } + + boolean cond = (boolean) condition.eval(folder); + if (!cond) { + return result.eval(folder); + } + + // TODO: Introduce comprehension safety controls, such as iteration limit. + folder.accuVal = loopStep.eval(folder); + } + return result.eval(folder); + } + + private Object evalList(Collection iterRange, Folder folder) throws CelEvaluationException { + int index = 0; + for (Object item : iterRange) { + if (iterVar2.isEmpty()) { + folder.iterVarVal = item; + } else { + folder.iterVarVal = (long) index; + folder.iterVar2Val = item; + } + + boolean cond = (boolean) condition.eval(folder); + if (!cond) { + return result.eval(folder); + } + + folder.accuVal = loopStep.eval(folder); + index++; + } + return result.eval(folder); + } + + private static Object maybeWrapAccumulator(Object val) { + if (val instanceof Collection) { + return new ConcatenatedListView<>((Collection) val); + } + // TODO: Introduce mutable map support (for comp v2) + return val; + } + + private static Object maybeUnwrapAccumulator(Object val) { + if (val instanceof ConcatenatedListView) { + return ImmutableList.copyOf((ConcatenatedListView) val); + } + + // TODO: Introduce mutable map support (for comp v2) + return val; + } + + private static class Folder implements GlobalResolver { + private final GlobalResolver resolver; + private final String accuVar; + private final String iterVar; + private final String iterVar2; + + private Object iterVarVal; + private Object iterVar2Val; + private Object accuVal; + + private Folder(GlobalResolver resolver, String accuVar, String iterVar, String iterVar2) { + this.resolver = resolver; + this.accuVar = accuVar; + this.iterVar = iterVar; + this.iterVar2 = iterVar2; + } + + @Override + public @Nullable Object resolve(String name) { + if (name.equals(accuVar)) { + return accuVal; + } + + if (name.equals(iterVar)) { + return this.iterVarVal; + } + + if (name.equals(iterVar2)) { + return this.iterVar2Val; + } + + return resolver.resolve(name); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 13d0d10ce..c751ba88c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -26,6 +26,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; +import dev.cel.common.ast.CelExpr.CelComprehension; import dev.cel.common.ast.CelExpr.CelList; import dev.cel.common.ast.CelExpr.CelMap; import dev.cel.common.ast.CelExpr.CelSelect; @@ -94,10 +95,12 @@ private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) { return planCreateStruct(celExpr, ctx); case MAP: return planCreateMap(celExpr, ctx); + case COMPREHENSION: + return planComprehension(celExpr, ctx); case NOT_SET: throw new UnsupportedOperationException("Unsupported kind: " + celExpr.getKind()); default: - throw new IllegalArgumentException("Not yet implemented kind: " + celExpr.getKind()); + throw new UnsupportedOperationException("Unexpected kind: " + celExpr.getKind()); } } @@ -280,6 +283,27 @@ private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) return EvalCreateMap.create(celExpr.id(), keys, values); } + private PlannedInterpretable planComprehension(CelExpr expr, PlannerContext ctx) { + CelComprehension comprehension = expr.comprehension(); + + PlannedInterpretable accuInit = plan(comprehension.accuInit(), ctx); + PlannedInterpretable iterRange = plan(comprehension.iterRange(), ctx); + PlannedInterpretable loopCondition = plan(comprehension.loopCondition(), ctx); + PlannedInterpretable loopStep = plan(comprehension.loopStep(), ctx); + PlannedInterpretable result = plan(comprehension.result(), ctx); + + return EvalFold.create( + expr.id(), + comprehension.accuVar(), + accuInit, + comprehension.iterVar(), + comprehension.iterVar2(), + iterRange, + loopCondition, + loopStep, + result); + } + /** * resolveFunction determines the call target, function name, and overload name (when unambiguous) * from the given call expr. diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 871b5aa95..642f3e5ca 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -144,7 +144,7 @@ public final class ProgramPlannerTest { newMemberOverload( "bytes_concat_bytes", SimpleType.BYTES, SimpleType.BYTES, SimpleType.BYTES))) .addMessageTypes(TestAllTypes.getDescriptor()) - .addLibraries(CelExtensions.optional()) + .addLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) .setContainer(CEL_CONTAINER) .build(); @@ -178,10 +178,7 @@ private static DefaultDispatcher newDispatcher() { builder, Operator.NOT_STRICTLY_FALSE.getFunction(), fromStandardFunction(NotStrictlyFalseFunction.create())); - addBindings( - builder, - "dyn", - fromStandardFunction(DynFunction.create())); + addBindings(builder, "dyn", fromStandardFunction(DynFunction.create())); // Custom functions addBindings( @@ -663,7 +660,7 @@ public void plan_select_safeTraversal() throws Exception { CelAbstractSyntaxTree ast = compile("msg.single_nested_message.bb"); Program program = PLANNER.plan(ast); - Object result = program.eval(ImmutableMap.of("msg", TestAllTypes.newBuilder().build())); + Object result = program.eval(ImmutableMap.of("msg", TestAllTypes.getDefaultInstance())); assertThat(result).isEqualTo(0L); } @@ -775,6 +772,35 @@ public void plan_select_badPresenceTest_throws() throws Exception { + " maps."); } + @Test + @TestParameters("{expression: '[1,2,3].exists(x, x > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(x, x < 0) == false'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i >= 0 && v > 0) == true'}") + @TestParameters("{expression: '[1,2,3].exists(i, v, i < 0 || v < 0) == false'}") + @TestParameters("{expression: '[1,2,3].map(x, x + 1) == [2,3,4]'}") + public void plan_comprehension_lists(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isTrue(); + } + + @Test + @TestParameters("{expression: '{\"a\": 1, \"b\": 2}.exists(k, k == \"a\")'}") + @TestParameters("{expression: '{\"a\": 1, \"b\": 2}.exists(k, k == \"c\") == false'}") + @TestParameters("{expression: '{\"a\": \"b\", \"c\": \"c\"}.exists(k, v, k == v)'}") + @TestParameters("{expression: '{\"a\": 1, \"b\": 2}.exists(k, v, v == 3) == false'}") + public void plan_comprehension_maps(String expression) throws Exception { + CelAbstractSyntaxTree ast = compile(expression); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isTrue(); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { @@ -848,12 +874,11 @@ private enum TypeLiteralTestCase { } } - @SuppressWarnings("Immutable") // Test only private enum PresenceTestCase { PROTO_FIELD_PRESENT( "has(msg.single_string)", TestAllTypes.newBuilder().setSingleString("foo").build(), true), - PROTO_FIELD_ABSENT("has(msg.single_string)", TestAllTypes.newBuilder().build(), false), + PROTO_FIELD_ABSENT("has(msg.single_string)", TestAllTypes.getDefaultInstance(), false), PROTO_NESTED_FIELD_PRESENT( "has(msg.single_nested_message.bb)", TestAllTypes.newBuilder() @@ -861,7 +886,7 @@ private enum PresenceTestCase { .build(), true), PROTO_NESTED_FIELD_ABSENT( - "has(msg.single_nested_message.bb)", TestAllTypes.newBuilder().build(), false), + "has(msg.single_nested_message.bb)", TestAllTypes.getDefaultInstance(), false), PROTO_MAP_KEY_PRESENT("has(map_var.foo)", ImmutableMap.of("foo", "1"), true), PROTO_MAP_KEY_ABSENT("has(map_var.bar)", ImmutableMap.of(), false); From 11edd6267ced14f4fd242a147b1cac3a65e6e012 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Fri, 26 Dec 2025 14:49:20 -0800 Subject: [PATCH 046/100] Add support for container aliases to CelEnvironment PiperOrigin-RevId: 849273994 --- .../src/main/java/dev/cel/bundle/BUILD.bazel | 2 +- .../java/dev/cel/bundle/CelEnvironment.java | 52 ++++++-- .../cel/bundle/CelEnvironmentExporter.java | 5 +- .../cel/bundle/CelEnvironmentYamlParser.java | 124 +++++++++++++++++- .../bundle/CelEnvironmentYamlSerializer.java | 46 ++++++- .../bundle/CelEnvironmentExporterTest.java | 22 ++++ .../dev/cel/bundle/CelEnvironmentTest.java | 24 +++- .../bundle/CelEnvironmentYamlParserTest.java | 49 +++++++ .../CelEnvironmentYamlSerializerTest.java | 9 +- .../java/dev/cel/common/CelContainer.java | 58 ++++++-- .../test/resources/environment/dump_env.yaml | 11 +- 11 files changed, 373 insertions(+), 29 deletions(-) diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel index 50b78c63b..0201a5807 100644 --- a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel @@ -73,7 +73,6 @@ java_library( "//common/types:type_providers", "//compiler:compiler_builder", "//extensions", - "//extensions:optional_library", "//parser:macro", "//runtime", "@maven//:com_google_errorprone_error_prone_annotations", @@ -103,6 +102,7 @@ java_library( ":environment", ":environment_exception", "//common:compiler_common", + "//common:container", "//common/formats:file_source", "//common/formats:parser_context", "//common/formats:yaml_helper", diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java index 9f33032a8..b54e3ca51 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java @@ -80,10 +80,9 @@ public abstract class CelEnvironment { public abstract String name(); /** - * An optional description of the config (example: location of the file containing the config - * content). + * Container, which captures default namespace and aliases for value resolution. */ - public abstract String container(); + public abstract CelContainer container(); /** * An optional description of the environment (example: location of the file containing the config @@ -124,7 +123,12 @@ public abstract static class Builder { public abstract Builder setDescription(String description); - public abstract Builder setContainer(String container); + public abstract Builder setContainer(CelContainer container); + + @CanIgnoreReturnValue + public Builder setContainer(String container) { + return setContainer(CelContainer.ofName(container)); + } @CanIgnoreReturnValue public Builder addExtensions(ExtensionConfig... extensions) { @@ -182,7 +186,7 @@ public static Builder newBuilder() { return new AutoValue_CelEnvironment.Builder() .setName("") .setDescription("") - .setContainer("") + .setContainer(CelContainer.ofName("")) .setVariables(ImmutableSet.of()) .setFunctions(ImmutableSet.of()); } @@ -195,8 +199,8 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions) CelCompilerBuilder compilerBuilder = celCompiler .toCompilerBuilder() + .setContainer(container()) .setTypeProvider(celTypeProvider) - .setContainer(CelContainer.ofName(container())) .addVarDeclarations( variables().stream() .map(v -> v.toCelVarDecl(celTypeProvider)) @@ -206,10 +210,6 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions) .map(f -> f.toCelFunctionDecl(celTypeProvider)) .collect(toImmutableList())); - if (!container().isEmpty()) { - compilerBuilder.setContainer(CelContainer.ofName(container())); - } - addAllCompilerExtensions(compilerBuilder, celOptions); applyStandardLibrarySubset(compilerBuilder); @@ -683,6 +683,38 @@ public static ExtensionConfig latest(String name) { } } + @AutoValue + abstract static class Alias { + abstract String alias(); + + abstract String qualifiedName(); + + static Builder newBuilder() { + return new AutoValue_CelEnvironment_Alias.Builder(); + } + + @AutoValue.Builder + abstract static class Builder implements RequiredFieldsChecker { + + abstract Optional alias(); + + abstract Optional qualifiedName(); + + abstract Builder setAlias(String alias); + + abstract Builder setQualifiedName(String qualifiedName); + + abstract Alias build(); + + @Override + public ImmutableList requiredFields() { + return ImmutableList.of( + RequiredField.of("alias", this::alias), + RequiredField.of("qualified_name", this::qualifiedName)); + } + } + } + @VisibleForTesting enum CanonicalCelExtension { BINDINGS((options, version) -> CelExtensions.bindings()), diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java index d303e0528..01410ad0d 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java @@ -161,6 +161,9 @@ public static CelEnvironmentExporter.Builder newBuilder() { * */ public CelEnvironment export(Cel cel) { + CelEnvironment.Builder envBuilder = + CelEnvironment.newBuilder().setContainer(cel.toCheckerBuilder().container()); + // Inventory is a full set of declarations and macros that are found in the configuration of // the supplied CEL instance. // @@ -171,8 +174,6 @@ public CelEnvironment export(Cel cel) { Set inventory = new HashSet<>(); collectInventory(inventory, cel); - - CelEnvironment.Builder envBuilder = CelEnvironment.newBuilder(); addExtensionConfigsAndRemoveFromInventory(envBuilder, inventory); addStandardLibrarySubsetAndRemoveFromInventory(envBuilder, inventory); addCustomDecls(envBuilder, inventory); diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java index 3acb73fa5..8c19fcfa6 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import dev.cel.bundle.CelEnvironment.Alias; import dev.cel.bundle.CelEnvironment.ExtensionConfig; import dev.cel.bundle.CelEnvironment.FunctionDecl; import dev.cel.bundle.CelEnvironment.LibrarySubset; @@ -35,6 +36,7 @@ import dev.cel.bundle.CelEnvironment.OverloadDecl; import dev.cel.bundle.CelEnvironment.TypeDecl; import dev.cel.bundle.CelEnvironment.VariableDecl; +import dev.cel.common.CelContainer; import dev.cel.common.CelIssue; import dev.cel.common.formats.CelFileSource; import dev.cel.common.formats.ParserContext; @@ -64,6 +66,8 @@ public final class CelEnvironmentYamlParser { private static final ExtensionConfig ERROR_EXTENSION_DECL = ExtensionConfig.of(ERROR); private static final FunctionSelector ERROR_FUNCTION_SELECTOR = FunctionSelector.create(ERROR, ImmutableSet.of()); + private static final Alias ERROR_ALIAS = + Alias.newBuilder().setAlias(ERROR).setQualifiedName(ERROR).build(); /** Generates a new instance of {@code CelEnvironmentYamlParser}. */ public static CelEnvironmentYamlParser newInstance() { @@ -88,6 +92,124 @@ public CelEnvironment parse(String environmentYamlSource, String description) return parser.parseYaml(environmentYamlSource, description); } + private CelContainer parseContainer(ParserContext ctx, Node node) { + long valueId = ctx.collectMetadata(node); + // Syntax variant 1: "container: `str`" + if (validateYamlType(node, YamlNodeType.STRING, YamlNodeType.TEXT)) { + return CelContainer.ofName(newString(ctx, node)); + } + + // Syntax variant 2: + // container + // name: str + // abbreviations: + // - a1 + // - a2 + // aliases: + // - alias: a1 + // qualified_name: q1 + // - alias: a2 + // qualified_name: q2 + if (!assertYamlType(ctx, valueId, node, YamlNodeType.MAP)) { + return CelContainer.ofName(ERROR); + } + + CelContainer.Builder builder = CelContainer.newBuilder(); + MappingNode variableMap = (MappingNode) node; + for (NodeTuple nodeTuple : variableMap.getValue()) { + Node keyNode = nodeTuple.getKeyNode(); + long keyId = ctx.collectMetadata(keyNode); + Node valueNode = nodeTuple.getValueNode(); + String keyName = ((ScalarNode) keyNode).getValue(); + switch (keyName) { + case "name": + builder.setName(newString(ctx, valueNode)); + break; + case "aliases": + ImmutableSet aliases = parseAliases(ctx, valueNode); + for (Alias alias : aliases) { + builder.addAlias(alias.alias(), alias.qualifiedName()); + } + break; + case "abbreviations": + builder.addAbbreviations(parseAbbreviations(ctx, valueNode)); + break; + default: + ctx.reportError(keyId, String.format("Unsupported container tag: %s", keyName)); + break; + } + } + + return builder.build(); + } + + private ImmutableSet parseAliases(ParserContext ctx, Node node) { + ImmutableSet.Builder aliasSetBuilder = ImmutableSet.builder(); + long valueId = ctx.collectMetadata(node); + if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) { + return aliasSetBuilder.build(); + } + + SequenceNode variableListNode = (SequenceNode) node; + for (Node elementNode : variableListNode.getValue()) { + aliasSetBuilder.add(parseAlias(ctx, elementNode)); + } + + return aliasSetBuilder.build(); + } + + private Alias parseAlias(ParserContext ctx, Node node) { + long id = ctx.collectMetadata(node); + if (!assertYamlType(ctx, id, node, YamlNodeType.MAP)) { + return ERROR_ALIAS; + } + + Alias.Builder builder = Alias.newBuilder(); + MappingNode attrMap = (MappingNode) node; + for (NodeTuple nodeTuple : attrMap.getValue()) { + Node keyNode = nodeTuple.getKeyNode(); + long keyId = ctx.collectMetadata(keyNode); + Node valueNode = nodeTuple.getValueNode(); + String keyName = ((ScalarNode) keyNode).getValue(); + switch (keyName) { + case "alias": + builder.setAlias(newString(ctx, valueNode)); + break; + case "qualified_name": + builder.setQualifiedName(newString(ctx, valueNode)); + break; + default: + ctx.reportError(keyId, String.format("Unsupported alias tag: %s", keyName)); + break; + } + } + + if (!assertRequiredFields(ctx, id, builder.getMissingRequiredFieldNames())) { + return ERROR_ALIAS; + } + + return builder.build(); + } + + private ImmutableSet parseAbbreviations(ParserContext ctx, Node node) { + long valueId = ctx.collectMetadata(node); + if (!assertYamlType(ctx, valueId, node, YamlNodeType.LIST)) { + return ImmutableSet.of(ERROR); + } + + ImmutableSet.Builder builder = ImmutableSet.builder(); + SequenceNode nameListNode = (SequenceNode) node; + for (Node elementNode : nameListNode.getValue()) { + long elementId = ctx.collectMetadata(elementNode); + if (!assertYamlType(ctx, elementId, elementNode, YamlNodeType.STRING)) { + return ImmutableSet.of(ERROR); + } + + builder.add(((ScalarNode) elementNode).getValue()); + } + return builder.build(); + } + private ImmutableSet parseVariables(ParserContext ctx, Node node) { long valueId = ctx.collectMetadata(node); ImmutableSet.Builder variableSetBuilder = ImmutableSet.builder(); @@ -620,7 +742,7 @@ private CelEnvironment.Builder parseConfig(ParserContext ctx, Node node) { builder.setDescription(newString(ctx, valueNode)); break; case "container": - builder.setContainer(newString(ctx, valueNode)); + builder.setContainer(parseContainer(ctx, valueNode)); break; case "variables": builder.setVariables(parseVariables(ctx, valueNode)); diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java index 687bf4ff9..81f206b94 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java @@ -16,10 +16,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import dev.cel.bundle.CelEnvironment.Alias; import dev.cel.bundle.CelEnvironment.LibrarySubset; import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector; import dev.cel.bundle.CelEnvironment.LibrarySubset.OverloadSelector; +import dev.cel.common.CelContainer; import java.util.Comparator; +import java.util.Map; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.nodes.Node; @@ -55,6 +58,8 @@ private CelEnvironmentYamlSerializer() { CelEnvironment.LibrarySubset.FunctionSelector.class, new RepresentFunctionSelector()); this.multiRepresenters.put( CelEnvironment.LibrarySubset.OverloadSelector.class, new RepresentOverloadSelector()); + this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias()); + this.multiRepresenters.put(CelContainer.class, new RepresentContainer()); } public static String toYaml(CelEnvironment environment) { @@ -72,7 +77,9 @@ public Node representData(Object data) { if (!environment.description().isEmpty()) { configMap.put("description", environment.description()); } - if (!environment.container().isEmpty()) { + if (!environment.container().name().isEmpty() + || !environment.container().abbreviations().isEmpty() + || !environment.container().aliases().isEmpty()) { configMap.put("container", environment.container()); } if (!environment.extensions().isEmpty()) { @@ -91,6 +98,43 @@ public Node representData(Object data) { } } + private final class RepresentContainer implements Represent { + + @Override + public Node representData(Object data) { + CelContainer container = (CelContainer) data; + ImmutableMap.Builder configMap = ImmutableMap.builder(); + if (!container.name().isEmpty()) { + configMap.put("name", container.name()); + } + if (!container.abbreviations().isEmpty()) { + configMap.put("abbreviations", container.abbreviations()); + } + if (!container.aliases().isEmpty()) { + ImmutableList.Builder aliases = ImmutableList.builder(); + for (Map.Entry entry : container.aliases().entrySet()) { + aliases.add( + Alias.newBuilder() + .setAlias(entry.getKey()) + .setQualifiedName(entry.getValue()) + .build()); + } + configMap.put("aliases", aliases.build()); + } + return represent(configMap.buildOrThrow()); + } + } + + private final class RepresentAlias implements Represent { + + @Override + public Node representData(Object data) { + Alias alias = (Alias) data; + return represent( + ImmutableMap.of("alias", alias.alias(), "qualified_name", alias.qualifiedName())); + } + } + private final class RepresentExtensionConfig implements Represent { @Override public Node representData(Object data) { diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java index fd178857f..b9a81a662 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java @@ -31,6 +31,7 @@ import dev.cel.bundle.CelEnvironment.OverloadDecl; import dev.cel.bundle.CelEnvironment.TypeDecl; import dev.cel.bundle.CelEnvironment.VariableDecl; +import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; import dev.cel.common.CelOverloadDecl; @@ -238,5 +239,26 @@ public void customVariables() { celEnvironment.variables().stream().map(VariableDecl::name).collect(toImmutableList())) .containsNoneOf("double", "null_type"); } + + @Test + public void container() { + Cel cel = + CelFactory.standardCelBuilder() + .setContainer( + CelContainer.newBuilder() + .setName("cntnr") + .addAbbreviations("foo.Bar", "baz.Qux") + .addAlias("nm", "user.name") + .addAlias("id", "user.id") + .build()) + .build(); + + CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build(); + CelEnvironment celEnvironment = exporter.export(cel); + CelContainer container = celEnvironment.container(); + assertThat(container.name()).isEqualTo("cntnr"); + assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder(); + assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder(); + } } diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java index 56a4a241c..6bc84a48f 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java @@ -24,6 +24,7 @@ import dev.cel.bundle.CelEnvironment.LibrarySubset; import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; import dev.cel.common.CelValidationResult; @@ -43,12 +44,33 @@ public void newBuilder_defaults() { assertThat(environment.source()).isEmpty(); assertThat(environment.name()).isEmpty(); assertThat(environment.description()).isEmpty(); - assertThat(environment.container()).isEmpty(); + assertThat(environment.container().name()).isEmpty(); + assertThat(environment.container().abbreviations()).isEmpty(); + assertThat(environment.container().aliases()).isEmpty(); assertThat(environment.extensions()).isEmpty(); assertThat(environment.variables()).isEmpty(); assertThat(environment.functions()).isEmpty(); } + @Test + public void container() { + CelEnvironment environment = + CelEnvironment.newBuilder() + .setContainer( + CelContainer.newBuilder() + .setName("cntr") + .addAbbreviations("foo.Bar", "baz.Qux") + .addAlias("nm", "user.name") + .addAlias("id", "user.id") + .build()) + .build(); + + assertThat(environment.container().name()).isEqualTo("cntr"); + assertThat(environment.container().abbreviations()).containsExactly("foo.Bar", "baz.Qux"); + assertThat(environment.container().aliases()) + .containsExactly("nm", "user.name", "id", "user.id"); + } + @Test public void extend_allExtensions() throws Exception { ImmutableSet extensionConfigs = diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java index fb51f3f87..d69d0517b 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java @@ -33,6 +33,7 @@ import dev.cel.bundle.CelEnvironment.TypeDecl; import dev.cel.bundle.CelEnvironment.VariableDecl; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; import dev.cel.common.CelSource; import dev.cel.common.ast.CelExpr; @@ -346,6 +347,36 @@ public void environment_setContainer() throws Exception { assertThat(environment.extend(CEL_WITH_MESSAGE_TYPES, CelOptions.DEFAULT)).isNotNull(); } + @Test + public void environment_setContainerWithAliasesAndAbbreviations() throws Exception { + String yamlConfig = + "container:\n" + + " name: 'google.rpc.context'\n" + + " abbreviations:\n" + + " - 'pkg3.lib3.Baz'\n" + + " - pkg4.lib4.Qux\n" + + " aliases:\n" + + " - alias: 'foo'\n" + + " qualified_name: 'pkg1.lib1.Foo'\n" + + " - alias: bar\n" + + " qualified_name: pkg2.lib2.Bar\n"; + + CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig); + + assertThat(environment) + .isEqualTo( + CelEnvironment.newBuilder() + .setContainer( + CelContainer.newBuilder() + .setName("google.rpc.context") + .addAbbreviations("pkg3.lib3.Baz", "pkg4.lib4.Qux") + .addAlias("foo", "pkg1.lib1.Foo") + .addAlias("bar", "pkg2.lib2.Bar") + .build()) + .setSource(environment.source().get()) + .build()); + } + @Test public void environment_withInlinedVariableDecl() throws Exception { String yamlConfig = @@ -623,6 +654,24 @@ private enum EnvironmentParseErrorTestcase { "ERROR: :7:11: Unsupported overload selector tag: unknown_tag\n" + " | unknown_tag: 'test_value'\n" + " | ..........^"), + MISSING_ALIAS_FIELDS( + "container:\n" + + " name: 'test_container'\n" + + " aliases:\n" + + " - qualified_name: 'test_qualified_name'\n", + "ERROR: :4:7: Missing required attribute(s): alias\n" + + " | - qualified_name: 'test_qualified_name'\n" + + " | ......^"), + ILLEGAL_ALIAS_TAG( + "container:\n" + + " name: 'test_container'\n" + + " aliases:\n" + + " - alias: 'test_alias'\n" + + " qualified_name: 'test_qualified_name'\n" + + " unknown_tag: 'test_value'\n", + "ERROR: :6:7: Unsupported alias tag: unknown_tag\n" + + " | unknown_tag: 'test_value'\n" + + " | ......^"), ; private final String yamlConfig; diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java index 2bd39ad1f..7e4be0912 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java @@ -28,6 +28,7 @@ import dev.cel.bundle.CelEnvironment.OverloadDecl; import dev.cel.bundle.CelEnvironment.TypeDecl; import dev.cel.bundle.CelEnvironment.VariableDecl; +import dev.cel.common.CelContainer; import java.io.IOException; import java.net.URL; import org.junit.Test; @@ -43,7 +44,13 @@ public void toYaml_success() throws Exception { CelEnvironment.newBuilder() .setName("dump_env") .setDescription("dump_env description") - .setContainer("test.container") + .setContainer( + CelContainer.newBuilder() + .setName("test.container") + .addAbbreviations("abbr1.Abbr1", "abbr2.Abbr2") + .addAlias("alias1", "qual.name1") + .addAlias("alias2", "qual.name2") + .build()) .addExtensions( ImmutableSet.of( ExtensionConfig.of("bindings"), diff --git a/common/src/main/java/dev/cel/common/CelContainer.java b/common/src/main/java/dev/cel/common/CelContainer.java index ccb1715ba..0ca566fac 100644 --- a/common/src/main/java/dev/cel/common/CelContainer.java +++ b/common/src/main/java/dev/cel/common/CelContainer.java @@ -14,8 +14,12 @@ package dev.cel.common; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; + import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -23,6 +27,7 @@ import com.google.errorprone.annotations.Immutable; import java.util.LinkedHashMap; import java.util.Locale; +import java.util.Map; import java.util.Optional; /** CelContainer holds a reference to an optional qualified container name and set of aliases. */ @@ -32,20 +37,38 @@ public abstract class CelContainer { public abstract String name(); - abstract ImmutableMap aliases(); + abstract ImmutableMap aliasMap(); + + /** + * Returns the aliases configured in the container. + * + *

    The key of the map is the alias and the value is the corresponding fully qualified name. + */ + public ImmutableMap aliases() { + return aliasMap().entrySet().stream() + .filter(e -> e.getValue().kind().equals(AliasKind.ALIAS)) + .collect(toImmutableMap(Map.Entry::getKey, e -> e.getValue().qualifiedName())); + } + + public ImmutableList abbreviations() { + return aliasMap().entrySet().stream() + .filter(e -> e.getValue().kind().equals(AliasKind.ABBREVIATION)) + .map(e -> e.getValue().qualifiedName()) + .collect(toImmutableList()); + } /** Builder for {@link CelContainer} */ @AutoValue.Builder public abstract static class Builder { - private final LinkedHashMap aliases = new LinkedHashMap<>(); + private final LinkedHashMap aliasMap = new LinkedHashMap<>(); abstract String name(); /** Sets the fully-qualified name of the container. */ public abstract Builder setName(String name); - abstract Builder setAliases(ImmutableMap aliases); + abstract Builder setAliasMap(ImmutableMap aliasMap); /** See {@link #addAbbreviations(ImmutableSet)} for documentation. */ @CanIgnoreReturnValue @@ -170,7 +193,7 @@ public Builder addAlias(String alias, String qualifiedName) { private void aliasAs(AliasKind kind, String qualifiedName, String alias) { validateAliasOrThrow(kind, qualifiedName, alias); - aliases.put(alias, qualifiedName); + aliasMap.put(alias, AliasEntry.create(kind, qualifiedName)); } private void validateAliasOrThrow(AliasKind kind, String qualifiedName, String alias) { @@ -185,12 +208,12 @@ private void validateAliasOrThrow(AliasKind kind, String qualifiedName, String a String.format("qualified name must not begin with a leading '.': %s", qualifiedName)); } - String aliasRef = aliases.get(alias); + AliasEntry aliasRef = aliasMap.get(alias); if (aliasRef != null) { throw new IllegalArgumentException( String.format( "%s collides with existing reference: name=%s, %s=%s, existing=%s", - kind, qualifiedName, kind, alias, aliasRef)); + kind, qualifiedName, kind, alias, aliasRef.qualifiedName())); } String containerName = name(); @@ -206,7 +229,7 @@ private void validateAliasOrThrow(AliasKind kind, String qualifiedName, String a @CheckReturnValue public CelContainer build() { - setAliases(ImmutableMap.copyOf(aliases)); + setAliasMap(ImmutableMap.copyOf(aliasMap)); return autoBuild(); } } @@ -263,7 +286,7 @@ public ImmutableSet resolveCandidateNames(String typeName) { public Builder toBuilder() { Builder builder = autoToBuilder(); - builder.aliases.putAll(aliases()); + builder.aliasMap.putAll(aliasMap()); return builder; } @@ -284,12 +307,12 @@ private Optional findAlias(String name) { simple = name.substring(0, dot); qualifier = name.substring(dot); } - String alias = aliases().get(simple); + AliasEntry alias = aliasMap().get(simple); if (alias == null) { return Optional.empty(); } - return Optional.of(alias + qualifier); + return Optional.of(alias.qualifiedName() + qualifier); } private static boolean isIdentifierChar(int r) { @@ -301,7 +324,7 @@ private static boolean isIdentifierChar(int r) { return r == '.' || r == '_' || Character.isLetter(r) || Character.isDigit(r); } - private enum AliasKind { + enum AliasKind { ALIAS, ABBREVIATION; @@ -310,4 +333,17 @@ public String toString() { return this.name().toLowerCase(Locale.getDefault()); } } + + /** Represents an alias or abbreviation. */ + @AutoValue + @Immutable + abstract static class AliasEntry { + static AliasEntry create(AliasKind kind, String qualifiedName) { + return new AutoValue_CelContainer_AliasEntry(kind, qualifiedName); + } + + abstract AliasKind kind(); + + abstract String qualifiedName(); + } } diff --git a/testing/src/test/resources/environment/dump_env.yaml b/testing/src/test/resources/environment/dump_env.yaml index 8f6829838..6a885ea51 100644 --- a/testing/src/test/resources/environment/dump_env.yaml +++ b/testing/src/test/resources/environment/dump_env.yaml @@ -14,7 +14,16 @@ name: dump_env description: dump_env description -container: test.container +container: + name: test.container + abbreviations: + - abbr1.Abbr1 + - abbr2.Abbr2 + aliases: + - alias: alias1 + qualified_name: qual.name1 + - alias: alias2 + qualified_name: qual.name2 extensions: - name: bindings - name: encoders From 463bee010ecadda42dc1bd41ce8b34d3d037d608 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 29 Dec 2025 06:50:39 -0800 Subject: [PATCH 047/100] Add iteration limit control to planned comprehensions PiperOrigin-RevId: 850043882 --- common/exceptions/BUILD.bazel | 6 ++ .../dev/cel/common/exceptions/BUILD.bazel | 13 ++++ .../CelIterationLimitExceededException.java | 31 ++++++++++ .../dev/cel/runtime/AccumulatedUnknowns.java | 2 +- .../dev/cel/runtime/planner/Attribute.java | 2 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 58 +++++++++-------- .../java/dev/cel/runtime/planner/EvalAnd.java | 27 +------- .../cel/runtime/planner/EvalAttribute.java | 29 +-------- .../cel/runtime/planner/EvalConditional.java | 44 +++---------- .../dev/cel/runtime/planner/EvalConstant.java | 22 +------ .../cel/runtime/planner/EvalCreateList.java | 36 ++--------- .../cel/runtime/planner/EvalCreateMap.java | 37 ++--------- .../cel/runtime/planner/EvalCreateStruct.java | 34 ++-------- .../dev/cel/runtime/planner/EvalFold.java | 62 +++++++------------ .../dev/cel/runtime/planner/EvalHelpers.java | 10 +-- .../java/dev/cel/runtime/planner/EvalOr.java | 27 +------- .../dev/cel/runtime/planner/EvalTestOnly.java | 27 +------- .../dev/cel/runtime/planner/EvalUnary.java | 29 ++------- .../cel/runtime/planner/EvalVarArgsCall.java | 29 +-------- .../cel/runtime/planner/EvalZeroArity.java | 25 +------- .../cel/runtime/planner/ExecutionFrame.java | 51 +++++++++++++++ .../cel/runtime/planner/MaybeAttribute.java | 4 +- .../cel/runtime/planner/MissingAttribute.java | 2 +- .../runtime/planner/NamespacedAttribute.java | 2 +- .../runtime/planner/PlannedInterpretable.java | 10 ++- .../cel/runtime/planner/PlannedProgram.java | 11 +++- .../cel/runtime/planner/ProgramPlanner.java | 14 +++-- .../runtime/planner/RelativeAttribute.java | 4 +- .../java/dev/cel/runtime/CelRuntimeTest.java | 2 +- .../cel/runtime/DefaultDispatcherTest.java | 2 +- .../runtime/planner/ProgramPlannerTest.java | 51 ++++++++++++++- 31 files changed, 292 insertions(+), 411 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java diff --git a/common/exceptions/BUILD.bazel b/common/exceptions/BUILD.bazel index 6ffb2dcb9..96e07ac65 100644 --- a/common/exceptions/BUILD.bazel +++ b/common/exceptions/BUILD.bazel @@ -40,3 +40,9 @@ java_library( # used_by_android exports = ["//common/src/main/java/dev/cel/common/exceptions:invalid_argument"], ) + +java_library( + name = "iteration_budget_exceeded", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:iteration_budget_exceeded"], +) diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel index 6bd1ad9ca..203866928 100644 --- a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel @@ -85,3 +85,16 @@ java_library( "//common/annotations", ], ) + +java_library( + name = "iteration_budget_exceeded", + srcs = ["CelIterationLimitExceededException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + ], +) diff --git a/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java b/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java new file mode 100644 index 000000000..ef0f1d8e3 --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java @@ -0,0 +1,31 @@ +// Copyright 2025 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.CelRuntimeException; +import dev.cel.common.annotations.Internal; +import java.util.Locale; + +/** Indicates that the iteration budget for a comprehension has been exceeded. */ +@Internal +public final class CelIterationLimitExceededException extends CelRuntimeException { + + public CelIterationLimitExceededException(int budget) { + super( + String.format(Locale.US, "Iteration budget exceeded: %d", budget), + CelErrorCode.ITERATION_BUDGET_EXCEEDED); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java index 77435a042..d27de2da2 100644 --- a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java +++ b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java @@ -2,7 +2,7 @@ // // 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 aj +// You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java index f20c9aadd..cc011ed34 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java @@ -20,7 +20,7 @@ /** Represents a resolvable symbol or path (such as a variable or a field selection). */ @Immutable interface Attribute { - Object resolve(GlobalResolver ctx); + Object resolve(GlobalResolver ctx, ExecutionFrame frame); Attribute addQualifier(Qualifier qualifier); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 09a082e87..b99b41fd5 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -37,6 +37,7 @@ java_library( "//common:cel_ast", "//common:container", "//common:operator", + "//common:options", "//common/annotations", "//common/ast", "//common/types", @@ -59,11 +60,14 @@ java_library( srcs = ["PlannedProgram.java"], deps = [ ":error_metadata", + ":execution_frame", ":planned_interpretable", ":strict_error_exception", "//:auto_value", + "//common:options", "//common:runtime_exception", "//common/values", + "//runtime", "//runtime:activation", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", @@ -78,11 +82,10 @@ java_library( name = "eval_const", srcs = ["EvalConstant.java"], deps = [ + ":execution_frame", ":planned_interpretable", "//common/values", "//common/values:cel_byte_string", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -111,6 +114,7 @@ java_library( ], deps = [ ":eval_helpers", + ":execution_frame", ":planned_interpretable", ":qualifier", "//common:container", @@ -158,10 +162,9 @@ java_library( srcs = ["EvalAttribute.java"], deps = [ ":attribute", + ":execution_frame", ":interpretable_attribute", ":qualifier", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -172,12 +175,11 @@ java_library( name = "eval_test_only", srcs = ["EvalTestOnly.java"], deps = [ + ":execution_frame", ":interpretable_attribute", ":presence_test_qualifier", ":qualifier", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -187,10 +189,9 @@ java_library( name = "eval_zero_arity", srcs = ["EvalZeroArity.java"], deps = [ + ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "//runtime:resolved_overload", ], @@ -201,10 +202,9 @@ java_library( srcs = ["EvalUnary.java"], deps = [ ":eval_helpers", + ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "//runtime:resolved_overload", ], @@ -215,10 +215,9 @@ java_library( srcs = ["EvalVarArgsCall.java"], deps = [ ":eval_helpers", + ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "//runtime:resolved_overload", ], @@ -229,10 +228,9 @@ java_library( srcs = ["EvalOr.java"], deps = [ ":eval_helpers", + ":execution_frame", ":planned_interpretable", "//common/values", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_guava_guava", ], @@ -243,10 +241,9 @@ java_library( srcs = ["EvalAnd.java"], deps = [ ":eval_helpers", + ":execution_frame", ":planned_interpretable", "//common/values", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_guava_guava", ], @@ -256,10 +253,9 @@ java_library( name = "eval_conditional", srcs = ["EvalConditional.java"], deps = [ + ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_guava_guava", ], @@ -269,13 +265,12 @@ java_library( name = "eval_create_struct", srcs = ["EvalCreateStruct.java"], deps = [ + ":execution_frame", ":planned_interpretable", "//common/types", "//common/values", "//common/values:cel_value_provider", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -286,10 +281,9 @@ java_library( name = "eval_create_list", srcs = ["EvalCreateList.java"], deps = [ + ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -300,10 +294,9 @@ java_library( name = "eval_create_map", srcs = ["EvalCreateMap.java"], deps = [ + ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -314,11 +307,10 @@ java_library( name = "eval_fold", srcs = ["EvalFold.java"], deps = [ + ":execution_frame", ":planned_interpretable", "//runtime:concatenated_list_view", "//runtime:evaluation_exception", - "//runtime:evaluation_listener", - "//runtime:function_resolver", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -326,10 +318,22 @@ java_library( ], ) +java_library( + name = "execution_frame", + srcs = ["ExecutionFrame.java"], + deps = [ + "//common:options", + "//common/exceptions:iteration_budget_exceeded", + "//runtime:interpretable", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "eval_helpers", srcs = ["EvalHelpers.java"], deps = [ + ":execution_frame", ":planned_interpretable", ":strict_error_exception", "//common:error_codes", @@ -362,6 +366,8 @@ java_library( name = "planned_interpretable", srcs = ["PlannedInterpretable.java"], deps = [ + ":execution_frame", + "//runtime:evaluation_exception", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", ], diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java index a3a39ce8a..b09191e9f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java @@ -18,8 +18,6 @@ import com.google.common.base.Preconditions; import dev.cel.common.values.ErrorValue; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; final class EvalAnd extends PlannedInterpretable { @@ -28,10 +26,10 @@ final class EvalAnd extends PlannedInterpretable { private final PlannedInterpretable[] args; @Override - public Object eval(GlobalResolver resolver) { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { ErrorValue errorValue = null; for (PlannedInterpretable arg : args) { - Object argVal = evalNonstrictly(arg, resolver); + Object argVal = evalNonstrictly(arg, resolver, frame); if (argVal instanceof Boolean) { // Short-circuit on false if (!((boolean) argVal)) { @@ -53,27 +51,6 @@ public Object eval(GlobalResolver resolver) { return true; } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - static EvalAnd create(long exprId, PlannedInterpretable[] args) { return new EvalAnd(exprId, args); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java index 826f7e1fa..fdd7ad2a3 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java @@ -15,8 +15,6 @@ package dev.cel.runtime.planner; import com.google.errorprone.annotations.Immutable; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; @Immutable @@ -25,36 +23,15 @@ final class EvalAttribute extends InterpretableAttribute { private final Attribute attr; @Override - public Object eval(GlobalResolver resolver) { - Object resolved = attr.resolve(resolver); + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { + Object resolved = attr.resolve(resolver, frame); if (resolved instanceof MissingAttribute) { - ((MissingAttribute) resolved).resolve(resolver); + ((MissingAttribute) resolved).resolve(resolver, frame); } return resolved; } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - @Override public EvalAttribute addQualifier(long exprId, Qualifier qualifier) { Attribute newAttribute = attr.addQualifier(qualifier); diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java index 4445d3e71..74482d629 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java @@ -16,23 +16,20 @@ import com.google.common.base.Preconditions; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; final class EvalConditional extends PlannedInterpretable { @SuppressWarnings("Immutable") - private final Interpretable[] args; + private final PlannedInterpretable[] args; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { - Interpretable condition = args[0]; - Interpretable truthy = args[1]; - Interpretable falsy = args[2]; + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { + PlannedInterpretable condition = args[0]; + PlannedInterpretable truthy = args[1]; + PlannedInterpretable falsy = args[2]; // TODO: Handle unknowns - Object condResult = condition.eval(resolver); + Object condResult = condition.eval(resolver, frame); if (!(condResult instanceof Boolean)) { throw new IllegalArgumentException( String.format("Expected boolean value, found :%s", condResult)); @@ -40,38 +37,17 @@ public Object eval(GlobalResolver resolver) throws CelEvaluationException { // TODO: Handle exhaustive eval if ((boolean) condResult) { - return truthy.eval(resolver); + return truthy.eval(resolver, frame); } - return falsy.eval(resolver); + return falsy.eval(resolver, frame); } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - static EvalConditional create(long exprId, Interpretable[] args) { + static EvalConditional create(long exprId, PlannedInterpretable[] args) { return new EvalConditional(exprId, args); } - private EvalConditional(long exprId, Interpretable[] args) { + private EvalConditional(long exprId, PlannedInterpretable[] args) { super(exprId); Preconditions.checkArgument(args.length == 3); this.args = args; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java index 408d04046..74d2811ea 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -18,8 +18,6 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.values.CelByteString; import dev.cel.common.values.NullValue; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; @Immutable @@ -40,25 +38,7 @@ final class EvalConstant extends PlannedInterpretable { private final Object constant; @Override - public Object eval(GlobalResolver resolver) { - return constant; - } - - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - return constant; - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - return constant; - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { return constant; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java index 4ec275eef..e519b968c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -17,53 +17,29 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; @Immutable final class EvalCreateList extends PlannedInterpretable { // Array contents are not mutated @SuppressWarnings("Immutable") - private final Interpretable[] values; + private final PlannedInterpretable[] values; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(values.length); - for (Interpretable value : values) { - builder.add(value.eval(resolver)); + for (PlannedInterpretable value : values) { + builder.add(value.eval(resolver, frame)); } return builder.build(); } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - static EvalCreateList create(long exprId, Interpretable[] values) { + static EvalCreateList create(long exprId, PlannedInterpretable[] values) { return new EvalCreateList(exprId, values); } - private EvalCreateList(long exprId, Interpretable[] values) { + private EvalCreateList(long exprId, PlannedInterpretable[] values) { super(exprId); this.values = values; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java index 38d690303..abdba90db 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -18,59 +18,34 @@ import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.Immutable; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; @Immutable final class EvalCreateMap extends PlannedInterpretable { // Array contents are not mutated @SuppressWarnings("Immutable") - private final Interpretable[] keys; + private final PlannedInterpretable[] keys; // Array contents are not mutated @SuppressWarnings("Immutable") - private final Interpretable[] values; + private final PlannedInterpretable[] values; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(keys.length); for (int i = 0; i < keys.length; i++) { - builder.put(keys[i].eval(resolver), values[i].eval(resolver)); + builder.put(keys[i].eval(resolver, frame), values[i].eval(resolver, frame)); } return builder.buildOrThrow(); } - - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - static EvalCreateMap create(long exprId, Interpretable[] keys, Interpretable[] values) { + static EvalCreateMap create(long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) { return new EvalCreateMap(exprId, keys, values); } - private EvalCreateMap(long exprId, Interpretable[] keys, Interpretable[] values) { + private EvalCreateMap(long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) { super(exprId); Preconditions.checkArgument(keys.length == values.length); this.keys = keys; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java index 7553add80..f1d6f75e5 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -19,10 +19,7 @@ import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.StructValue; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; -import dev.cel.runtime.Interpretable; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -39,13 +36,13 @@ final class EvalCreateStruct extends PlannedInterpretable { // Array contents are not mutated @SuppressWarnings("Immutable") - private final Interpretable[] values; + private final PlannedInterpretable[] values; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { Map fieldValues = new HashMap<>(); for (int i = 0; i < keys.length; i++) { - Object value = values[i].eval(resolver); + Object value = values[i].eval(resolver, frame); fieldValues.put(keys[i], value); } @@ -62,33 +59,12 @@ public Object eval(GlobalResolver resolver) throws CelEvaluationException { return value; } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - static EvalCreateStruct create( long exprId, CelValueProvider valueProvider, StructType structType, String[] keys, - Interpretable[] values) { + PlannedInterpretable[] values) { return new EvalCreateStruct(exprId, valueProvider, structType, keys, values); } @@ -97,7 +73,7 @@ private EvalCreateStruct( CelValueProvider valueProvider, StructType structType, String[] keys, - Interpretable[] values) { + PlannedInterpretable[] values) { super(exprId); this.valueProvider = valueProvider; this.structType = structType; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java index 2a8ba1603..49047f3a4 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -17,8 +17,6 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.ConcatenatedListView; import dev.cel.runtime.GlobalResolver; import java.util.Collection; @@ -73,16 +71,16 @@ private EvalFold( } @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { - Object iterRangeRaw = iterRange.eval(resolver); + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { + Object iterRangeRaw = iterRange.eval(resolver, frame); Folder folder = new Folder(resolver, accuVar, iterVar, iterVar2); - folder.accuVal = maybeWrapAccumulator(accuInit.eval(folder)); + folder.accuVal = maybeWrapAccumulator(accuInit.eval(folder, frame)); Object result; if (iterRangeRaw instanceof Map) { - result = evalMap((Map) iterRangeRaw, folder); + result = evalMap((Map) iterRangeRaw, folder, frame); } else if (iterRangeRaw instanceof Collection) { - result = evalList((Collection) iterRangeRaw, folder); + result = evalList((Collection) iterRangeRaw, folder, frame); } else { throw new IllegalArgumentException("Unexpected iter_range type: " + iterRangeRaw.getClass()); } @@ -90,48 +88,32 @@ public Object eval(GlobalResolver resolver) throws CelEvaluationException { return maybeUnwrapAccumulator(result); } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - private Object evalMap(Map iterRange, Folder folder) throws CelEvaluationException { + private Object evalMap(Map iterRange, Folder folder, ExecutionFrame frame) + throws CelEvaluationException { for (Map.Entry entry : iterRange.entrySet()) { + frame.incrementIterations(); + folder.iterVarVal = entry.getKey(); if (!iterVar2.isEmpty()) { folder.iterVar2Val = entry.getValue(); } - - boolean cond = (boolean) condition.eval(folder); + + boolean cond = (boolean) condition.eval(folder, frame); if (!cond) { - return result.eval(folder); + return result.eval(folder, frame); } - // TODO: Introduce comprehension safety controls, such as iteration limit. - folder.accuVal = loopStep.eval(folder); + folder.accuVal = loopStep.eval(folder, frame); } - return result.eval(folder); + return result.eval(folder, frame); } - private Object evalList(Collection iterRange, Folder folder) throws CelEvaluationException { + private Object evalList(Collection iterRange, Folder folder, ExecutionFrame frame) + throws CelEvaluationException { int index = 0; for (Object item : iterRange) { + frame.incrementIterations(); + if (iterVar2.isEmpty()) { folder.iterVarVal = item; } else { @@ -139,15 +121,15 @@ private Object evalList(Collection iterRange, Folder folder) throws CelEvalua folder.iterVar2Val = item; } - boolean cond = (boolean) condition.eval(folder); + boolean cond = (boolean) condition.eval(folder, frame); if (!cond) { - return result.eval(folder); + return result.eval(folder, frame); } - folder.accuVal = loopStep.eval(folder); + folder.accuVal = loopStep.eval(folder, frame); index++; } - return result.eval(folder); + return result.eval(folder, frame); } private static Object maybeWrapAccumulator(Object val) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index 3b5bda1bc..8d2805469 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -21,9 +21,10 @@ final class EvalHelpers { - static Object evalNonstrictly(PlannedInterpretable interpretable, GlobalResolver resolver) { + static Object evalNonstrictly( + PlannedInterpretable interpretable, GlobalResolver resolver, ExecutionFrame frame) { try { - return interpretable.eval(resolver); + return interpretable.eval(resolver, frame); } catch (StrictErrorException e) { // Intercept the strict exception to get a more localized expr ID for error reporting purposes // Example: foo [1] && strict_err [2] -> ID 2 is propagated. @@ -33,9 +34,10 @@ static Object evalNonstrictly(PlannedInterpretable interpretable, GlobalResolver } } - static Object evalStrictly(PlannedInterpretable interpretable, GlobalResolver resolver) { + static Object evalStrictly( + PlannedInterpretable interpretable, GlobalResolver resolver, ExecutionFrame frame) { try { - return interpretable.eval(resolver); + return interpretable.eval(resolver, frame); } catch (CelRuntimeException e) { throw new StrictErrorException(e, interpretable.exprId()); } catch (Exception e) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java index f287bdd59..8c8f5954d 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java @@ -18,8 +18,6 @@ import com.google.common.base.Preconditions; import dev.cel.common.values.ErrorValue; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; final class EvalOr extends PlannedInterpretable { @@ -28,10 +26,10 @@ final class EvalOr extends PlannedInterpretable { private final PlannedInterpretable[] args; @Override - public Object eval(GlobalResolver resolver) { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { ErrorValue errorValue = null; for (PlannedInterpretable arg : args) { - Object argVal = evalNonstrictly(arg, resolver); + Object argVal = evalNonstrictly(arg, resolver, frame); if (argVal instanceof Boolean) { // Short-circuit on true if (((boolean) argVal)) { @@ -53,27 +51,6 @@ public Object eval(GlobalResolver resolver) { return false; } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - static EvalOr create(long exprId, PlannedInterpretable[] args) { return new EvalOr(exprId, args); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java index a48016537..30ecdbd83 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalTestOnly.java @@ -16,8 +16,6 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.GlobalResolver; @Immutable @@ -26,29 +24,8 @@ final class EvalTestOnly extends InterpretableAttribute { private final InterpretableAttribute attr; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { - return attr.eval(resolver); - } - - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { + return attr.eval(resolver, frame); } @Override diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java index 13b59d11e..d1a33017b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -18,8 +18,6 @@ import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; @@ -29,35 +27,16 @@ final class EvalUnary extends PlannedInterpretable { private final PlannedInterpretable arg; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { Object argVal = - resolvedOverload.isStrict() ? evalStrictly(arg, resolver) : evalNonstrictly(arg, resolver); + resolvedOverload.isStrict() + ? evalStrictly(arg, resolver, frame) + : evalNonstrictly(arg, resolver, frame); Object[] arguments = new Object[] {argVal}; return resolvedOverload.getDefinition().apply(arguments); } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - static EvalUnary create( long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) { return new EvalUnary(exprId, resolvedOverload, arg); diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index a2a4c0acc..da2979ad1 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -18,8 +18,6 @@ import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; @@ -30,40 +28,19 @@ final class EvalVarArgsCall extends PlannedInterpretable { private final PlannedInterpretable[] args; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { Object[] argVals = new Object[args.length]; for (int i = 0; i < args.length; i++) { PlannedInterpretable arg = args[i]; argVals[i] = resolvedOverload.isStrict() - ? evalStrictly(arg, resolver) - : evalNonstrictly(arg, resolver); + ? evalStrictly(arg, resolver, frame) + : evalNonstrictly(arg, resolver, frame); } return resolvedOverload.getDefinition().apply(argVals); } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - static EvalVarArgsCall create( long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) { return new EvalVarArgsCall(exprId, resolvedOverload, args); diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java index 628e4a70f..6bda7619d 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -15,8 +15,6 @@ package dev.cel.runtime.planner; import dev.cel.runtime.CelEvaluationException; -import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelFunctionResolver; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; @@ -26,31 +24,10 @@ final class EvalZeroArity extends PlannedInterpretable { private final CelResolvedOverload resolvedOverload; @Override - public Object eval(GlobalResolver resolver) throws CelEvaluationException { + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { return resolvedOverload.getDefinition().apply(EMPTY_ARRAY); } - @Override - public Object eval(GlobalResolver resolver, CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - - @Override - public Object eval( - GlobalResolver resolver, - CelFunctionResolver lateBoundFunctionResolver, - CelEvaluationListener listener) { - // TODO: Implement support - throw new UnsupportedOperationException("Not yet supported"); - } - static EvalZeroArity create(long exprId, CelResolvedOverload resolvedOverload) { return new EvalZeroArity(exprId, resolvedOverload); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java b/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java new file mode 100644 index 000000000..a436d397a --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java @@ -0,0 +1,51 @@ +// Copyright 2025 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.runtime.planner; + +import dev.cel.common.CelOptions; +import dev.cel.common.exceptions.CelIterationLimitExceededException; +import dev.cel.runtime.GlobalResolver; +import org.jspecify.annotations.Nullable; + +/** Tracks execution context within a planned program. */ +final class ExecutionFrame implements GlobalResolver { + + private final GlobalResolver delegate; + private final int comprehensionIterationLimit; + private int iterationCount; + + @Override + public @Nullable Object resolve(String name) { + return delegate.resolve(name); + } + + void incrementIterations() { + if (comprehensionIterationLimit < 0) { + return; + } + if (++iterationCount > comprehensionIterationLimit) { + throw new CelIterationLimitExceededException(comprehensionIterationLimit); + } + } + + static ExecutionFrame create(GlobalResolver delegate, CelOptions celOptions) { + return new ExecutionFrame(delegate, celOptions.comprehensionMaxIterations()); + } + + private ExecutionFrame(GlobalResolver delegate, int limit) { + this.delegate = delegate; + this.comprehensionIterationLimit = limit; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java index 542067349..40a9f6203 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/MaybeAttribute.java @@ -28,10 +28,10 @@ final class MaybeAttribute implements Attribute { private final ImmutableList attributes; @Override - public Object resolve(GlobalResolver ctx) { + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { MissingAttribute maybeError = null; for (NamespacedAttribute attr : attributes) { - Object value = attr.resolve(ctx); + Object value = attr.resolve(ctx, frame); if (value == null) { continue; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java index bbb4e0422..596d1bae4 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java @@ -24,7 +24,7 @@ final class MissingAttribute implements Attribute { private final ImmutableSet missingAttributes; @Override - public Object resolve(GlobalResolver ctx) { + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index b90ac0824..d513bc7ba 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -34,7 +34,7 @@ final class NamespacedAttribute implements Attribute { private final CelTypeProvider typeProvider; @Override - public Object resolve(GlobalResolver ctx) { + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { for (String name : namespacedNames) { Object value = ctx.resolve(name); if (value != null) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java index 87a1a7dc4..5ce3208f8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java @@ -15,12 +15,18 @@ package dev.cel.runtime.planner; import com.google.errorprone.annotations.Immutable; -import dev.cel.runtime.Interpretable; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.GlobalResolver; @Immutable -abstract class PlannedInterpretable implements Interpretable { +abstract class PlannedInterpretable { private final long exprId; + /** Runs interpretation with the given activation which supplies name/value bindings. */ + abstract Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException; + + // TODO: Implement support for late-bound functions and evaluation listener + long exprId() { return exprId; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index d1214fab0..646ad6c85 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -16,6 +16,7 @@ import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; import dev.cel.common.values.ErrorValue; import dev.cel.runtime.Activation; @@ -33,6 +34,8 @@ abstract class PlannedProgram implements Program { abstract ErrorMetadata metadata(); + abstract CelOptions options(); + @Override public Object eval() throws CelEvaluationException { return evalOrThrow(interpretable(), GlobalResolver.EMPTY); @@ -52,7 +55,8 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio private Object evalOrThrow(PlannedInterpretable interpretable, GlobalResolver resolver) throws CelEvaluationException { try { - Object evalResult = interpretable.eval(resolver); + ExecutionFrame frame = ExecutionFrame.create(resolver, options()); + Object evalResult = interpretable.eval(resolver, frame); if (evalResult instanceof ErrorValue) { ErrorValue errorValue = (ErrorValue) evalResult; throw newCelEvaluationException(errorValue.exprId(), errorValue.value()); @@ -78,7 +82,8 @@ private CelEvaluationException newCelEvaluationException(long exprId, Exception return builder.setMetadata(metadata(), exprId).build(); } - static Program create(PlannedInterpretable interpretable, ErrorMetadata metadata) { - return new AutoValue_PlannedProgram(interpretable, metadata); + static Program create( + PlannedInterpretable interpretable, ErrorMetadata metadata, CelOptions options) { + return new AutoValue_PlannedProgram(interpretable, metadata, options); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index c751ba88c..1559b8482 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -21,6 +21,7 @@ import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; +import dev.cel.common.CelOptions; import dev.cel.common.Operator; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; @@ -61,6 +62,8 @@ public final class ProgramPlanner { private final DefaultDispatcher dispatcher; private final AttributeFactory attributeFactory; private final CelContainer container; + private final CelOptions options; + /** * Plans a {@link Program} from the provided parsed-only or type-checked {@link @@ -76,7 +79,7 @@ public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException { ErrorMetadata errorMetadata = ErrorMetadata.create(ast.getSource().getPositionsMap(), ast.getSource().getDescription()); - return PlannedProgram.create(plannedInterpretable, errorMetadata); + return PlannedProgram.create(plannedInterpretable, errorMetadata, options); } private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) { @@ -451,9 +454,10 @@ public static ProgramPlanner newPlanner( CelValueProvider valueProvider, DefaultDispatcher dispatcher, CelValueConverter celValueConverter, - CelContainer container) { + CelContainer container, + CelOptions options) { return new ProgramPlanner( - typeProvider, valueProvider, dispatcher, celValueConverter, container); + typeProvider, valueProvider, dispatcher, celValueConverter, container, options); } private ProgramPlanner( @@ -461,11 +465,13 @@ private ProgramPlanner( CelValueProvider valueProvider, DefaultDispatcher dispatcher, CelValueConverter celValueConverter, - CelContainer container) { + CelContainer container, + CelOptions options) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; this.dispatcher = dispatcher; this.container = container; + this.options = options; this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider, celValueConverter); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java index 7357d8147..a913849f6 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java @@ -31,8 +31,8 @@ final class RelativeAttribute implements Attribute { private final ImmutableList qualifiers; @Override - public Object resolve(GlobalResolver ctx) { - Object obj = EvalHelpers.evalStrictly(operand, ctx); + public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { + Object obj = EvalHelpers.evalStrictly(operand, ctx, frame); obj = celValueConverter.toRuntimeValue(obj); for (Qualifier qualifier : qualifiers) { diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java index 39723083e..7d7243384 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java @@ -2,7 +2,7 @@ // // 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 aj +// You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // diff --git a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java index fbcfbd813..255360ee1 100644 --- a/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DefaultDispatcherTest.java @@ -2,7 +2,7 @@ // // 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 aj +// You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 642f3e5ca..968fdcc94 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -116,7 +116,12 @@ public final class ProgramPlannerTest { private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner( - TYPE_PROVIDER, VALUE_PROVIDER, newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER); + TYPE_PROVIDER, + VALUE_PROVIDER, + newDispatcher(), + CEL_VALUE_CONVERTER, + CEL_CONTAINER, + CEL_OPTIONS); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() @@ -801,6 +806,50 @@ public void plan_comprehension_maps(String expression) throws Exception { assertThat(result).isTrue(); } + @Test + @TestParameters("{expression: '[1, 2, 3, 4, 5, 6].map(x, x)'}") + @TestParameters("{expression: '[1, 2, 3].map(x, [1, 2].map(y, x + y))'}") + public void plan_comprehension_iterationLimit_throws(String expression) throws Exception { + CelOptions options = CelOptions.current().comprehensionMaxIterations(5).build(); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + ProtoMessageValueProvider.newInstance(options, DYNAMIC_PROTO), + newDispatcher(), + CEL_VALUE_CONVERTER, + CEL_CONTAINER, + options); + CelAbstractSyntaxTree ast = compile(expression); + + Program program = planner.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e).hasMessageThat().contains("Iteration budget exceeded: 5"); + assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ITERATION_BUDGET_EXCEEDED); + } + + @Test + public void plan_comprehension_iterationLimit_success() throws Exception { + CelOptions options = CelOptions.current().comprehensionMaxIterations(10).build(); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + ProtoMessageValueProvider.newInstance(options, DYNAMIC_PROTO), + newDispatcher(), + CEL_VALUE_CONVERTER, + CEL_CONTAINER, + options); + CelAbstractSyntaxTree ast = compile("[1, 2, 3].map(x, [1, 2].map(y, x + y))"); + + Program program = planner.plan(ast); + + Object result = program.eval(); + assertThat(result) + .isEqualTo( + ImmutableList.of( + ImmutableList.of(2L, 3L), ImmutableList.of(3L, 4L), ImmutableList.of(4L, 5L))); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); if (isParseOnly) { From 17ca9217fa2d4b989bd42cef5ba83b97fc7b61d9 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 29 Dec 2025 11:44:33 -0800 Subject: [PATCH 048/100] Remove grouped overloads from CelStandardFunctions PiperOrigin-RevId: 850118838 --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 3 + .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 21 +- .../dev/cel/runtime/CelStandardFunctions.java | 679 +++++------------- .../src/test/java/dev/cel/runtime/BUILD.bazel | 2 + .../cel/runtime/CelStandardFunctionsTest.java | 9 +- 5 files changed, 198 insertions(+), 516 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index b55aec00f..cf89d0a80 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -828,6 +828,9 @@ java_library( "//common/types:cel_types", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", + "//runtime/standard:add", + "//runtime/standard:int", + "//runtime/standard:timestamp", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 1b7071505..15591e680 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -43,9 +43,9 @@ import dev.cel.common.types.CelTypes; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.ProtoMessageValueProvider; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Arithmetic; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Comparison; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Conversions; +import dev.cel.runtime.standard.AddOperator.AddOverload; +import dev.cel.runtime.standard.IntFunction.IntOverload; +import dev.cel.runtime.standard.TimestampFunction.TimestampOverload; import java.util.Arrays; import java.util.HashMap; import java.util.Optional; @@ -339,7 +339,7 @@ private ImmutableSet newStandardFunctionBindings( (standardFunction, standardOverload) -> { switch (standardFunction) { case INT: - if (standardOverload.equals(Conversions.INT64_TO_INT64)) { + if (standardOverload.equals(IntOverload.INT64_TO_INT64)) { // Note that we require UnsignedLong flag here to avoid ambiguous // overloads against "uint64_to_int64", because they both use the same // Java Long class. We skip adding this identity function if the flag is @@ -350,26 +350,23 @@ private ImmutableSet newStandardFunctionBindings( case TIMESTAMP: // TODO: Remove this flag guard once the feature has been // auto-enabled. - if (standardOverload.equals(Conversions.INT64_TO_TIMESTAMP)) { + if (standardOverload.equals(TimestampOverload.INT64_TO_TIMESTAMP)) { return options.enableTimestampEpoch(); } break; case STRING: return options.enableStringConversion(); case ADD: - Arithmetic arithmetic = (Arithmetic) standardOverload; - if (arithmetic.equals(Arithmetic.ADD_STRING)) { + if (standardOverload.equals(AddOverload.ADD_STRING)) { return options.enableStringConcatenation(); } - if (arithmetic.equals(Arithmetic.ADD_LIST)) { + if (standardOverload.equals(AddOverload.ADD_LIST)) { return options.enableListConcatenation(); } break; default: - if (standardOverload instanceof Comparison - && !options.enableHeterogeneousNumericComparisons()) { - Comparison comparison = (Comparison) standardOverload; - return !comparison.isHeterogeneousComparison(); + if (!options.enableHeterogeneousNumericComparisons()) { + return !CelStandardFunctions.isHeterogeneousComparison(standardOverload); } break; } diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index 98e4c8636..bedd41728 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -22,16 +22,6 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelOptions; import dev.cel.common.annotations.Internal; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Arithmetic; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.BooleanOperator; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Comparison; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Conversions; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.DateTime; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Index; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.InternalOperator; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Relation; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Size; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.StringMatchers; import dev.cel.runtime.standard.AddOperator; import dev.cel.runtime.standard.AddOperator.AddOverload; import dev.cel.runtime.standard.BoolFunction; @@ -118,6 +108,32 @@ /** Runtime function bindings for the standard functions in CEL. */ @Immutable public final class CelStandardFunctions { + private static final ImmutableSet HETEROGENEOUS_COMPARISON_OPERATORS = + ImmutableSet.of( + LessOverload.LESS_DOUBLE_UINT64, + LessOverload.LESS_INT64_UINT64, + LessOverload.LESS_UINT64_INT64, + LessOverload.LESS_INT64_DOUBLE, + LessOverload.LESS_DOUBLE_INT64, + LessOverload.LESS_UINT64_DOUBLE, + LessEqualsOverload.LESS_EQUALS_INT64_UINT64, + LessEqualsOverload.LESS_EQUALS_UINT64_INT64, + LessEqualsOverload.LESS_EQUALS_INT64_DOUBLE, + LessEqualsOverload.LESS_EQUALS_DOUBLE_INT64, + LessEqualsOverload.LESS_EQUALS_UINT64_DOUBLE, + LessEqualsOverload.LESS_EQUALS_DOUBLE_UINT64, + GreaterOverload.GREATER_INT64_UINT64, + GreaterOverload.GREATER_UINT64_INT64, + GreaterOverload.GREATER_INT64_DOUBLE, + GreaterOverload.GREATER_DOUBLE_INT64, + GreaterOverload.GREATER_UINT64_DOUBLE, + GreaterOverload.GREATER_DOUBLE_UINT64, + GreaterEqualsOverload.GREATER_EQUALS_INT64_UINT64, + GreaterEqualsOverload.GREATER_EQUALS_UINT64_INT64, + GreaterEqualsOverload.GREATER_EQUALS_INT64_DOUBLE, + GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_INT64, + GreaterEqualsOverload.GREATER_EQUALS_UINT64_DOUBLE, + GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_UINT64); private final ImmutableSet standardOverloads; @@ -171,516 +187,175 @@ public final class CelStandardFunctions { * special-cased, and does not appear in this enum. */ public enum StandardFunction { - LOGICAL_NOT(BooleanOperator.LOGICAL_NOT), - IN(InternalOperator.IN_LIST, InternalOperator.IN_MAP), - NOT_STRICTLY_FALSE(InternalOperator.NOT_STRICTLY_FALSE), - EQUALS(Relation.EQUALS), - NOT_EQUALS(Relation.NOT_EQUALS), - BOOL(Conversions.BOOL_TO_BOOL, Conversions.STRING_TO_BOOL), + LOGICAL_NOT(LogicalNotOverload.LOGICAL_NOT), + IN(InOverload.IN_LIST, InOverload.IN_MAP), + NOT_STRICTLY_FALSE(NotStrictlyFalseOverload.NOT_STRICTLY_FALSE), + EQUALS(EqualsOverload.EQUALS), + NOT_EQUALS(NotEqualsOverload.NOT_EQUALS), + BOOL(BoolOverload.BOOL_TO_BOOL, BoolOverload.STRING_TO_BOOL), ADD( - Arithmetic.ADD_INT64, - Arithmetic.ADD_UINT64, - Arithmetic.ADD_DOUBLE, - Arithmetic.ADD_STRING, - Arithmetic.ADD_BYTES, - Arithmetic.ADD_LIST, - Arithmetic.ADD_TIMESTAMP_DURATION, - Arithmetic.ADD_DURATION_TIMESTAMP, - Arithmetic.ADD_DURATION_DURATION), + AddOverload.ADD_INT64, + AddOverload.ADD_UINT64, + AddOverload.ADD_DOUBLE, + AddOverload.ADD_STRING, + AddOverload.ADD_BYTES, + AddOverload.ADD_LIST, + AddOverload.ADD_TIMESTAMP_DURATION, + AddOverload.ADD_DURATION_TIMESTAMP, + AddOverload.ADD_DURATION_DURATION), SUBTRACT( - Arithmetic.SUBTRACT_INT64, - Arithmetic.SUBTRACT_TIMESTAMP_TIMESTAMP, - Arithmetic.SUBTRACT_TIMESTAMP_DURATION, - Arithmetic.SUBTRACT_UINT64, - Arithmetic.SUBTRACT_DOUBLE, - Arithmetic.SUBTRACT_DURATION_DURATION), - MULTIPLY(Arithmetic.MULTIPLY_INT64, Arithmetic.MULTIPLY_DOUBLE, Arithmetic.MULTIPLY_UINT64), - DIVIDE(Arithmetic.DIVIDE_DOUBLE, Arithmetic.DIVIDE_INT64, Arithmetic.DIVIDE_UINT64), - MODULO(Arithmetic.MODULO_INT64, Arithmetic.MODULO_UINT64), - NEGATE(Arithmetic.NEGATE_INT64, Arithmetic.NEGATE_DOUBLE), - INDEX(Index.INDEX_LIST, Index.INDEX_MAP), + SubtractOverload.SUBTRACT_INT64, + SubtractOverload.SUBTRACT_TIMESTAMP_TIMESTAMP, + SubtractOverload.SUBTRACT_TIMESTAMP_DURATION, + SubtractOverload.SUBTRACT_UINT64, + SubtractOverload.SUBTRACT_DOUBLE, + SubtractOverload.SUBTRACT_DURATION_DURATION), + MULTIPLY( + MultiplyOverload.MULTIPLY_INT64, + MultiplyOverload.MULTIPLY_DOUBLE, + MultiplyOverload.MULTIPLY_UINT64), + DIVIDE(DivideOverload.DIVIDE_DOUBLE, DivideOverload.DIVIDE_INT64, DivideOverload.DIVIDE_UINT64), + MODULO(ModuloOverload.MODULO_INT64, ModuloOverload.MODULO_UINT64), + NEGATE(NegateOverload.NEGATE_INT64, NegateOverload.NEGATE_DOUBLE), + INDEX(IndexOverload.INDEX_LIST, IndexOverload.INDEX_MAP), SIZE( - Size.SIZE_STRING, - Size.SIZE_BYTES, - Size.SIZE_LIST, - Size.SIZE_MAP, - Size.STRING_SIZE, - Size.BYTES_SIZE, - Size.LIST_SIZE, - Size.MAP_SIZE), + SizeOverload.SIZE_STRING, + SizeOverload.SIZE_BYTES, + SizeOverload.SIZE_LIST, + SizeOverload.SIZE_MAP, + SizeOverload.STRING_SIZE, + SizeOverload.BYTES_SIZE, + SizeOverload.LIST_SIZE, + SizeOverload.MAP_SIZE), INT( - Conversions.INT64_TO_INT64, - Conversions.UINT64_TO_INT64, - Conversions.DOUBLE_TO_INT64, - Conversions.STRING_TO_INT64, - Conversions.TIMESTAMP_TO_INT64), + IntOverload.INT64_TO_INT64, + IntOverload.UINT64_TO_INT64, + IntOverload.DOUBLE_TO_INT64, + IntOverload.STRING_TO_INT64, + IntOverload.TIMESTAMP_TO_INT64), UINT( - Conversions.UINT64_TO_UINT64, - Conversions.INT64_TO_UINT64, - Conversions.DOUBLE_TO_UINT64, - Conversions.STRING_TO_UINT64), + UintOverload.UINT64_TO_UINT64, + UintOverload.INT64_TO_UINT64, + UintOverload.DOUBLE_TO_UINT64, + UintOverload.STRING_TO_UINT64), DOUBLE( - Conversions.DOUBLE_TO_DOUBLE, - Conversions.INT64_TO_DOUBLE, - Conversions.STRING_TO_DOUBLE, - Conversions.UINT64_TO_DOUBLE), + DoubleOverload.DOUBLE_TO_DOUBLE, + DoubleOverload.INT64_TO_DOUBLE, + DoubleOverload.STRING_TO_DOUBLE, + DoubleOverload.UINT64_TO_DOUBLE), STRING( - Conversions.STRING_TO_STRING, - Conversions.INT64_TO_STRING, - Conversions.DOUBLE_TO_STRING, - Conversions.BOOL_TO_STRING, - Conversions.BYTES_TO_STRING, - Conversions.TIMESTAMP_TO_STRING, - Conversions.DURATION_TO_STRING, - Conversions.UINT64_TO_STRING), - BYTES(Conversions.BYTES_TO_BYTES, Conversions.STRING_TO_BYTES), - DURATION(Conversions.DURATION_TO_DURATION, Conversions.STRING_TO_DURATION), + StringOverload.STRING_TO_STRING, + StringOverload.INT64_TO_STRING, + StringOverload.DOUBLE_TO_STRING, + StringOverload.BOOL_TO_STRING, + StringOverload.BYTES_TO_STRING, + StringOverload.TIMESTAMP_TO_STRING, + StringOverload.DURATION_TO_STRING, + StringOverload.UINT64_TO_STRING), + BYTES(BytesOverload.BYTES_TO_BYTES, BytesOverload.STRING_TO_BYTES), + DURATION(DurationOverload.DURATION_TO_DURATION, DurationOverload.STRING_TO_DURATION), TIMESTAMP( - Conversions.STRING_TO_TIMESTAMP, - Conversions.TIMESTAMP_TO_TIMESTAMP, - Conversions.INT64_TO_TIMESTAMP), - DYN(Conversions.TO_DYN), - MATCHES(StringMatchers.MATCHES, StringMatchers.MATCHES_STRING), - CONTAINS(StringMatchers.CONTAINS_STRING), - ENDS_WITH(StringMatchers.ENDS_WITH_STRING), - STARTS_WITH(StringMatchers.STARTS_WITH_STRING), + TimestampOverload.STRING_TO_TIMESTAMP, + TimestampOverload.TIMESTAMP_TO_TIMESTAMP, + TimestampOverload.INT64_TO_TIMESTAMP), + DYN(DynOverload.TO_DYN), + MATCHES(MatchesOverload.MATCHES, MatchesOverload.MATCHES_STRING), + CONTAINS(ContainsOverload.CONTAINS_STRING), + ENDS_WITH(EndsWithOverload.ENDS_WITH_STRING), + STARTS_WITH(StartsWithOverload.STARTS_WITH_STRING), // Date/time Functions - GET_FULL_YEAR(DateTime.TIMESTAMP_TO_YEAR, DateTime.TIMESTAMP_TO_YEAR_WITH_TZ), - GET_MONTH(DateTime.TIMESTAMP_TO_MONTH, DateTime.TIMESTAMP_TO_MONTH_WITH_TZ), - GET_DAY_OF_YEAR(DateTime.TIMESTAMP_TO_DAY_OF_YEAR, DateTime.TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ), + GET_FULL_YEAR( + GetFullYearOverload.TIMESTAMP_TO_YEAR, GetFullYearOverload.TIMESTAMP_TO_YEAR_WITH_TZ), + GET_MONTH(GetMonthOverload.TIMESTAMP_TO_MONTH, GetMonthOverload.TIMESTAMP_TO_MONTH_WITH_TZ), + GET_DAY_OF_YEAR( + GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR, + GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ), GET_DAY_OF_MONTH( - DateTime.TIMESTAMP_TO_DAY_OF_MONTH, DateTime.TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ), + GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH, + GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ), GET_DATE( - DateTime.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED, - DateTime.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ), - GET_DAY_OF_WEEK(DateTime.TIMESTAMP_TO_DAY_OF_WEEK, DateTime.TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ), + GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED, + GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ), + GET_DAY_OF_WEEK( + GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK, + GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ), GET_HOURS( - DateTime.TIMESTAMP_TO_HOURS, - DateTime.TIMESTAMP_TO_HOURS_WITH_TZ, - DateTime.DURATION_TO_HOURS), + GetHoursOverload.TIMESTAMP_TO_HOURS, + GetHoursOverload.TIMESTAMP_TO_HOURS_WITH_TZ, + GetHoursOverload.DURATION_TO_HOURS), GET_MINUTES( - DateTime.TIMESTAMP_TO_MINUTES, - DateTime.TIMESTAMP_TO_MINUTES_WITH_TZ, - DateTime.DURATION_TO_MINUTES), + GetMinutesOverload.TIMESTAMP_TO_MINUTES, + GetMinutesOverload.TIMESTAMP_TO_MINUTES_WITH_TZ, + GetMinutesOverload.DURATION_TO_MINUTES), GET_SECONDS( - DateTime.TIMESTAMP_TO_SECONDS, - DateTime.TIMESTAMP_TO_SECONDS_WITH_TZ, - DateTime.DURATION_TO_SECONDS), + GetSecondsOverload.TIMESTAMP_TO_SECONDS, + GetSecondsOverload.TIMESTAMP_TO_SECONDS_WITH_TZ, + GetSecondsOverload.DURATION_TO_SECONDS), GET_MILLISECONDS( - DateTime.TIMESTAMP_TO_MILLISECONDS, - DateTime.TIMESTAMP_TO_MILLISECONDS_WITH_TZ, - DateTime.DURATION_TO_MILLISECONDS), + GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS, + GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS_WITH_TZ, + GetMillisecondsOverload.DURATION_TO_MILLISECONDS), LESS( - Comparison.LESS_BOOL, - Comparison.LESS_INT64, - Comparison.LESS_UINT64, - Comparison.LESS_DOUBLE, - Comparison.LESS_STRING, - Comparison.LESS_BYTES, - Comparison.LESS_TIMESTAMP, - Comparison.LESS_DURATION, - Comparison.LESS_INT64_UINT64, - Comparison.LESS_UINT64_INT64, - Comparison.LESS_INT64_DOUBLE, - Comparison.LESS_DOUBLE_INT64, - Comparison.LESS_UINT64_DOUBLE, - Comparison.LESS_DOUBLE_UINT64), + LessOverload.LESS_BOOL, + LessOverload.LESS_INT64, + LessOverload.LESS_UINT64, + LessOverload.LESS_DOUBLE, + LessOverload.LESS_STRING, + LessOverload.LESS_BYTES, + LessOverload.LESS_TIMESTAMP, + LessOverload.LESS_DURATION, + LessOverload.LESS_INT64_UINT64, + LessOverload.LESS_UINT64_INT64, + LessOverload.LESS_INT64_DOUBLE, + LessOverload.LESS_DOUBLE_INT64, + LessOverload.LESS_UINT64_DOUBLE, + LessOverload.LESS_DOUBLE_UINT64), LESS_EQUALS( - Comparison.LESS_EQUALS_BOOL, - Comparison.LESS_EQUALS_INT64, - Comparison.LESS_EQUALS_UINT64, - Comparison.LESS_EQUALS_DOUBLE, - Comparison.LESS_EQUALS_STRING, - Comparison.LESS_EQUALS_BYTES, - Comparison.LESS_EQUALS_TIMESTAMP, - Comparison.LESS_EQUALS_DURATION, - Comparison.LESS_EQUALS_INT64_UINT64, - Comparison.LESS_EQUALS_UINT64_INT64, - Comparison.LESS_EQUALS_INT64_DOUBLE, - Comparison.LESS_EQUALS_DOUBLE_INT64, - Comparison.LESS_EQUALS_UINT64_DOUBLE, - Comparison.LESS_EQUALS_DOUBLE_UINT64), + LessEqualsOverload.LESS_EQUALS_BOOL, + LessEqualsOverload.LESS_EQUALS_INT64, + LessEqualsOverload.LESS_EQUALS_UINT64, + LessEqualsOverload.LESS_EQUALS_DOUBLE, + LessEqualsOverload.LESS_EQUALS_STRING, + LessEqualsOverload.LESS_EQUALS_BYTES, + LessEqualsOverload.LESS_EQUALS_TIMESTAMP, + LessEqualsOverload.LESS_EQUALS_DURATION, + LessEqualsOverload.LESS_EQUALS_INT64_UINT64, + LessEqualsOverload.LESS_EQUALS_UINT64_INT64, + LessEqualsOverload.LESS_EQUALS_INT64_DOUBLE, + LessEqualsOverload.LESS_EQUALS_DOUBLE_INT64, + LessEqualsOverload.LESS_EQUALS_UINT64_DOUBLE, + LessEqualsOverload.LESS_EQUALS_DOUBLE_UINT64), GREATER( - Comparison.GREATER_BOOL, - Comparison.GREATER_INT64, - Comparison.GREATER_UINT64, - Comparison.GREATER_DOUBLE, - Comparison.GREATER_STRING, - Comparison.GREATER_BYTES, - Comparison.GREATER_TIMESTAMP, - Comparison.GREATER_DURATION, - Comparison.GREATER_INT64_UINT64, - Comparison.GREATER_UINT64_INT64, - Comparison.GREATER_INT64_DOUBLE, - Comparison.GREATER_DOUBLE_INT64, - Comparison.GREATER_UINT64_DOUBLE, - Comparison.GREATER_DOUBLE_UINT64), + GreaterOverload.GREATER_BOOL, + GreaterOverload.GREATER_INT64, + GreaterOverload.GREATER_UINT64, + GreaterOverload.GREATER_DOUBLE, + GreaterOverload.GREATER_STRING, + GreaterOverload.GREATER_BYTES, + GreaterOverload.GREATER_TIMESTAMP, + GreaterOverload.GREATER_DURATION, + GreaterOverload.GREATER_INT64_UINT64, + GreaterOverload.GREATER_UINT64_INT64, + GreaterOverload.GREATER_INT64_DOUBLE, + GreaterOverload.GREATER_DOUBLE_INT64, + GreaterOverload.GREATER_UINT64_DOUBLE, + GreaterOverload.GREATER_DOUBLE_UINT64), GREATER_EQUALS( - Comparison.GREATER_EQUALS_BOOL, - Comparison.GREATER_EQUALS_BYTES, - Comparison.GREATER_EQUALS_DOUBLE, - Comparison.GREATER_EQUALS_DURATION, - Comparison.GREATER_EQUALS_INT64, - Comparison.GREATER_EQUALS_STRING, - Comparison.GREATER_EQUALS_TIMESTAMP, - Comparison.GREATER_EQUALS_UINT64, - Comparison.GREATER_EQUALS_INT64_UINT64, - Comparison.GREATER_EQUALS_UINT64_INT64, - Comparison.GREATER_EQUALS_INT64_DOUBLE, - Comparison.GREATER_EQUALS_DOUBLE_INT64, - Comparison.GREATER_EQUALS_UINT64_DOUBLE, - Comparison.GREATER_EQUALS_DOUBLE_UINT64); - - /** Container class for CEL standard function overloads. */ - public static final class Overload { - - /** - * Overloads for internal functions that may have been rewritten by macros - * (ex: @in, @not_strictly_false) - */ - public enum InternalOperator implements CelStandardOverload { - IN_LIST(InOverload.IN_LIST), - IN_MAP(InOverload.IN_MAP), - NOT_STRICTLY_FALSE(NotStrictlyFalseOverload.NOT_STRICTLY_FALSE); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - InternalOperator(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for functions that test relations. */ - public enum Relation implements CelStandardOverload { - EQUALS(EqualsOverload.EQUALS), - NOT_EQUALS(NotEqualsOverload.NOT_EQUALS); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - Relation(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for performing arithmetic operations. */ - public enum Arithmetic implements CelStandardOverload { - ADD_INT64(AddOverload.ADD_INT64), - ADD_UINT64(AddOverload.ADD_UINT64), - ADD_BYTES(AddOverload.ADD_BYTES), - ADD_DOUBLE(AddOverload.ADD_DOUBLE), - ADD_DURATION_DURATION(AddOverload.ADD_DURATION_DURATION), - ADD_TIMESTAMP_DURATION(AddOverload.ADD_TIMESTAMP_DURATION), - ADD_STRING(AddOverload.ADD_STRING), - ADD_DURATION_TIMESTAMP(AddOverload.ADD_DURATION_TIMESTAMP), - ADD_LIST(AddOverload.ADD_LIST), - - SUBTRACT_INT64(SubtractOverload.SUBTRACT_INT64), - SUBTRACT_TIMESTAMP_TIMESTAMP(SubtractOverload.SUBTRACT_TIMESTAMP_TIMESTAMP), - SUBTRACT_TIMESTAMP_DURATION(SubtractOverload.SUBTRACT_TIMESTAMP_DURATION), - SUBTRACT_UINT64(SubtractOverload.SUBTRACT_UINT64), - SUBTRACT_DOUBLE(SubtractOverload.SUBTRACT_DOUBLE), - SUBTRACT_DURATION_DURATION(SubtractOverload.SUBTRACT_DURATION_DURATION), - - MULTIPLY_INT64(MultiplyOverload.MULTIPLY_INT64), - MULTIPLY_DOUBLE(MultiplyOverload.MULTIPLY_DOUBLE), - MULTIPLY_UINT64(MultiplyOverload.MULTIPLY_UINT64), - - DIVIDE_DOUBLE(DivideOverload.DIVIDE_DOUBLE), - DIVIDE_INT64(DivideOverload.DIVIDE_INT64), - DIVIDE_UINT64(DivideOverload.DIVIDE_UINT64), - - MODULO_INT64(ModuloOverload.MODULO_INT64), - MODULO_UINT64(ModuloOverload.MODULO_UINT64), - - NEGATE_INT64(NegateOverload.NEGATE_INT64), - NEGATE_DOUBLE(NegateOverload.NEGATE_DOUBLE); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - Arithmetic(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for indexing a list or a map. */ - public enum Index implements CelStandardOverload { - INDEX_LIST(IndexOverload.INDEX_LIST), - INDEX_MAP(IndexOverload.INDEX_MAP); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - Index(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for retrieving the size of a literal or a collection. */ - public enum Size implements CelStandardOverload { - SIZE_BYTES(SizeOverload.SIZE_BYTES), - BYTES_SIZE(SizeOverload.BYTES_SIZE), - SIZE_LIST(SizeOverload.SIZE_LIST), - LIST_SIZE(SizeOverload.LIST_SIZE), - SIZE_STRING(SizeOverload.SIZE_STRING), - STRING_SIZE(SizeOverload.STRING_SIZE), - SIZE_MAP(SizeOverload.SIZE_MAP), - MAP_SIZE(SizeOverload.MAP_SIZE); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - Size(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for performing type conversions. */ - public enum Conversions implements CelStandardOverload { - BOOL_TO_BOOL(BoolOverload.BOOL_TO_BOOL), - STRING_TO_BOOL(BoolOverload.STRING_TO_BOOL), - INT64_TO_INT64(IntOverload.INT64_TO_INT64), - DOUBLE_TO_INT64(IntOverload.DOUBLE_TO_INT64), - STRING_TO_INT64(IntOverload.STRING_TO_INT64), - TIMESTAMP_TO_INT64(IntOverload.TIMESTAMP_TO_INT64), - UINT64_TO_INT64(IntOverload.UINT64_TO_INT64), - - UINT64_TO_UINT64(UintOverload.UINT64_TO_UINT64), - INT64_TO_UINT64(UintOverload.INT64_TO_UINT64), - DOUBLE_TO_UINT64(UintOverload.DOUBLE_TO_UINT64), - STRING_TO_UINT64(UintOverload.STRING_TO_UINT64), - - DOUBLE_TO_DOUBLE(DoubleOverload.DOUBLE_TO_DOUBLE), - INT64_TO_DOUBLE(DoubleOverload.INT64_TO_DOUBLE), - STRING_TO_DOUBLE(DoubleOverload.STRING_TO_DOUBLE), - UINT64_TO_DOUBLE(DoubleOverload.UINT64_TO_DOUBLE), - - STRING_TO_STRING(StringOverload.STRING_TO_STRING), - INT64_TO_STRING(StringOverload.INT64_TO_STRING), - DOUBLE_TO_STRING(StringOverload.DOUBLE_TO_STRING), - BOOL_TO_STRING(StringOverload.BOOL_TO_STRING), - BYTES_TO_STRING(StringOverload.BYTES_TO_STRING), - TIMESTAMP_TO_STRING(StringOverload.TIMESTAMP_TO_STRING), - DURATION_TO_STRING(StringOverload.DURATION_TO_STRING), - UINT64_TO_STRING(StringOverload.UINT64_TO_STRING), - - BYTES_TO_BYTES(BytesOverload.BYTES_TO_BYTES), - STRING_TO_BYTES(BytesOverload.STRING_TO_BYTES), - - DURATION_TO_DURATION(DurationOverload.DURATION_TO_DURATION), - STRING_TO_DURATION(DurationOverload.STRING_TO_DURATION), - - STRING_TO_TIMESTAMP(TimestampOverload.STRING_TO_TIMESTAMP), - TIMESTAMP_TO_TIMESTAMP(TimestampOverload.TIMESTAMP_TO_TIMESTAMP), - INT64_TO_TIMESTAMP(TimestampOverload.INT64_TO_TIMESTAMP), - - TO_DYN(DynOverload.TO_DYN); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - Conversions(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** - * Overloads for functions performing string matching, such as regular expressions or contains - * check. - */ - public enum StringMatchers implements CelStandardOverload { - MATCHES(MatchesOverload.MATCHES), - MATCHES_STRING(MatchesOverload.MATCHES_STRING), - CONTAINS_STRING(ContainsOverload.CONTAINS_STRING), - ENDS_WITH_STRING(EndsWithOverload.ENDS_WITH_STRING), - STARTS_WITH_STRING(StartsWithOverload.STARTS_WITH_STRING); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - StringMatchers(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for logical operators that return a bool as a result. */ - public enum BooleanOperator implements CelStandardOverload { - LOGICAL_NOT(LogicalNotOverload.LOGICAL_NOT); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - BooleanOperator(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for functions performing date/time operations. */ - public enum DateTime implements CelStandardOverload { - TIMESTAMP_TO_YEAR(GetFullYearOverload.TIMESTAMP_TO_YEAR), - TIMESTAMP_TO_YEAR_WITH_TZ(GetFullYearOverload.TIMESTAMP_TO_YEAR_WITH_TZ), - TIMESTAMP_TO_MONTH(GetMonthOverload.TIMESTAMP_TO_MONTH), - TIMESTAMP_TO_MONTH_WITH_TZ(GetMonthOverload.TIMESTAMP_TO_MONTH_WITH_TZ), - TIMESTAMP_TO_DAY_OF_YEAR(GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR), - TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ(GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ), - TIMESTAMP_TO_DAY_OF_MONTH(GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH), - TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ(GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ), - TIMESTAMP_TO_DAY_OF_MONTH_1_BASED(GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED), - TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ( - GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ), - - TIMESTAMP_TO_DAY_OF_WEEK(GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK), - TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ(GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ), - TIMESTAMP_TO_HOURS(GetHoursOverload.TIMESTAMP_TO_HOURS), - TIMESTAMP_TO_HOURS_WITH_TZ(GetHoursOverload.TIMESTAMP_TO_HOURS_WITH_TZ), - TIMESTAMP_TO_MINUTES(GetMinutesOverload.TIMESTAMP_TO_MINUTES), - TIMESTAMP_TO_MINUTES_WITH_TZ(GetMinutesOverload.TIMESTAMP_TO_MINUTES_WITH_TZ), - TIMESTAMP_TO_SECONDS(GetSecondsOverload.TIMESTAMP_TO_SECONDS), - TIMESTAMP_TO_SECONDS_WITH_TZ(GetSecondsOverload.TIMESTAMP_TO_SECONDS_WITH_TZ), - TIMESTAMP_TO_MILLISECONDS(GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS), - TIMESTAMP_TO_MILLISECONDS_WITH_TZ( - GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS_WITH_TZ), - DURATION_TO_HOURS(GetHoursOverload.DURATION_TO_HOURS), - DURATION_TO_MINUTES(GetMinutesOverload.DURATION_TO_MINUTES), - DURATION_TO_SECONDS(GetSecondsOverload.DURATION_TO_SECONDS), - DURATION_TO_MILLISECONDS(GetMillisecondsOverload.DURATION_TO_MILLISECONDS); - - private final CelStandardOverload standardOverload; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - DateTime(CelStandardOverload standardOverload) { - this.standardOverload = standardOverload; - } - } - - /** Overloads for performing numeric comparisons. */ - public enum Comparison implements CelStandardOverload { - LESS_BOOL(LessOverload.LESS_BOOL, false), - LESS_INT64(LessOverload.LESS_INT64, false), - LESS_UINT64(LessOverload.LESS_UINT64, false), - LESS_BYTES(LessOverload.LESS_BYTES, false), - LESS_DOUBLE(LessOverload.LESS_DOUBLE, false), - LESS_DOUBLE_UINT64(LessOverload.LESS_DOUBLE_UINT64, true), - LESS_INT64_UINT64(LessOverload.LESS_INT64_UINT64, true), - LESS_UINT64_INT64(LessOverload.LESS_UINT64_INT64, true), - LESS_INT64_DOUBLE(LessOverload.LESS_INT64_DOUBLE, true), - LESS_DOUBLE_INT64(LessOverload.LESS_DOUBLE_INT64, true), - LESS_UINT64_DOUBLE(LessOverload.LESS_UINT64_DOUBLE, true), - LESS_DURATION(LessOverload.LESS_DURATION, false), - LESS_STRING(LessOverload.LESS_STRING, false), - LESS_TIMESTAMP(LessOverload.LESS_TIMESTAMP, false), - LESS_EQUALS_BOOL(LessEqualsOverload.LESS_EQUALS_BOOL, false), - LESS_EQUALS_BYTES(LessEqualsOverload.LESS_EQUALS_BYTES, false), - LESS_EQUALS_DOUBLE(LessEqualsOverload.LESS_EQUALS_DOUBLE, false), - LESS_EQUALS_DURATION(LessEqualsOverload.LESS_EQUALS_DURATION, false), - LESS_EQUALS_INT64(LessEqualsOverload.LESS_EQUALS_INT64, false), - LESS_EQUALS_STRING(LessEqualsOverload.LESS_EQUALS_STRING, false), - LESS_EQUALS_TIMESTAMP(LessEqualsOverload.LESS_EQUALS_TIMESTAMP, false), - LESS_EQUALS_UINT64(LessEqualsOverload.LESS_EQUALS_UINT64, false), - LESS_EQUALS_INT64_UINT64(LessEqualsOverload.LESS_EQUALS_INT64_UINT64, true), - LESS_EQUALS_UINT64_INT64(LessEqualsOverload.LESS_EQUALS_UINT64_INT64, true), - LESS_EQUALS_INT64_DOUBLE(LessEqualsOverload.LESS_EQUALS_INT64_DOUBLE, true), - LESS_EQUALS_DOUBLE_INT64(LessEqualsOverload.LESS_EQUALS_DOUBLE_INT64, true), - LESS_EQUALS_UINT64_DOUBLE(LessEqualsOverload.LESS_EQUALS_UINT64_DOUBLE, true), - LESS_EQUALS_DOUBLE_UINT64(LessEqualsOverload.LESS_EQUALS_DOUBLE_UINT64, true), - GREATER_BOOL(GreaterOverload.GREATER_BOOL, false), - GREATER_BYTES(GreaterOverload.GREATER_BYTES, false), - GREATER_DOUBLE(GreaterOverload.GREATER_DOUBLE, false), - GREATER_DURATION(GreaterOverload.GREATER_DURATION, false), - GREATER_INT64(GreaterOverload.GREATER_INT64, false), - GREATER_STRING(GreaterOverload.GREATER_STRING, false), - GREATER_TIMESTAMP(GreaterOverload.GREATER_TIMESTAMP, false), - GREATER_UINT64(GreaterOverload.GREATER_UINT64, false), - GREATER_INT64_UINT64(GreaterOverload.GREATER_INT64_UINT64, true), - GREATER_UINT64_INT64(GreaterOverload.GREATER_UINT64_INT64, true), - GREATER_INT64_DOUBLE(GreaterOverload.GREATER_INT64_DOUBLE, true), - GREATER_DOUBLE_INT64(GreaterOverload.GREATER_DOUBLE_INT64, true), - GREATER_UINT64_DOUBLE(GreaterOverload.GREATER_UINT64_DOUBLE, true), - GREATER_DOUBLE_UINT64(GreaterOverload.GREATER_DOUBLE_UINT64, true), - GREATER_EQUALS_BOOL(GreaterEqualsOverload.GREATER_EQUALS_BOOL, false), - GREATER_EQUALS_BYTES(GreaterEqualsOverload.GREATER_EQUALS_BYTES, false), - GREATER_EQUALS_DOUBLE(GreaterEqualsOverload.GREATER_EQUALS_DOUBLE, false), - GREATER_EQUALS_DURATION(GreaterEqualsOverload.GREATER_EQUALS_DURATION, false), - GREATER_EQUALS_INT64(GreaterEqualsOverload.GREATER_EQUALS_INT64, false), - GREATER_EQUALS_STRING(GreaterEqualsOverload.GREATER_EQUALS_STRING, false), - GREATER_EQUALS_TIMESTAMP(GreaterEqualsOverload.GREATER_EQUALS_TIMESTAMP, false), - GREATER_EQUALS_UINT64(GreaterEqualsOverload.GREATER_EQUALS_UINT64, false), - GREATER_EQUALS_INT64_UINT64(GreaterEqualsOverload.GREATER_EQUALS_INT64_UINT64, true), - GREATER_EQUALS_UINT64_INT64(GreaterEqualsOverload.GREATER_EQUALS_UINT64_INT64, true), - GREATER_EQUALS_INT64_DOUBLE(GreaterEqualsOverload.GREATER_EQUALS_INT64_DOUBLE, true), - GREATER_EQUALS_DOUBLE_INT64(GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_INT64, true), - GREATER_EQUALS_UINT64_DOUBLE(GreaterEqualsOverload.GREATER_EQUALS_UINT64_DOUBLE, true), - GREATER_EQUALS_DOUBLE_UINT64(GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_UINT64, true); - - private final CelStandardOverload standardOverload; - private final boolean isHeterogeneousComparison; - - @Override - public CelFunctionBinding newFunctionBinding( - CelOptions celOptions, RuntimeEquality runtimeEquality) { - return standardOverload.newFunctionBinding(celOptions, runtimeEquality); - } - - Comparison(CelStandardOverload standardOverload, boolean isHeterogeneousComparison) { - this.standardOverload = standardOverload; - this.isHeterogeneousComparison = isHeterogeneousComparison; - } - - public boolean isHeterogeneousComparison() { - return isHeterogeneousComparison; - } - } - - private Overload() {} - } + GreaterEqualsOverload.GREATER_EQUALS_BOOL, + GreaterEqualsOverload.GREATER_EQUALS_BYTES, + GreaterEqualsOverload.GREATER_EQUALS_DOUBLE, + GreaterEqualsOverload.GREATER_EQUALS_DURATION, + GreaterEqualsOverload.GREATER_EQUALS_INT64, + GreaterEqualsOverload.GREATER_EQUALS_STRING, + GreaterEqualsOverload.GREATER_EQUALS_TIMESTAMP, + GreaterEqualsOverload.GREATER_EQUALS_UINT64, + GreaterEqualsOverload.GREATER_EQUALS_INT64_UINT64, + GreaterEqualsOverload.GREATER_EQUALS_UINT64_INT64, + GreaterEqualsOverload.GREATER_EQUALS_INT64_DOUBLE, + GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_INT64, + GreaterEqualsOverload.GREATER_EQUALS_UINT64_DOUBLE, + GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_UINT64); private final ImmutableSet standardOverloads; @@ -832,6 +507,10 @@ public static Builder newBuilder() { return new Builder(); } + static boolean isHeterogeneousComparison(CelStandardOverload overload) { + return HETEROGENEOUS_COMPARISON_OPERATORS.contains(overload); + } + private CelStandardFunctions(ImmutableSet standardOverloads) { this.standardOverloads = standardOverloads; } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 1541e9c84..eb0d652f1 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -81,8 +81,10 @@ java_library( "//runtime:type_resolver", "//runtime:unknown_attributes", "//runtime:unknown_options", + "//runtime/standard:add", "//runtime/standard:not_strictly_false", "//runtime/standard:standard_overload", + "//runtime/standard:subtract", "//testing/protos:message_with_enum_cel_java_proto", "//testing/protos:message_with_enum_java_proto", "//testing/protos:multi_file_cel_java_proto", diff --git a/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java b/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java index eac68fb63..d85ef7424 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelStandardFunctionsTest.java @@ -24,8 +24,9 @@ import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; import dev.cel.runtime.CelStandardFunctions.StandardFunction; -import dev.cel.runtime.CelStandardFunctions.StandardFunction.Overload.Arithmetic; +import dev.cel.runtime.standard.AddOperator.AddOverload; import dev.cel.runtime.standard.CelStandardOverload; +import dev.cel.runtime.standard.SubtractOperator.SubtractOverload; import org.junit.Test; import org.junit.runner.RunWith; @@ -118,12 +119,12 @@ public void standardFunctions_filterFunctions() { .filterFunctions( (func, over) -> { if (func.equals(CelStandardFunctions.StandardFunction.ADD) - && over.equals(Arithmetic.ADD_INT64)) { + && over.equals(AddOverload.ADD_INT64)) { return true; } if (func.equals(CelStandardFunctions.StandardFunction.SUBTRACT) - && over.equals(Arithmetic.SUBTRACT_INT64)) { + && over.equals(SubtractOverload.SUBTRACT_INT64)) { return true; } @@ -132,7 +133,7 @@ public void standardFunctions_filterFunctions() { .build(); assertThat(celStandardFunction.getOverloads()) - .containsExactly(Arithmetic.ADD_INT64, Arithmetic.SUBTRACT_INT64); + .containsExactly(AddOverload.ADD_INT64, SubtractOverload.SUBTRACT_INT64); } @Test From 713cf00f91513c07b2f7927179157eb207dcf18b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 14:21:54 +0000 Subject: [PATCH 049/100] Bump tj-actions/changed-files Bumps the github_actions group with 1 update in the /.github/workflows directory: [tj-actions/changed-files](https://github.com/tj-actions/changed-files). Updates `tj-actions/changed-files` from 44 to 46 - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v44...v46) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-version: '46' dependency-type: direct:production dependency-group: github_actions ... Signed-off-by: dependabot[bot] --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 358cc718c..56048491a 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -45,7 +45,7 @@ jobs: # -- Start of Maven Conformance Tests (Ran only when there's version changes) -- - name: Get changed file id: changed_file - uses: tj-actions/changed-files@v44 + uses: tj-actions/changed-files@v46 with: files: publish/cel_version.bzl - name: Verify Version Consistency From 55884108015a524590ad0684fad8e7a66588b59d Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 30 Dec 2025 11:07:01 -0800 Subject: [PATCH 050/100] Associate original function names to each of CEL Standard Function binding PiperOrigin-RevId: 850462832 --- common/BUILD.bazel | 5 +++ .../src/main/java/dev/cel/common/BUILD.bazel | 8 +++++ .../dev/cel/runtime/standard/AddOperator.java | 3 +- .../java/dev/cel/runtime/standard/BUILD.bazel | 32 +++++++++++++++++++ .../cel/runtime/standard/BoolFunction.java | 2 +- .../cel/runtime/standard/BytesFunction.java | 2 +- .../runtime/standard/CelStandardFunction.java | 10 ++++-- .../runtime/standard/ContainsFunction.java | 2 +- .../cel/runtime/standard/DivideOperator.java | 3 +- .../cel/runtime/standard/DoubleFunction.java | 2 +- .../runtime/standard/DurationFunction.java | 2 +- .../dev/cel/runtime/standard/DynFunction.java | 2 +- .../runtime/standard/EndsWithFunction.java | 2 +- .../cel/runtime/standard/EqualsOperator.java | 4 ++- .../cel/runtime/standard/GetDateFunction.java | 2 +- .../standard/GetDayOfMonthFunction.java | 2 +- .../standard/GetDayOfWeekFunction.java | 2 +- .../standard/GetDayOfYearFunction.java | 2 +- .../runtime/standard/GetFullYearFunction.java | 2 +- .../runtime/standard/GetHoursFunction.java | 2 +- .../standard/GetMillisecondsFunction.java | 2 +- .../runtime/standard/GetMinutesFunction.java | 2 +- .../runtime/standard/GetMonthFunction.java | 2 +- .../runtime/standard/GetSecondsFunction.java | 2 +- .../standard/GreaterEqualsOperator.java | 4 ++- .../cel/runtime/standard/GreaterOperator.java | 4 ++- .../dev/cel/runtime/standard/InOperator.java | 4 ++- .../cel/runtime/standard/IndexOperator.java | 4 ++- .../dev/cel/runtime/standard/IntFunction.java | 2 +- .../runtime/standard/LessEqualsOperator.java | 4 ++- .../cel/runtime/standard/LessOperator.java | 4 ++- .../runtime/standard/LogicalNotOperator.java | 4 ++- .../cel/runtime/standard/MatchesFunction.java | 2 +- .../cel/runtime/standard/ModuloOperator.java | 3 +- .../runtime/standard/MultiplyOperator.java | 3 +- .../cel/runtime/standard/NegateOperator.java | 3 +- .../runtime/standard/NotEqualsOperator.java | 4 ++- .../standard/NotStrictlyFalseFunction.java | 4 ++- .../cel/runtime/standard/SizeFunction.java | 2 +- .../runtime/standard/StartsWithFunction.java | 2 +- .../cel/runtime/standard/StringFunction.java | 2 +- .../runtime/standard/SubtractOperator.java | 3 +- .../runtime/standard/TimestampFunction.java | 2 +- .../cel/runtime/standard/UintFunction.java | 2 +- 44 files changed, 118 insertions(+), 43 deletions(-) diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 815d8a1da..7b5c7af4c 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -126,3 +126,8 @@ java_library( name = "operator", exports = ["//common/src/main/java/dev/cel/common:operator"], ) + +cel_android_library( + name = "operator_android", + exports = ["//common/src/main/java/dev/cel/common:operator_android"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index a5fffe049..333ec4b78 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -360,3 +360,11 @@ java_library( ], deps = ["@maven//:com_google_guava_guava"], ) + +cel_android_library( + name = "operator_android", + srcs = ["Operator.java"], + tags = [ + ], + deps = ["@maven_android//:com_google_guava_guava"], +) diff --git a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java index 783ddd81a..82bb308d1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.ADD; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -170,6 +171,6 @@ public CelFunctionBinding newFunctionBinding( } private AddOperator(ImmutableSet overloads) { - super(overloads); + super(ADD.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index 041d07dea..ed189014f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -48,6 +48,7 @@ java_library( ":arithmetic_helpers", ":standard_function", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers", @@ -70,6 +71,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers_android", @@ -92,6 +94,7 @@ java_library( ":arithmetic_helpers", ":standard_function", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers", @@ -113,6 +116,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//common/internal:date_time_helpers_android", @@ -364,6 +368,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -380,6 +385,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -742,6 +748,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -763,6 +770,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -782,6 +790,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -803,6 +812,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -822,6 +832,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -838,6 +849,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -852,6 +864,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -869,6 +882,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -924,6 +938,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -945,6 +960,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -964,6 +980,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//common/internal:comparison_functions", "//common/internal:proto_time_utils", @@ -985,6 +1002,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common/internal:comparison_functions_android", "//common/internal:proto_time_utils_android", @@ -1004,6 +1022,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -1020,6 +1039,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1071,6 +1091,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1090,6 +1111,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1107,6 +1129,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1126,6 +1149,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1143,6 +1167,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1162,6 +1187,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1179,6 +1205,7 @@ java_library( deps = [ ":arithmetic_helpers", ":standard_overload", + "//common:operator", "//common:options", "//common:runtime_exception", "//runtime:function_binding", @@ -1198,6 +1225,7 @@ cel_android_library( ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//common:runtime_exception", "//runtime:function_binding_android", @@ -1214,6 +1242,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:runtime_equality", @@ -1230,6 +1259,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1426,6 +1456,7 @@ java_library( ], deps = [ ":standard_overload", + "//common:operator", "//common:options", "//runtime:function_binding", "//runtime:internal_function_binder", @@ -1443,6 +1474,7 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", + "//common:operator_android", "//common:options", "//runtime:function_binding_android", "//runtime:internal_function_binder_android", diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java index 5d9d3919c..60cc8b156 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java @@ -87,6 +87,6 @@ public CelFunctionBinding newFunctionBinding( } private BoolFunction(ImmutableSet overloads) { - super(overloads); + super("bool", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java index e2a5230ce..7e3ab2b2f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java @@ -76,6 +76,6 @@ public CelFunctionBinding newFunctionBinding( } private BytesFunction(ImmutableSet overloads) { - super(overloads); + super("bytes", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java index bde6fa6b5..73d53287c 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java @@ -14,8 +14,9 @@ package dev.cel.runtime.standard; -import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Preconditions.checkArgument; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelOptions; @@ -28,6 +29,7 @@ */ @Immutable public abstract class CelStandardFunction { + private final String name; private final ImmutableSet overloads; public ImmutableSet newFunctionBindings( @@ -40,8 +42,10 @@ public ImmutableSet newFunctionBindings( return builder.build(); } - CelStandardFunction(ImmutableSet overloads) { - checkState(!overloads.isEmpty(), "At least 1 overload must be provided."); + CelStandardFunction(String name, ImmutableSet overloads) { + checkArgument(!Strings.isNullOrEmpty(name), "Function name must be provided."); + checkArgument(!overloads.isEmpty(), "At least 1 overload must be provided."); this.overloads = overloads; + this.name = name; } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java index 21f8390b9..bf3f9f7a7 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ContainsFunction.java @@ -58,6 +58,6 @@ public CelFunctionBinding newFunctionBinding( } private ContainsFunction(ImmutableSet overloads) { - super(overloads); + super("contains", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java index b9fdad33c..a9c36b0ff 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.DIVIDE; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -90,6 +91,6 @@ public CelFunctionBinding newFunctionBinding( } private DivideOperator(ImmutableSet overloads) { - super(overloads); + super(DIVIDE.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java index 508df1983..b541bd379 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java @@ -87,6 +87,6 @@ public CelFunctionBinding newFunctionBinding( } private DoubleFunction(ImmutableSet overloads) { - super(overloads); + super("double", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java index 72f96f785..436ffd1d4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -88,6 +88,6 @@ public CelFunctionBinding newFunctionBinding( } private DurationFunction(ImmutableSet overloads) { - super(overloads); + super("duration", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java index d3cb34de9..da855de82 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DynFunction.java @@ -57,6 +57,6 @@ public CelFunctionBinding newFunctionBinding( } private DynFunction(ImmutableSet overloads) { - super(overloads); + super("dyn", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java index eeda546fe..7f4e8c035 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/EndsWithFunction.java @@ -59,6 +59,6 @@ public CelFunctionBinding newFunctionBinding( } private EndsWithFunction(ImmutableSet overloads) { - super(overloads); + super("endsWith", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java index ae78decbd..c505e069e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/EqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.EQUALS; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -58,6 +60,6 @@ public CelFunctionBinding newFunctionBinding( } private EqualsOperator(ImmutableSet overloads) { - super(overloads); + super(EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java index 04e046062..bb0fb0d79 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java @@ -89,6 +89,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDateFunction(ImmutableSet overloads) { - super(overloads); + super("getDate", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java index b6f146aa9..e35888fb5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java @@ -90,6 +90,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDayOfMonthFunction(ImmutableSet overloads) { - super(overloads); + super("getDayOfMonth", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java index cede6c1e1..e2fa02961 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java @@ -106,6 +106,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDayOfWeekFunction(ImmutableSet overloads) { - super(overloads); + super("getDayOfWeek", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java index 966c70bc6..4c0e62637 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java @@ -91,6 +91,6 @@ public CelFunctionBinding newFunctionBinding( } private GetDayOfYearFunction(ImmutableSet overloads) { - super(overloads); + super("getDayOfYear", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java index a91fffb72..925f61307 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java @@ -90,6 +90,6 @@ public CelFunctionBinding newFunctionBinding( } private GetFullYearFunction(ImmutableSet overloads) { - super(overloads); + super("getFullYear", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java index 94fb6391e..afe16556f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java @@ -101,6 +101,6 @@ public CelFunctionBinding newFunctionBinding( } private GetHoursFunction(ImmutableSet overloads) { - super(overloads); + super("getHours", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java index 672226eaf..32b17cc88 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java @@ -112,6 +112,6 @@ public CelFunctionBinding newFunctionBinding( } private GetMillisecondsFunction(ImmutableSet overloads) { - super(overloads); + super("getMilliseconds", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java index 5f75d14ef..5f701f1e1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java @@ -102,6 +102,6 @@ public CelFunctionBinding newFunctionBinding( } private GetMinutesFunction(ImmutableSet overloads) { - super(overloads); + super("getMinutes", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java index dc4e66889..a8a9ded68 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java @@ -89,6 +89,6 @@ public CelFunctionBinding newFunctionBinding( } private GetMonthFunction(ImmutableSet overloads) { - super(overloads); + super("getMonth", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java index 70fe96ec4..80030f50f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java @@ -114,6 +114,6 @@ public CelFunctionBinding newFunctionBinding( } private GetSecondsFunction(ImmutableSet overloads) { - super(overloads); + super("getSeconds", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java index be41e0f5f..c04b4398f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.GREATER_EQUALS; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -197,6 +199,6 @@ public CelFunctionBinding newFunctionBinding( } private GreaterEqualsOperator(ImmutableSet overloads) { - super(overloads); + super(GREATER_EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java index 21b59eebb..80edb8a9b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.GREATER; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -190,6 +192,6 @@ public CelFunctionBinding newFunctionBinding( } private GreaterOperator(ImmutableSet overloads) { - super(overloads); + super(GREATER.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java index 9a2c99f43..bf80cc6ff 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/InOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.IN; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -70,6 +72,6 @@ public CelFunctionBinding newFunctionBinding( } private InOperator(ImmutableSet overloads) { - super(overloads); + super(IN.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java index d1b88a88f..f48e8fbf5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IndexOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.INDEX; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -65,6 +67,6 @@ public CelFunctionBinding newFunctionBinding( } private IndexOperator(ImmutableSet overloads) { - super(overloads); + super(INDEX.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java index fda307a9d..248131865 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -130,6 +130,6 @@ public CelFunctionBinding newFunctionBinding( } private IntFunction(ImmutableSet overloads) { - super(overloads); + super("int", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java index e09fcce5e..7688acbc1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.LESS_EQUALS; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -194,6 +196,6 @@ public CelFunctionBinding newFunctionBinding( } private LessEqualsOperator(ImmutableSet overloads) { - super(overloads); + super(LESS_EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java index 4afbc20a2..a53a13a92 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.LESS; + import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.ByteString; @@ -190,6 +192,6 @@ public CelFunctionBinding newFunctionBinding( } private LessOperator(ImmutableSet overloads) { - super(overloads); + super(LESS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java index a4be6385b..6c2ec8efd 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LogicalNotOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.LOGICAL_NOT; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -57,6 +59,6 @@ public CelFunctionBinding newFunctionBinding( } private LogicalNotOperator(ImmutableSet overloads) { - super(overloads); + super(LOGICAL_NOT.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java index 7c24f65a2..62df04110 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java @@ -84,6 +84,6 @@ public CelFunctionBinding newFunctionBinding( } private MatchesFunction(ImmutableSet overloads) { - super(overloads); + super("matches", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java index e7246851c..01e02764d 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.MODULO; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -85,6 +86,6 @@ public CelFunctionBinding newFunctionBinding( } private ModuloOperator(ImmutableSet overloads) { - super(overloads); + super(MODULO.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java index 7e9e2f352..c55a4c968 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.MULTIPLY; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -103,6 +104,6 @@ public CelFunctionBinding newFunctionBinding( } private MultiplyOperator(ImmutableSet overloads) { - super(overloads); + super(MULTIPLY.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java index 2e3f094f5..cc072fdec 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.NEGATE; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -72,6 +73,6 @@ public CelFunctionBinding newFunctionBinding( } private NegateOperator(ImmutableSet overloads) { - super(overloads); + super(NEGATE.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java index 5a0475f29..27b17676e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotEqualsOperator.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.NOT_EQUALS; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -60,6 +62,6 @@ public CelFunctionBinding newFunctionBinding( } private NotEqualsOperator(ImmutableSet overloads) { - super(overloads); + super(NOT_EQUALS.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java index af7d1a3c2..8e0ceead4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NotStrictlyFalseFunction.java @@ -14,6 +14,8 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.NOT_STRICTLY_FALSE; + import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; @@ -63,6 +65,6 @@ public CelFunctionBinding newFunctionBinding( } private NotStrictlyFalseFunction(ImmutableSet overloads) { - super(overloads); + super(NOT_STRICTLY_FALSE.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java index 9f79605ed..a8f787665 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SizeFunction.java @@ -98,6 +98,6 @@ public CelFunctionBinding newFunctionBinding( } private SizeFunction(ImmutableSet overloads) { - super(overloads); + super("size", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java index 6626d6912..457dd3cf1 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StartsWithFunction.java @@ -58,6 +58,6 @@ public CelFunctionBinding newFunctionBinding( } private StartsWithFunction(ImmutableSet overloads) { - super(overloads); + super("startsWith", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java index c491bfe76..9311962ca 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -134,6 +134,6 @@ public CelFunctionBinding newFunctionBinding( } private StringFunction(ImmutableSet overloads) { - super(overloads); + super("string", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java index 4b0ecd3ae..8a7595f92 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -14,6 +14,7 @@ package dev.cel.runtime.standard; +import static dev.cel.common.Operator.SUBTRACT; import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; @@ -157,6 +158,6 @@ public CelFunctionBinding newFunctionBinding( } private SubtractOperator(ImmutableSet overloads) { - super(overloads); + super(SUBTRACT.getFunction(), overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 34fb15768..27c495034 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -107,6 +107,6 @@ public CelFunctionBinding newFunctionBinding( } private TimestampFunction(ImmutableSet overloads) { - super(overloads); + super("timestamp", overloads); } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java index 89424e7fc..d120c6b75 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java @@ -158,6 +158,6 @@ public CelFunctionBinding newFunctionBinding( } private UintFunction(ImmutableSet overloads) { - super(overloads); + super("uint", overloads); } } From ceb8db36149ac0c8e590e9e1ed6c1730a29c807e Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Tue, 30 Dec 2025 11:53:43 -0800 Subject: [PATCH 051/100] Optimize `list.distinct()` PiperOrigin-RevId: 850474438 --- .../cel/extensions/CelListsExtensions.java | 46 ++++++++++++------- .../java/dev/cel/runtime/RuntimeEquality.java | 35 ++++++++++++++ .../dev/cel/runtime/RuntimeEqualityTest.java | 27 +++++++++++ 3 files changed, 91 insertions(+), 17 deletions(-) diff --git a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java index 385023da7..fb46d4747 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java @@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import dev.cel.checker.CelCheckerBuilder; import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelIssue; @@ -316,27 +317,38 @@ public static ImmutableList genRange(long end) { return builder.build(); } + private static class RuntimeEqualityObjectWrapper { + private final Object object; + private final int hashCode; + private final RuntimeEquality runtimeEquality; + + RuntimeEqualityObjectWrapper(Object object, RuntimeEquality runtimeEquality) { + this.object = object; + this.runtimeEquality = runtimeEquality; + this.hashCode = runtimeEquality.hashCode(object); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof RuntimeEqualityObjectWrapper)) { + return false; + } + return runtimeEquality.objectEquals(object, ((RuntimeEqualityObjectWrapper) obj).object); + } + } + private static ImmutableList distinct( Collection list, RuntimeEquality runtimeEquality) { - // TODO Optimize this method, which currently has the O(N^2) complexity. int size = list.size(); ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(size); - List theList; - if (list instanceof List) { - theList = (List) list; - } else { - theList = ImmutableList.copyOf(list); - } - for (int i = 0; i < size; i++) { - Object element = theList.get(i); - boolean found = false; - for (int j = 0; j < i; j++) { - if (runtimeEquality.objectEquals(element, theList.get(j))) { - found = true; - break; - } - } - if (!found) { + Set distinctValues = Sets.newHashSetWithExpectedSize(size); + for (Object element : list) { + if (distinctValues.add(new RuntimeEqualityObjectWrapper(element, runtimeEquality))) { builder.add(element); } } diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java b/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java index d0b2fcd6b..912462e00 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java @@ -223,6 +223,41 @@ public boolean objectEquals(Object x, Object y) { return Objects.equals(x, y); } + /** + * Returns the hash code consistent with the {@link #objectEquals(Object, Object)} method. For + * example, {@code hashCode(1) == hashCode(1.0)} since {@code objectEquals(1, 1.0)} is true. + */ + public int hashCode(Object object) { + if (object == null) { + return 0; + } + + if (celOptions.disableCelStandardEquality()) { + return Objects.hashCode(object); + } + + object = runtimeHelpers.adaptValue(object); + if (object instanceof Number) { + return Double.hashCode(((Number) object).doubleValue()); + } + if (object instanceof Iterable) { + int h = 1; + Iterable iter = (Iterable) object; + for (Object elem : iter) { + h = h * 31 + hashCode(elem); + } + return h; + } + if (object instanceof Map) { + int h = 0; + for (Map.Entry entry : ((Map) object).entrySet()) { + h += hashCode(entry.getKey()) ^ hashCode(entry.getValue()); + } + return h; + } + return Objects.hashCode(object); + } + private static Optional doubleToUnsignedLossless(Number v) { Optional conv = RuntimeHelpers.doubleToUnsignedChecked(v.doubleValue()); return conv.map(ul -> ul.longValue() == v.doubleValue() ? ul : null); diff --git a/runtime/src/test/java/dev/cel/runtime/RuntimeEqualityTest.java b/runtime/src/test/java/dev/cel/runtime/RuntimeEqualityTest.java index d942295ba..00e55873c 100644 --- a/runtime/src/test/java/dev/cel/runtime/RuntimeEqualityTest.java +++ b/runtime/src/test/java/dev/cel/runtime/RuntimeEqualityTest.java @@ -14,8 +14,12 @@ package dev.cel.runtime; +import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelOptions; import dev.cel.expr.conformance.proto2.TestAllTypes; @@ -25,6 +29,29 @@ @RunWith(TestParameterInjector.class) public final class RuntimeEqualityTest { + @Test + public void objectEquals_and_hashCode() { + RuntimeEquality runtimeEquality = + RuntimeEquality.create(RuntimeHelpers.create(), CelOptions.DEFAULT); + assertEqualityAndHashCode(runtimeEquality, 1, 1); + assertEqualityAndHashCode(runtimeEquality, 2, 2L); + assertEqualityAndHashCode(runtimeEquality, 3, 3.0); + assertEqualityAndHashCode(runtimeEquality, 4, UnsignedLong.valueOf(4)); + assertEqualityAndHashCode( + runtimeEquality, + ImmutableList.of(1, 2, 3), + ImmutableList.of(1.0, 2L, UnsignedLong.valueOf(3))); + assertEqualityAndHashCode( + runtimeEquality, + ImmutableMap.of("a", 1, "b", 2), + ImmutableMap.of("a", 1L, "b", UnsignedLong.valueOf(2))); + } + + private void assertEqualityAndHashCode(RuntimeEquality runtimeEquality, Object obj1, Object obj2) { + assertThat(runtimeEquality.objectEquals(obj1, obj2)).isTrue(); + assertThat(runtimeEquality.hashCode(obj1)).isEqualTo(runtimeEquality.hashCode(obj2)); + } + @Test public void objectEquals_messageLite_throws() { RuntimeEquality runtimeEquality = From e966e90577bb83dfead7e2cd4ad353cbb68bf1e9 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 5 Jan 2026 12:59:34 -0800 Subject: [PATCH 052/100] Migrate the runtime to use canonical CEL exception classes PiperOrigin-RevId: 852421357 --- common/BUILD.bazel | 7 -- common/exceptions/BUILD.bazel | 6 ++ .../src/main/java/dev/cel/common/BUILD.bazel | 12 --- .../dev/cel/common/exceptions/BUILD.bazel | 26 +++-- .../CelAttributeNotFoundException.java | 1 - .../exceptions/CelBadFormatException.java | 1 - .../exceptions/CelDivideByZeroException.java | 1 - .../CelIndexOutOfBoundsException.java | 1 - .../CelInvalidArgumentException.java | 1 - .../CelIterationLimitExceededException.java | 1 - .../CelNumericOverflowException.java | 1 - .../{ => exceptions}/CelRuntimeException.java | 9 +- .../java/dev/cel/common/internal/BUILD.bazel | 13 +-- .../cel/common/internal/DateTimeHelpers.java | 7 +- .../dev/cel/common/internal/ProtoAdapter.java | 7 +- .../cel/common/internal/ProtoLiteAdapter.java | 11 +-- .../main/java/dev/cel/extensions/BUILD.bazel | 1 + .../dev/cel/extensions/CelMathExtensions.java | 9 +- .../test/java/dev/cel/extensions/BUILD.bazel | 3 +- .../CelComprehensionsExtensionsTest.java | 8 +- .../src/main/java/dev/cel/runtime/BUILD.bazel | 32 +++--- .../CelEvaluationExceptionBuilder.java | 9 +- .../runtime/CelValueRuntimeTypeProvider.java | 15 +-- .../dev/cel/runtime/DefaultInterpreter.java | 2 +- .../runtime/DescriptorMessageProvider.java | 22 +---- .../java/dev/cel/runtime/RuntimeEquality.java | 7 +- .../java/dev/cel/runtime/RuntimeHelpers.java | 41 ++++---- .../java/dev/cel/runtime/planner/BUILD.bazel | 6 +- .../dev/cel/runtime/planner/EvalHelpers.java | 2 +- .../cel/runtime/planner/PlannedProgram.java | 2 +- .../runtime/planner/StrictErrorException.java | 2 +- .../dev/cel/runtime/standard/AddOperator.java | 9 +- .../runtime/standard/ArithmeticHelpers.java | 31 ------ .../java/dev/cel/runtime/standard/BUILD.bazel | 98 ++++++------------- .../cel/runtime/standard/BoolFunction.java | 11 +-- .../cel/runtime/standard/DivideOperator.java | 5 +- .../cel/runtime/standard/DoubleFunction.java | 5 +- .../runtime/standard/DurationFunction.java | 7 +- .../dev/cel/runtime/standard/IntFunction.java | 18 ++-- .../cel/runtime/standard/MatchesFunction.java | 7 +- .../cel/runtime/standard/ModuloOperator.java | 5 +- .../runtime/standard/MultiplyOperator.java | 9 +- .../cel/runtime/standard/NegateOperator.java | 5 +- .../cel/runtime/standard/StringFunction.java | 15 +-- .../runtime/standard/SubtractOperator.java | 9 +- .../runtime/standard/TimestampFunction.java | 7 +- .../cel/runtime/standard/UintFunction.java | 26 ++--- .../src/test/java/dev/cel/runtime/BUILD.bazel | 5 +- .../CelEvaluationExceptionBuilderTest.java | 8 +- .../cel/runtime/CelRuntimeLegacyImplTest.java | 3 +- .../DescriptorMessageProviderTest.java | 8 +- .../ProtoMessageRuntimeEqualityTest.java | 2 +- .../ProtoMessageRuntimeHelpersTest.java | 21 ++-- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/ProgramPlannerTest.java | 7 +- 55 files changed, 226 insertions(+), 362 deletions(-) rename common/src/main/java/dev/cel/common/{ => exceptions}/CelRuntimeException.java (84%) delete mode 100644 runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 7b5c7af4c..21a124565 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -53,13 +53,6 @@ java_library( exports = ["//common/src/main/java/dev/cel/common:mutable_source"], ) -java_library( - name = "runtime_exception", - # used_by_android - visibility = ["//:internal"], - exports = ["//common/src/main/java/dev/cel/common:runtime_exception"], -) - java_library( name = "proto_json_adapter", exports = ["//common/src/main/java/dev/cel/common:proto_json_adapter"], diff --git a/common/exceptions/BUILD.bazel b/common/exceptions/BUILD.bazel index 96e07ac65..e24f2629c 100644 --- a/common/exceptions/BUILD.bazel +++ b/common/exceptions/BUILD.bazel @@ -5,6 +5,12 @@ package( default_visibility = ["//:internal"], ) +java_library( + name = "runtime_exception", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:runtime_exception"], +) + java_library( name = "attribute_not_found", # used_by_android diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 333ec4b78..ba223d213 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -172,18 +172,6 @@ java_library( ], ) -java_library( - name = "runtime_exception", - srcs = ["CelRuntimeException.java"], - # used_by_android - tags = [ - ], - deps = [ - ":error_codes", - "//common/annotations", - ], -) - java_library( name = "mutable_ast", srcs = ["CelMutableAst.java"], diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel index 203866928..d000ef926 100644 --- a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel @@ -8,6 +8,18 @@ package( ], ) +java_library( + name = "runtime_exception", + srcs = ["CelRuntimeException.java"], + # used_by_android + tags = [ + ], + deps = [ + "//common:error_codes", + "//common/annotations", + ], +) + java_library( name = "attribute_not_found", srcs = ["CelAttributeNotFoundException.java"], @@ -15,8 +27,8 @@ java_library( tags = [ ], deps = [ + ":runtime_exception", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", ], ) @@ -28,8 +40,8 @@ java_library( tags = [ ], deps = [ + ":runtime_exception", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", ], ) @@ -41,8 +53,8 @@ java_library( tags = [ ], deps = [ + ":runtime_exception", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", ], ) @@ -54,8 +66,8 @@ java_library( tags = [ ], deps = [ + ":runtime_exception", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", ], ) @@ -67,8 +79,8 @@ java_library( tags = [ ], deps = [ + ":runtime_exception", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", ], ) @@ -80,8 +92,8 @@ java_library( tags = [ ], deps = [ + ":runtime_exception", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", ], ) @@ -93,8 +105,8 @@ java_library( tags = [ ], deps = [ + ":runtime_exception", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", ], ) diff --git a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java index 4aa43693d..6204fd2fe 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java @@ -15,7 +15,6 @@ package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; import java.util.Arrays; import java.util.Collection; diff --git a/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java index 92dde0101..ba4db602a 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelBadFormatException.java @@ -15,7 +15,6 @@ package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; /** Indicates that a data conversion failed due to a mismatch in the format specification. */ diff --git a/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java index d433d804c..c507797c5 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelDivideByZeroException.java @@ -15,7 +15,6 @@ package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; /** Indicates that a division by zero occurred. */ diff --git a/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java index 3c2d0d03a..72a6cd1c0 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelIndexOutOfBoundsException.java @@ -15,7 +15,6 @@ package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; /** Indicates that a list index access was attempted using an index that is out of bounds. */ diff --git a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java index 5bbaf6cab..41358bb79 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java @@ -15,7 +15,6 @@ package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; /** Indicates that an invalid argument was supplied to a function. */ diff --git a/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java b/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java index ef0f1d8e3..cfaa25f35 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelIterationLimitExceededException.java @@ -15,7 +15,6 @@ package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; import java.util.Locale; diff --git a/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java index 439d1348f..78fbe807e 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelNumericOverflowException.java @@ -15,7 +15,6 @@ package dev.cel.common.exceptions; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; /** diff --git a/common/src/main/java/dev/cel/common/CelRuntimeException.java b/common/src/main/java/dev/cel/common/exceptions/CelRuntimeException.java similarity index 84% rename from common/src/main/java/dev/cel/common/CelRuntimeException.java rename to common/src/main/java/dev/cel/common/exceptions/CelRuntimeException.java index 3856d77f5..c87e192bd 100644 --- a/common/src/main/java/dev/cel/common/CelRuntimeException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelRuntimeException.java @@ -12,21 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package dev.cel.common; +package dev.cel.common.exceptions; +import dev.cel.common.CelErrorCode; import dev.cel.common.annotations.Internal; /** * Wrapper for an unchecked runtime exception with a CelErrorCode supplied. * *

    Note: This is not to be confused with the notion of CEL Runtime. Use {@code - * CelEvaluationException} instead to signify an evaluation error. corresponds to the CelErrorCode. + * CelEvaluationException} instead to signify an evaluation error. */ @Internal -public class CelRuntimeException extends RuntimeException { +public abstract class CelRuntimeException extends RuntimeException { private final CelErrorCode errorCode; - public CelRuntimeException(String errorMessage, CelErrorCode errorCode) { + CelRuntimeException(String errorMessage, CelErrorCode errorCode) { super(errorMessage); this.errorCode = errorCode; } diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index ca8f6375e..a0e564788 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -173,11 +173,9 @@ java_library( ":proto_lite_adapter", ":proto_message_factory", ":well_known_proto", - "//:auto_value", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:numeric_overflow", "//common/values", "//common/values:cel_byte_string", "@maven//:com_google_code_findbugs_annotations", @@ -194,11 +192,10 @@ java_library( ], deps = [ ":well_known_proto", - "//common:error_codes", "//common:options", "//common:proto_json_adapter", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:numeric_overflow", "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", @@ -441,9 +438,8 @@ java_library( tags = [ ], deps = [ - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:bad_format", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", ], @@ -455,9 +451,8 @@ cel_android_library( tags = [ ], deps = [ - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:bad_format", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], diff --git a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java index 1d11caf63..805a9bd75 100644 --- a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java +++ b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java @@ -16,9 +16,8 @@ import com.google.common.base.Strings; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelBadFormatException; import java.time.DateTimeException; import java.time.Duration; import java.time.Instant; @@ -184,7 +183,7 @@ private static ZoneId timeZone(String tz) { try { int ind = tz.indexOf(":"); if (ind == -1) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } int hourOffset = Integer.parseInt(tz.substring(0, ind)); @@ -199,7 +198,7 @@ private static ZoneId timeZone(String tz) { return ZoneId.of(formattedOffset); } catch (DateTimeException e2) { - throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e2); } } } diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java index cd4be2f5e..3c3382ef2 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -31,10 +31,9 @@ import com.google.protobuf.MapEntry; import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.values.CelByteString; import dev.cel.common.values.NullValue; import java.util.ArrayList; @@ -361,7 +360,7 @@ private static int intCheckedCast(long value) { try { return Ints.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } @@ -369,7 +368,7 @@ private static int unsignedIntCheckedCast(long value) { try { return UnsignedInts.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } } diff --git a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java index e05d92f68..3b13ecea1 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java @@ -42,11 +42,10 @@ import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelProtoJsonAdapter; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.values.CelByteString; import java.time.Instant; import java.util.Map; @@ -284,14 +283,14 @@ private Message adaptValueToUint32(Object value) { try { return UInt32Value.of(unsignedIntCheckedCast((Long) value)); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } if (value instanceof UnsignedLong) { try { return UInt32Value.of(unsignedIntCheckedCast(((UnsignedLong) value).longValue())); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } @@ -316,7 +315,7 @@ private static int intCheckedCast(long value) { try { return Ints.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } @@ -324,7 +323,7 @@ private static int unsignedIntCheckedCast(long value) { try { return UnsignedInts.checkedCast(value); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException(e); } } diff --git a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel index 9b897cf84..ed2d19d6f 100644 --- a/extensions/src/main/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/main/java/dev/cel/extensions/BUILD.bazel @@ -123,6 +123,7 @@ java_library( "//common:compiler_common", "//common:options", "//common/ast", + "//common/exceptions:numeric_overflow", "//common/internal:comparison_functions", "//common/types", "//compiler:compiler_builder", diff --git a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java index 1e318aedc..e74177bf1 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java @@ -32,6 +32,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.ExprKind.Kind; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.ComparisonFunctions; import dev.cel.common.types.ListType; import dev.cel.common.types.SimpleType; @@ -216,8 +217,7 @@ enum Function { "math_@max_int_double", Long.class, Double.class, CelMathExtensions::maxPair), CelFunctionBinding.from( "math_@max_double_int", Double.class, Long.class, CelMathExtensions::maxPair), - CelFunctionBinding.from( - "math_@max_list_dyn", List.class, CelMathExtensions::maxList)), + CelFunctionBinding.from("math_@max_list_dyn", List.class, CelMathExtensions::maxList)), ImmutableSet.of( CelFunctionBinding.from("math_@max_uint", Long.class, x -> x), CelFunctionBinding.from( @@ -640,8 +640,7 @@ enum Function { ImmutableSet.of( CelFunctionBinding.from( "math_sqrt_double", Double.class, CelMathExtensions::sqrtDouble), - CelFunctionBinding.from( - "math_sqrt_int", Long.class, CelMathExtensions::sqrtInt), + CelFunctionBinding.from("math_sqrt_int", Long.class, CelMathExtensions::sqrtInt), CelFunctionBinding.from( "math_sqrt_uint", UnsignedLong.class, CelMathExtensions::sqrtUint))); @@ -856,7 +855,7 @@ private static Comparable minPair(Comparable x, Comparable y) { private static long absExact(long x) { if (x == Long.MIN_VALUE) { // The only case where standard Math.abs overflows silently - throw new ArithmeticException("integer overflow"); + throw new CelNumericOverflowException("integer overflow"); } return Math.abs(x); } diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index 45d48aeca..d5155f662 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -14,7 +14,8 @@ java_library( "//common:compiler_common", "//common:container", "//common:options", - "//common/internal:proto_time_utils", + "//common/exceptions:divide_by_zero", + "//common/exceptions:index_out_of_bounds", "//common/types", "//common/types:type_providers", "//common/values", diff --git a/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java index 5318234b9..34696b688 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelComprehensionsExtensionsTest.java @@ -24,6 +24,8 @@ import dev.cel.common.CelFunctionDecl; import dev.cel.common.CelOptions; import dev.cel.common.CelValidationException; +import dev.cel.common.exceptions.CelDivideByZeroException; +import dev.cel.common.exceptions.CelIndexOutOfBoundsException; import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeParamType; import dev.cel.compiler.CelCompiler; @@ -347,13 +349,13 @@ public void twoVarComprehension_keyCollision_runtimeError(String expr, String er } @Test - public void twoVarComprehension_arithematicException_runtimeError() throws Exception { + public void twoVarComprehension_arithmeticException_runtimeError() throws Exception { CelAbstractSyntaxTree ast = CEL_COMPILER.compile("[0].all(i, k, i/k < k)").getAst(); CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); - assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e).hasCauseThat().hasMessageThat().contains("/ by zero"); } @@ -364,7 +366,7 @@ public void twoVarComprehension_outOfBounds_runtimeError() throws Exception { CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> CEL_RUNTIME.createProgram(ast).eval()); - assertThat(e).hasCauseThat().isInstanceOf(IndexOutOfBoundsException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelIndexOutOfBoundsException.class); assertThat(e).hasCauseThat().hasMessageThat().contains("Index out of bounds: 1"); } } diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index cf89d0a80..ac5599ba9 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -98,10 +98,9 @@ java_library( ":runtime_type_provider", "//common:cel_descriptor_util", "//common:cel_descriptors", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/internal:cel_descriptor_pools", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", @@ -298,9 +297,9 @@ java_library( "//common:cel_ast", "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", "//common/ast", + "//common/exceptions:runtime_exception", "//common/types", "//common/types:type_providers", "//common/values:cel_byte_string", @@ -308,7 +307,6 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:org_jspecify_jspecify", ], ) @@ -337,9 +335,9 @@ cel_android_library( "//common:cel_ast_android", "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", "//common/ast:ast_android", + "//common/exceptions:runtime_exception", "//common/types:type_providers_android", "//common/types:types_android", "//common/values:cel_byte_string", @@ -360,10 +358,9 @@ java_library( ], deps = [ ":runtime_helpers", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/internal:comparison_functions", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -378,10 +375,9 @@ cel_android_library( ], deps = [ ":runtime_helpers_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/internal:comparison_functions_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -416,10 +412,11 @@ cel_android_library( ], deps = [ ":concatenated_list_view", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:divide_by_zero", + "//common/exceptions:index_out_of_bounds", + "//common/exceptions:numeric_overflow", "//common/internal:converter", "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", @@ -439,10 +436,11 @@ java_library( ], deps = [ ":concatenated_list_view", - "//common:error_codes", "//common:options", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:divide_by_zero", + "//common/exceptions:index_out_of_bounds", + "//common/exceptions:numeric_overflow", "//common/internal:converter", "//common/values", "@maven//:com_google_errorprone_error_prone_annotations", @@ -558,8 +556,8 @@ java_library( ":evaluation_exception", ":metadata", "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:runtime_exception", "//common/internal:safe_string_formatter", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:org_jspecify_jspecify", @@ -1032,9 +1030,8 @@ java_library( deps = [ ":runtime_type_provider", ":unknown_attributes", - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/values", "//common/values:base_proto_cel_value_converter", "//common/values:base_proto_message_value_provider", @@ -1053,9 +1050,8 @@ cel_android_library( deps = [ ":runtime_type_provider_android", ":unknown_attributes_android", - "//common:error_codes", - "//common:runtime_exception", "//common/annotations", + "//common/exceptions:attribute_not_found", "//common/values:base_proto_cel_value_converter_android", "//common/values:base_proto_message_value_provider_android", "//common/values:cel_value_android", diff --git a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java index 6adbcb672..986788bac 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelEvaluationExceptionBuilder.java @@ -16,8 +16,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.SafeStringFormatter; import org.jspecify.annotations.Nullable; @@ -83,16 +83,15 @@ public static CelEvaluationExceptionBuilder newBuilder(String message, Object... */ @Internal public static CelEvaluationExceptionBuilder newBuilder(CelRuntimeException celRuntimeException) { - // TODO: Temporary until migration is complete. - Throwable cause = celRuntimeException.getCause(); + // Intercept the cause to prevent including the cause's class name in the exception message. String message = - cause == null + celRuntimeException.getCause() == null ? celRuntimeException.getMessage() : celRuntimeException.getCause().getMessage(); return new CelEvaluationExceptionBuilder(message) .setErrorCode(celRuntimeException.getErrorCode()) - .setCause(cause); + .setCause(celRuntimeException); } private CelEvaluationExceptionBuilder(String message) { diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java index 989d3b7be..e071289ca 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java @@ -18,9 +18,8 @@ import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageLite; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelAttributeNotFoundException; import dev.cel.common.values.BaseProtoCelValueConverter; import dev.cel.common.values.BaseProtoMessageValueProvider; import dev.cel.common.values.CelValue; @@ -82,9 +81,7 @@ public Object selectField(Object message, String fieldName) { return map.get(fieldName); } - throw new CelRuntimeException( - new IllegalArgumentException(String.format("key '%s' is not present in map.", fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forMissingMapKey(fieldName); } SelectableValue selectableValue = getSelectableValueOrThrow(message, fieldName); @@ -142,13 +139,7 @@ private Object maybeUnwrapCelValue(Object object) { } private static void throwInvalidFieldSelection(String fieldName) { - throw new CelRuntimeException( - new IllegalArgumentException( - String.format( - "Error resolving field '%s'. Field selections must be performed on messages or" - + " maps.", - fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forFieldResolution(fieldName); } private CelValueRuntimeTypeProvider( diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index 47e3d3b13..ba5d05915 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -31,7 +31,6 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelCall; @@ -42,6 +41,7 @@ import dev.cel.common.ast.CelExpr.CelStruct; import dev.cel.common.ast.CelExpr.ExprKind; import dev.cel.common.ast.CelReference; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.TypeType; diff --git a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java index ab6934e01..f2ea85b9b 100644 --- a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java @@ -21,10 +21,9 @@ import com.google.protobuf.Message; import com.google.protobuf.MessageOrBuilder; import com.google.protobuf.NullValue; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelAttributeNotFoundException; import dev.cel.common.internal.DynamicProto; import dev.cel.common.internal.ProtoAdapter; import dev.cel.common.internal.ProtoMessageFactory; @@ -79,10 +78,8 @@ public DescriptorMessageProvider(ProtoMessageFactory protoMessageFactory, CelOpt .newBuilder(messageName) .orElseThrow( () -> - new CelRuntimeException( - new IllegalArgumentException( - String.format("cannot resolve '%s' as a message", messageName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND)); + CelAttributeNotFoundException.of( + String.format("cannot resolve '%s' as a message", messageName))); try { Descriptor descriptor = builder.getDescriptorForType(); @@ -122,10 +119,7 @@ public DescriptorMessageProvider(ProtoMessageFactory protoMessageFactory, CelOpt if (isOptionalMessage) { return Optional.empty(); } else { - throw new CelRuntimeException( - new IllegalArgumentException( - String.format("key '%s' is not present in map.", fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forMissingMapKey(fieldName); } } @@ -197,13 +191,7 @@ private FieldDescriptor findField(Descriptor descriptor, String fieldName) { private static MessageOrBuilder assertFullProtoMessage(Object candidate, String fieldName) { if (!(candidate instanceof MessageOrBuilder)) { // This can happen when the field selection is done on dyn, and it is not a message. - throw new CelRuntimeException( - new IllegalArgumentException( - String.format( - "Error resolving field '%s'. Field selections must be performed on messages or" - + " maps.", - fieldName)), - CelErrorCode.ATTRIBUTE_NOT_FOUND); + throw CelAttributeNotFoundException.forFieldResolution(fieldName); } return (MessageOrBuilder) candidate; } diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java b/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java index 912462e00..56a8761cd 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeEquality.java @@ -17,10 +17,9 @@ import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageLiteOrBuilder; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelAttributeNotFoundException; import dev.cel.common.internal.ComparisonFunctions; import java.util.Iterator; import java.util.List; @@ -67,8 +66,8 @@ public B indexMap(Map map, A index) { if (value.isPresent()) { return (B) value.get(); } - throw new CelRuntimeException( - new IndexOutOfBoundsException(index.toString()), CelErrorCode.ATTRIBUTE_NOT_FOUND); + + throw CelAttributeNotFoundException.of(index.toString()); } /** Determine whether the {@code map} contains the given {@code key}. */ diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 25f01ef59..0ee7824b7 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -23,10 +23,11 @@ import com.google.protobuf.Duration; import com.google.protobuf.MessageLiteOrBuilder; import com.google.re2j.Pattern; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelDivideByZeroException; +import dev.cel.common.exceptions.CelIndexOutOfBoundsException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.Converter; import dev.cel.common.values.NullValue; import java.time.format.DateTimeParseException; @@ -116,17 +117,11 @@ public static A indexList(List list, Number index) { if (index instanceof Double) { return doubleToLongLossless(index.doubleValue()) .map(v -> indexList(list, v)) - .orElseThrow( - () -> - new CelRuntimeException( - new IndexOutOfBoundsException("Index out of bounds: " + index.doubleValue()), - CelErrorCode.INDEX_OUT_OF_BOUNDS)); + .orElseThrow(() -> new CelIndexOutOfBoundsException(index.doubleValue())); } int castIndex = Ints.checkedCast(index.longValue()); if (castIndex < 0 || castIndex >= list.size()) { - throw new CelRuntimeException( - new IndexOutOfBoundsException("Index out of bounds: " + castIndex), - CelErrorCode.INDEX_OUT_OF_BOUNDS); + throw new CelIndexOutOfBoundsException(castIndex); } return list.get(castIndex); } @@ -145,7 +140,7 @@ public static long int64Add(long x, long y, CelOptions celOptions) { public static long int64Divide(long x, long y, CelOptions celOptions) { if (celOptions.errorOnIntWrap() && x == Long.MIN_VALUE && y == -1) { - throw new ArithmeticException("most negative number wraps"); + throw new CelNumericOverflowException("most negative number wraps"); } return x / y; } @@ -186,13 +181,13 @@ public static long uint64Add(long x, long y, CelOptions celOptions) { if (celOptions.errorOnIntWrap()) { if (x < 0 && y < 0) { // Both numbers are in the upper half of the range, so it must overflow. - throw new ArithmeticException("range overflow on unsigned addition"); + throw new CelNumericOverflowException("range overflow on unsigned addition"); } long z = x + y; if ((x < 0 || y < 0) && z >= 0) { // Only one number is in the upper half of the range. It overflows if the result // is not in the upper half. - throw new ArithmeticException("range overflow on unsigned addition"); + throw new CelNumericOverflowException("range overflow on unsigned addition"); } return z; } @@ -201,7 +196,7 @@ public static long uint64Add(long x, long y, CelOptions celOptions) { public static UnsignedLong uint64Add(UnsignedLong x, UnsignedLong y) { if (x.compareTo(UnsignedLong.MAX_VALUE.minus(y)) > 0) { - throw new ArithmeticException("range overflow on unsigned addition"); + throw new CelNumericOverflowException("range overflow on unsigned addition"); } return x.plus(y); } @@ -228,7 +223,7 @@ public static long uint64Divide(long x, long y, CelOptions celOptions) { ? UnsignedLongs.divide(x, y) : UnsignedLong.valueOf(x).dividedBy(UnsignedLong.valueOf(y)).longValue(); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(e); } } @@ -240,8 +235,7 @@ static long uint64Divide(long x, long y) { public static UnsignedLong uint64Divide(UnsignedLong x, UnsignedLong y) { if (y.equals(UnsignedLong.ZERO)) { - throw new CelRuntimeException( - new ArithmeticException("/ by zero"), CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(); } return x.dividedBy(y); } @@ -252,14 +246,13 @@ public static long uint64Mod(long x, long y, CelOptions celOptions) { ? UnsignedLongs.remainder(x, y) : UnsignedLong.valueOf(x).mod(UnsignedLong.valueOf(y)).longValue(); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(e); } } public static UnsignedLong uint64Mod(UnsignedLong x, UnsignedLong y) { if (y.equals(UnsignedLong.ZERO)) { - throw new CelRuntimeException( - new ArithmeticException("/ by zero"), CelErrorCode.DIVIDE_BY_ZERO); + throw new CelDivideByZeroException(); } return x.mod(y); } @@ -276,7 +269,7 @@ public static long uint64Multiply(long x, long y, CelOptions celOptions) { ? x * y : UnsignedLong.valueOf(x).times(UnsignedLong.valueOf(y)).longValue(); if (celOptions.errorOnIntWrap() && y != 0 && Long.divideUnsigned(z, y) != x) { - throw new ArithmeticException("multiply out of unsigned integer range"); + throw new CelNumericOverflowException("multiply out of unsigned integer range"); } return z; } @@ -289,7 +282,7 @@ static long uint64Multiply(long x, long y) { public static UnsignedLong uint64Multiply(UnsignedLong x, UnsignedLong y) { if (!y.equals(UnsignedLong.ZERO) && x.compareTo(UnsignedLong.MAX_VALUE.dividedBy(y)) > 0) { - throw new ArithmeticException("multiply out of unsigned integer range"); + throw new CelNumericOverflowException("multiply out of unsigned integer range"); } return x.times(y); } @@ -299,7 +292,7 @@ public static long uint64Subtract(long x, long y, CelOptions celOptions) { // Throw an overflow error if x < y, as unsigned longs. This happens if y has its high // bit set and x does not, or if they have the same high bit and x < y as signed longs. if ((x < 0 && y < 0 && x < y) || (x >= 0 && y >= 0 && x < y) || (x >= 0 && y < 0)) { - throw new ArithmeticException("unsigned subtraction underflow"); + throw new CelNumericOverflowException("unsigned subtraction underflow"); } // fallthrough } @@ -310,7 +303,7 @@ public static UnsignedLong uint64Subtract(UnsignedLong x, UnsignedLong y) { // Throw an overflow error if x < y, as unsigned longs. This happens if y has its high // bit set and x does not, or if they have the same high bit and x < y as signed longs. if (x.compareTo(y) < 0) { - throw new ArithmeticException("unsigned subtraction underflow"); + throw new CelNumericOverflowException("unsigned subtraction underflow"); } return x.minus(y); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index b99b41fd5..a6fe64c05 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -65,7 +65,7 @@ java_library( ":strict_error_exception", "//:auto_value", "//common:options", - "//common:runtime_exception", + "//common/exceptions:runtime_exception", "//common/values", "//runtime", "//runtime:activation", @@ -337,7 +337,7 @@ java_library( ":planned_interpretable", ":strict_error_exception", "//common:error_codes", - "//common:runtime_exception", + "//common/exceptions:runtime_exception", "//common/values", "//runtime:interpretable", ], @@ -348,7 +348,7 @@ java_library( srcs = ["StrictErrorException.java"], deps = [ "//common:error_codes", - "//common:runtime_exception", + "//common/exceptions:runtime_exception", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index 8d2805469..de034d035 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -15,7 +15,7 @@ package dev.cel.runtime.planner; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.values.ErrorValue; import dev.cel.runtime.GlobalResolver; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index 646ad6c85..bba1c394f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -17,7 +17,7 @@ import com.google.auto.value.AutoValue; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.values.ErrorValue; import dev.cel.runtime.Activation; import dev.cel.runtime.CelEvaluationException; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java index 3acd6ff27..441a1f27c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java @@ -15,7 +15,7 @@ package dev.cel.runtime.planner; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; /** * An exception that's raised when a strict call failed to invoke, which includes the source of diff --git a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java index 82bb308d1..c4e0bec42 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -15,7 +15,6 @@ package dev.cel.runtime.standard; import static dev.cel.common.Operator.ADD; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; @@ -23,7 +22,7 @@ import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; @@ -62,7 +61,7 @@ public enum AddOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Add(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), ADD_UINT64( @@ -76,7 +75,7 @@ public enum AddOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Add(x, y); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } else { @@ -88,7 +87,7 @@ public enum AddOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Add(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java b/runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java deleted file mode 100644 index 310e9401f..000000000 --- a/runtime/src/main/java/dev/cel/runtime/standard/ArithmeticHelpers.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2025 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.runtime.standard; - -import dev.cel.common.CelErrorCode; - -final class ArithmeticHelpers { - - static CelErrorCode getArithmeticErrorCode(ArithmeticException e) { - String exceptionMessage = e.getMessage(); - // The two known cases for an arithmetic exception is divide by zero and overflow. - if (exceptionMessage.equals("/ by zero")) { - return CelErrorCode.DIVIDE_BY_ZERO; - } - return CelErrorCode.NUMERIC_OVERFLOW; - } - - private ArithmeticHelpers() {} -} diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index ed189014f..5853220ae 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -45,12 +45,11 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function", ":standard_overload", "//common:operator", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", @@ -68,12 +67,11 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:operator_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", @@ -91,12 +89,11 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function", ":standard_overload", "//common:operator", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", @@ -113,12 +110,11 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:operator_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", @@ -136,9 +132,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:safe_string_formatter", "//runtime:function_binding", "//runtime:runtime_equality", @@ -155,9 +150,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:safe_string_formatter", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -236,9 +230,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -254,9 +247,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -270,9 +262,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -290,9 +281,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -898,9 +888,9 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -919,9 +909,9 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1054,9 +1044,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:invalid_argument", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1073,9 +1062,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:invalid_argument", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1089,11 +1077,10 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:operator", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1108,12 +1095,11 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:operator_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1127,11 +1113,10 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:operator", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1146,12 +1131,11 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:operator_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1165,11 +1149,10 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:operator", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1184,12 +1167,11 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:operator_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:divide_by_zero", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1203,11 +1185,10 @@ java_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_overload", "//common:operator", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1222,12 +1203,11 @@ cel_android_library( tags = [ ], deps = [ - ":arithmetic_helpers", ":standard_function_android", ":standard_overload_android", "//common:operator_android", "//common:options", - "//common:runtime_exception", + "//common/exceptions:numeric_overflow", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1338,9 +1318,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", @@ -1360,9 +1339,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", @@ -1380,9 +1358,8 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", @@ -1401,9 +1378,8 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", @@ -1420,9 +1396,9 @@ java_library( ], deps = [ ":standard_overload", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime:runtime_helpers", @@ -1439,9 +1415,9 @@ cel_android_library( deps = [ ":standard_function_android", ":standard_overload_android", - "//common:error_codes", "//common:options", - "//common:runtime_exception", + "//common/exceptions:bad_format", + "//common/exceptions:numeric_overflow", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "//runtime:runtime_helpers_android", @@ -1508,13 +1484,3 @@ cel_android_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) - -java_library( - name = "arithmetic_helpers", - srcs = ["ArithmeticHelpers.java"], - # used_by_android - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - ], -) diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java index 60cc8b156..668934aac 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BoolFunction.java @@ -15,9 +15,8 @@ package dev.cel.runtime.standard; import com.google.common.collect.ImmutableSet; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.common.internal.SafeStringFormatter; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; @@ -64,11 +63,9 @@ public enum BoolOverload implements CelStandardOverload { case "0": return false; default: - throw new CelRuntimeException( - new IllegalArgumentException( - SafeStringFormatter.format( - "Type conversion error from 'string' to 'bool': [%s]", str)), - CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException( + SafeStringFormatter.format( + "Type conversion error from 'string' to 'bool': [%s]", str)); } })); diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java index a9c36b0ff..ddd78cef8 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DivideOperator.java @@ -15,12 +15,11 @@ package dev.cel.runtime.standard; import static dev.cel.common.Operator.DIVIDE; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -58,7 +57,7 @@ public enum DivideOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Divide(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelDivideByZeroException(e); } })), DIVIDE_UINT64( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java index b541bd379..b8d4b74c0 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DoubleFunction.java @@ -16,9 +16,8 @@ import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.util.Arrays; @@ -56,7 +55,7 @@ public enum DoubleOverload implements CelStandardOverload { try { return Double.parseDouble(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } })), UINT64_TO_DOUBLE( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java index 436ffd1d4..e1f11644e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -16,9 +16,8 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -62,13 +61,13 @@ public enum DurationOverload implements CelStandardOverload { try { return RuntimeHelpers.createJavaDurationFromString(d); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } else { try { return RuntimeHelpers.createDurationFromString(d); } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } })), diff --git a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java index 248131865..63959af87 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -17,9 +17,9 @@ import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; @@ -56,9 +56,7 @@ public enum IntOverload implements CelStandardOverload { UnsignedLong.class, (UnsignedLong arg) -> { if (arg.compareTo(UnsignedLong.valueOf(Long.MAX_VALUE)) > 0) { - throw new CelRuntimeException( - new IllegalArgumentException("unsigned out of int range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("unsigned out of int range"); } return arg.longValue(); }); @@ -68,9 +66,7 @@ public enum IntOverload implements CelStandardOverload { Long.class, (Long arg) -> { if (celOptions.errorOnIntWrap() && arg < 0) { - throw new CelRuntimeException( - new IllegalArgumentException("unsigned out of int range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("unsigned out of int range"); } return arg; }); @@ -86,9 +82,7 @@ public enum IntOverload implements CelStandardOverload { return RuntimeHelpers.doubleToLongChecked(arg) .orElseThrow( () -> - new CelRuntimeException( - new IllegalArgumentException("double is out of range for int"), - CelErrorCode.NUMERIC_OVERFLOW)); + new CelNumericOverflowException("double is out of range for int")); } return arg.longValue(); })), @@ -101,7 +95,7 @@ public enum IntOverload implements CelStandardOverload { try { return Long.parseLong(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } })), TIMESTAMP_TO_INT64( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java index 62df04110..a82a7d439 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java @@ -15,9 +15,8 @@ package dev.cel.runtime.standard; import com.google.common.collect.ImmutableSet; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelInvalidArgumentException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -51,7 +50,7 @@ public enum MatchesOverload implements CelStandardOverload { try { return RuntimeHelpers.matches(string, regexp, celOptions); } catch (RuntimeException e) { - throw new CelRuntimeException(e, CelErrorCode.INVALID_ARGUMENT); + throw new CelInvalidArgumentException(e); } })), // Duplicate receiver-style matches overload. @@ -65,7 +64,7 @@ public enum MatchesOverload implements CelStandardOverload { try { return RuntimeHelpers.matches(string, regexp, celOptions); } catch (RuntimeException e) { - throw new CelRuntimeException(e, CelErrorCode.INVALID_ARGUMENT); + throw new CelInvalidArgumentException(e); } })), ; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java index 01e02764d..baa74fe15 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/ModuloOperator.java @@ -15,12 +15,11 @@ package dev.cel.runtime.standard; import static dev.cel.common.Operator.MODULO; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -54,7 +53,7 @@ public enum ModuloOverload implements CelStandardOverload { try { return x % y; } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelDivideByZeroException(e); } })), MODULO_UINT64( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java index c55a4c968..425723652 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/MultiplyOperator.java @@ -15,12 +15,11 @@ package dev.cel.runtime.standard; import static dev.cel.common.Operator.MULTIPLY; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -54,7 +53,7 @@ public enum MultiplyOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Multiply(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), MULTIPLY_DOUBLE( @@ -72,7 +71,7 @@ public enum MultiplyOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Multiply(x, y); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } else { @@ -84,7 +83,7 @@ public enum MultiplyOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Multiply(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java index cc072fdec..c61c395cb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/NegateOperator.java @@ -15,11 +15,10 @@ package dev.cel.runtime.standard; import static dev.cel.common.Operator.NEGATE; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -52,7 +51,7 @@ public enum NegateOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Negate(x, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), NEGATE_DOUBLE( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java index 9311962ca..8fbc8add7 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -20,9 +20,8 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; @@ -69,10 +68,8 @@ public enum StringOverload implements CelStandardOverload { CelByteString.class, (byteStr) -> { if (!byteStr.isValidUtf8()) { - throw new CelRuntimeException( - new IllegalArgumentException( - "invalid UTF-8 in bytes, cannot convert to string"), - CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException( + "invalid UTF-8 in bytes, cannot convert to string"); } return byteStr.toStringUtf8(); }); @@ -82,10 +79,8 @@ public enum StringOverload implements CelStandardOverload { ByteString.class, (byteStr) -> { if (!byteStr.isValidUtf8()) { - throw new CelRuntimeException( - new IllegalArgumentException( - "invalid UTF-8 in bytes, cannot convert to string"), - CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException( + "invalid UTF-8 in bytes, cannot convert to string"); } return byteStr.toStringUtf8(); }); diff --git a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java index 8a7595f92..784c46825 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -15,14 +15,13 @@ package dev.cel.runtime.standard; import static dev.cel.common.Operator.SUBTRACT; -import static dev.cel.runtime.standard.ArithmeticHelpers.getArithmeticErrorCode; import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.protobuf.Duration; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; @@ -59,7 +58,7 @@ public enum SubtractOverload implements CelStandardOverload { try { return RuntimeHelpers.int64Subtract(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } })), SUBTRACT_TIMESTAMP_TIMESTAMP( @@ -105,7 +104,7 @@ public enum SubtractOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Subtract(x, y); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } else { @@ -117,7 +116,7 @@ public enum SubtractOverload implements CelStandardOverload { try { return RuntimeHelpers.uint64Subtract(x, y, celOptions); } catch (ArithmeticException e) { - throw new CelRuntimeException(e, getArithmeticErrorCode(e)); + throw new CelNumericOverflowException(e); } }); } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java index 27c495034..00a4ed43e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -16,9 +16,8 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; @@ -56,14 +55,14 @@ public enum TimestampOverload implements CelStandardOverload { try { return DateTimeHelpers.parse(ts); } catch (DateTimeParseException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } else { try { return ProtoTimeUtils.parse(ts); } catch (ParseException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } } })), diff --git a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java index d120c6b75..be2217dd9 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/UintFunction.java @@ -17,9 +17,9 @@ import com.google.common.collect.ImmutableSet; import com.google.common.primitives.UnsignedLong; import com.google.common.primitives.UnsignedLongs; -import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; +import dev.cel.common.exceptions.CelNumericOverflowException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; @@ -61,9 +61,7 @@ public enum UintOverload implements CelStandardOverload { Long.class, (Long arg) -> { if (celOptions.errorOnIntWrap() && arg < 0) { - throw new CelRuntimeException( - new IllegalArgumentException("int out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("int out of uint range"); } return UnsignedLong.valueOf(arg); }); @@ -73,9 +71,7 @@ public enum UintOverload implements CelStandardOverload { Long.class, (Long arg) -> { if (celOptions.errorOnIntWrap() && arg < 0) { - throw new CelRuntimeException( - new IllegalArgumentException("int out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW); + throw new CelNumericOverflowException("int out of uint range"); } return arg; }); @@ -91,10 +87,7 @@ public enum UintOverload implements CelStandardOverload { if (celOptions.errorOnIntWrap()) { return RuntimeHelpers.doubleToUnsignedChecked(arg) .orElseThrow( - () -> - new CelRuntimeException( - new IllegalArgumentException("double out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW)); + () -> new CelNumericOverflowException("double out of uint range")); } return UnsignedLong.valueOf(BigDecimal.valueOf(arg).toBigInteger()); }); @@ -108,9 +101,8 @@ public enum UintOverload implements CelStandardOverload { .map(UnsignedLong::longValue) .orElseThrow( () -> - new CelRuntimeException( - new IllegalArgumentException("double out of uint range"), - CelErrorCode.NUMERIC_OVERFLOW)); + new CelNumericOverflowException( + "double out of uint range")); } return arg.longValue(); }); @@ -126,7 +118,7 @@ public enum UintOverload implements CelStandardOverload { try { return UnsignedLong.valueOf(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } }); } else { @@ -137,7 +129,7 @@ public enum UintOverload implements CelStandardOverload { try { return UnsignedLongs.parseUnsignedLong(arg); } catch (NumberFormatException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + throw new CelBadFormatException(e); } }); } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index eb0d652f1..ed76fdbe7 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -37,8 +37,11 @@ java_library( "//common:error_codes", "//common:options", "//common:proto_v1alpha1_ast", - "//common:runtime_exception", "//common/ast", + "//common/exceptions:bad_format", + "//common/exceptions:divide_by_zero", + "//common/exceptions:numeric_overflow", + "//common/exceptions:runtime_exception", "//common/internal:cel_descriptor_pools", "//common/internal:converter", "//common/internal:default_message_factory", diff --git a/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java b/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java index b3bc32e20..c073a8443 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelEvaluationExceptionBuilderTest.java @@ -18,7 +18,8 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelBadFormatException; +import dev.cel.common.exceptions.CelRuntimeException; import org.junit.Test; import org.junit.runner.RunWith; @@ -87,15 +88,14 @@ public boolean hasPosition(long exprId) { @Test public void builder_fromCelRuntimeException() { IllegalStateException cause = new IllegalStateException("cause error message"); - CelRuntimeException celRuntimeException = - new CelRuntimeException(cause, CelErrorCode.BAD_FORMAT); + CelRuntimeException celRuntimeException = new CelBadFormatException(cause); CelEvaluationExceptionBuilder builder = CelEvaluationExceptionBuilder.newBuilder(celRuntimeException); CelEvaluationException e = builder.build(); assertThat(e).hasMessageThat().isEqualTo("evaluation error: cause error message"); - assertThat(e).hasCauseThat().isEqualTo(cause); + assertThat(e).hasCauseThat().isEqualTo(celRuntimeException); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.BAD_FORMAT); } } diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java index c4a041f6a..fa3b5f4ae 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeLegacyImplTest.java @@ -18,6 +18,7 @@ import com.google.protobuf.Message; import dev.cel.common.CelException; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.common.values.CelValueProvider; import dev.cel.compiler.CelCompiler; import dev.cel.compiler.CelCompilerFactory; @@ -39,7 +40,7 @@ public void evalException() throws CelException { CelRuntime runtime = CelRuntimeFactory.standardCelRuntimeBuilder().build(); CelRuntime.Program program = runtime.createProgram(compiler.compile("1/0").getAst()); CelEvaluationException e = Assert.assertThrows(CelEvaluationException.class, program::eval); - assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java index 8866d1fa6..6ab1638b1 100644 --- a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java @@ -31,7 +31,7 @@ import dev.cel.common.CelDescriptors; import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; @@ -114,8 +114,8 @@ public void createMessage_missingDescriptorError() { () -> provider.createMessage( "google.api.tools.contract.test.MissingMessageTypes", ImmutableMap.of())); - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); + assertThat(e).hasCauseThat().isNull(); } @Test @@ -159,8 +159,8 @@ public void selectField_mapKeyNotFound() { CelRuntimeException e = Assert.assertThrows( CelRuntimeException.class, () -> provider.selectField(ImmutableMap.of(), "hello")); - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); + assertThat(e).hasCauseThat().isNull(); } @Test @@ -177,8 +177,8 @@ public void selectField_nonProtoObjectError() { CelRuntimeException e = Assert.assertThrows( CelRuntimeException.class, () -> provider.selectField("hello", "not_a_field")); - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.ATTRIBUTE_NOT_FOUND); + assertThat(e).hasCauseThat().isNull(); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java index 16806c856..dc4607e5f 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeEqualityTest.java @@ -42,7 +42,7 @@ import com.google.rpc.context.AttributeContext.Request; import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.AdaptingTypes; import dev.cel.common.internal.BidiConverter; import dev.cel.common.internal.DefaultDescriptorPool; diff --git a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java index f6d701741..45b050fd1 100644 --- a/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java +++ b/runtime/src/test/java/dev/cel/runtime/ProtoMessageRuntimeHelpersTest.java @@ -36,7 +36,8 @@ import com.google.protobuf.UInt64Value; import com.google.protobuf.Value; import dev.cel.common.CelOptions; -import dev.cel.common.CelRuntimeException; +import dev.cel.common.exceptions.CelNumericOverflowException; +import dev.cel.common.exceptions.CelRuntimeException; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; import dev.cel.common.values.CelByteString; @@ -91,7 +92,7 @@ public void int64Divide() throws Exception { assertThat(ProtoMessageRuntimeHelpers.int64Divide(Long.MIN_VALUE, -1, CelOptions.LEGACY)) .isEqualTo(Long.MIN_VALUE); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.int64Divide(Long.MIN_VALUE, -1, CelOptions.DEFAULT)); } @@ -171,7 +172,7 @@ public void uint64Add_signedLongs() throws Exception { assertThat(ProtoMessageRuntimeHelpers.uint64Add(4, 4, CelOptions.DEFAULT)).isEqualTo(8); assertThat(ProtoMessageRuntimeHelpers.uint64Add(-1, 1, CelOptions.LEGACY)).isEqualTo(0); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Add(-1, 1, CelOptions.DEFAULT)); } @@ -185,7 +186,7 @@ public void uint64Add_unsignedLongs() throws Exception { UnsignedLong.MAX_VALUE.minus(UnsignedLong.ONE), UnsignedLong.ONE)) .isEqualTo(UnsignedLong.MAX_VALUE); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Add(UnsignedLong.MAX_VALUE, UnsignedLong.ONE)); } @@ -202,7 +203,7 @@ public void uint64Multiply_signedLongs() throws Exception { .build())) .isEqualTo(0); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Multiply(Long.MIN_VALUE, 2, CelOptions.DEFAULT)); } @@ -213,7 +214,7 @@ public void uint64Multiply_unsignedLongs() throws Exception { UnsignedLong.valueOf(32), UnsignedLong.valueOf(2))) .isEqualTo(UnsignedLong.valueOf(64)); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Multiply( UnsignedLong.MAX_VALUE, UnsignedLong.valueOf(2))); @@ -295,13 +296,13 @@ public void uint64Subtract_signedLongs() throws Exception { assertThat(ProtoMessageRuntimeHelpers.uint64Subtract(-1, 2, CelOptions.DEFAULT)).isEqualTo(-3); assertThat(ProtoMessageRuntimeHelpers.uint64Subtract(0, 1, CelOptions.LEGACY)).isEqualTo(-1); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(0, 1, CelOptions.DEFAULT)); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(-3, -1, CelOptions.DEFAULT)); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(55, -40, CelOptions.DEFAULT)); } @@ -314,7 +315,7 @@ public void uint64Subtract_unsignedLongs() throws Exception { UnsignedLong.valueOf(3), UnsignedLong.valueOf(2))) .isEqualTo(UnsignedLong.ONE); assertThrows( - ArithmeticException.class, + CelNumericOverflowException.class, () -> ProtoMessageRuntimeHelpers.uint64Subtract(UnsignedLong.ONE, UnsignedLong.valueOf(2))); } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 01df7c9ee..2106fa5fe 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -23,6 +23,7 @@ java_library( "//common:operator", "//common:options", "//common/ast", + "//common/exceptions:divide_by_zero", "//common/internal:cel_descriptor_pools", "//common/internal:default_message_factory", "//common/internal:dynamic_proto", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 968fdcc94..52ab5e417 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -38,6 +38,7 @@ import dev.cel.common.CelSource; import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; +import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; @@ -536,7 +537,7 @@ public void plan_call_logicalOr_throws(String expression) throws Exception { CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); assertThat(e).hasMessageThat().startsWith("evaluation error at :"); assertThat(e).hasMessageThat().endsWith("/ by zero"); - assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } @@ -568,7 +569,7 @@ public void plan_call_logicalAnd_throws(String expression) throws Exception { CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); assertThat(e).hasMessageThat().startsWith("evaluation error at :"); assertThat(e).hasMessageThat().endsWith("/ by zero"); - assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } @@ -598,7 +599,7 @@ public void plan_call_conditional_throws(String expression) throws Exception { CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); assertThat(e).hasMessageThat().startsWith("evaluation error at :"); assertThat(e).hasMessageThat().endsWith("/ by zero"); - assertThat(e).hasCauseThat().isInstanceOf(ArithmeticException.class); + assertThat(e).hasCauseThat().isInstanceOf(CelDivideByZeroException.class); assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.DIVIDE_BY_ZERO); } From 233430f86fdd7144b836e29b3b1c3b0a1101a034 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 6 Jan 2026 15:33:49 -0800 Subject: [PATCH 053/100] Group overloads to a single function to facilitate dynamic dispatch PiperOrigin-RevId: 852961755 --- .../src/main/java/dev/cel/runtime/BUILD.bazel | 12 +- .../dev/cel/runtime/CelFunctionBinding.java | 25 ++- .../dev/cel/runtime/CelFunctionOverload.java | 37 ++++ .../dev/cel/runtime/CelResolvedOverload.java | 38 +--- .../dev/cel/runtime/CelStandardFunctions.java | 122 +++++++---- .../dev/cel/runtime/DefaultDispatcher.java | 29 ++- .../dev/cel/runtime/FunctionBindingImpl.java | 86 ++++++++ .../runtime/standard/CelStandardFunction.java | 11 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 14 +- .../runtime/planner/ProgramPlannerTest.java | 204 ++++++------------ 10 files changed, 346 insertions(+), 232 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index ac5599ba9..3b0e2762a 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -122,6 +122,7 @@ java_library( deps = [ ":evaluation_exception", ":evaluation_exception_builder", + ":function_binding", ":function_overload", ":function_resolver", ":resolved_overload", @@ -141,6 +142,7 @@ cel_android_library( deps = [ ":evaluation_exception", ":evaluation_exception_builder", + ":function_binding_android", ":function_overload_android", ":function_resolver_android", ":resolved_overload_android", @@ -609,6 +611,7 @@ java_library( deps = [ ":function_binding", ":runtime_equality", + "//common:operator", "//common:options", "//common/annotations", "//runtime/standard:add", @@ -666,6 +669,7 @@ cel_android_library( deps = [ ":function_binding_android", ":runtime_equality_android", + "//common:operator_android", "//common:options", "//common/annotations", "//runtime/standard:add_android", @@ -723,6 +727,7 @@ java_library( tags = [ ], deps = [ + ":evaluation_exception", ":function_overload", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -735,6 +740,7 @@ cel_android_library( tags = [ ], deps = [ + ":evaluation_exception", ":function_overload_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -774,7 +780,9 @@ java_library( ], deps = [ ":evaluation_exception", + "//runtime:unknown_attributes", "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", ], ) @@ -785,7 +793,9 @@ cel_android_library( ], deps = [ ":evaluation_exception", + "//runtime:unknown_attributes_android", "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", ], ) @@ -1173,7 +1183,6 @@ java_library( ], deps = [ ":function_overload", - ":unknown_attributes", "//:auto_value", "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -1188,7 +1197,6 @@ cel_android_library( ], deps = [ ":function_overload_android", - ":unknown_attributes_android", "//:auto_value", "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java index 79b0f3f54..c7b63926b 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionBinding.java @@ -14,8 +14,13 @@ package dev.cel.runtime; +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; +import java.util.Collection; /** * Binding consisting of an overload id, a Java-native argument signature, and an overload @@ -35,7 +40,6 @@ * *

    Examples: string_startsWith_string, mathMax_list, lessThan_money_money */ - @Immutable public interface CelFunctionBinding { String getOverloadId(); @@ -70,4 +74,23 @@ static CelFunctionBinding from( return new FunctionBindingImpl( overloadId, ImmutableList.copyOf(argTypes), impl, /* isStrict= */ true); } + + /** See {@link #fromOverloads(String, Collection)}. */ + static ImmutableSet fromOverloads( + String functionName, CelFunctionBinding... overloadBindings) { + return fromOverloads(functionName, ImmutableList.copyOf(overloadBindings)); + } + + /** + * Creates a set of bindings for a function, enabling dynamic dispatch logic to select the correct + * overload at runtime based on argument types. + */ + static ImmutableSet fromOverloads( + String functionName, Collection overloadBindings) { + checkArgument(!Strings.isNullOrEmpty(functionName), "Function name cannot be null or empty"); + checkArgument(!overloadBindings.isEmpty(), "You must provide at least one binding."); + + return FunctionBindingImpl.groupOverloadsToFunction( + functionName, ImmutableSet.copyOf(overloadBindings)); + } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java index a1341cb21..3e30a2146 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionOverload.java @@ -14,7 +14,9 @@ package dev.cel.runtime; +import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import java.util.Map; /** Interface describing the general signature of all CEL custom function implementations. */ @Immutable @@ -43,4 +45,39 @@ interface Unary { interface Binary { Object apply(T1 arg1, T2 arg2) throws CelEvaluationException; } + + /** + * Returns true if the overload's expected argument types match the types of the given arguments. + */ + static boolean canHandle( + Object[] arguments, ImmutableList> parameterTypes, boolean isStrict) { + if (parameterTypes.size() != arguments.length) { + return false; + } + for (int i = 0; i < parameterTypes.size(); i++) { + Class paramType = parameterTypes.get(i); + Object arg = arguments[i]; + if (arg == null) { + // null can be assigned to messages, maps, and to objects. + // TODO: Remove null special casing + if (paramType != Object.class && !Map.class.isAssignableFrom(paramType)) { + return false; + } + continue; + } + + if (arg instanceof Exception || arg instanceof CelUnknownSet) { + // Only non-strict functions can accept errors/unknowns as arguments to a function + if (!isStrict) { + // Skip assignability check below, but continue to validate remaining args + continue; + } + } + + if (!paramType.isAssignableFrom(arg.getClass())) { + return false; + } + } + return true; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java index f6a0c4f99..2bcdf3a2d 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java +++ b/runtime/src/main/java/dev/cel/runtime/CelResolvedOverload.java @@ -19,7 +19,6 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.annotations.Internal; import java.util.List; -import java.util.Map; /** * Representation of a function overload which has been resolved to a specific set of argument types @@ -80,41 +79,6 @@ public static CelResolvedOverload of( * Returns true if the overload's expected argument types match the types of the given arguments. */ boolean canHandle(Object[] arguments) { - return canHandle(arguments, getParameterTypes(), isStrict()); - } - - /** - * Returns true if the overload's expected argument types match the types of the given arguments. - */ - public static boolean canHandle( - Object[] arguments, ImmutableList> parameterTypes, boolean isStrict) { - if (parameterTypes.size() != arguments.length) { - return false; - } - for (int i = 0; i < parameterTypes.size(); i++) { - Class paramType = parameterTypes.get(i); - Object arg = arguments[i]; - if (arg == null) { - // null can be assigned to messages, maps, and to objects. - // TODO: Remove null special casing - if (paramType != Object.class && !Map.class.isAssignableFrom(paramType)) { - return false; - } - continue; - } - - if (arg instanceof Exception || arg instanceof CelUnknownSet) { - // Only non-strict functions can accept errors/unknowns as arguments to a function - if (!isStrict) { - // Skip assignability check below, but continue to validate remaining args - continue; - } - } - - if (!paramType.isAssignableFrom(arg.getClass())) { - return false; - } - } - return true; + return CelFunctionOverload.canHandle(arguments, getParameterTypes(), isStrict()); } } diff --git a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java index bedd41728..39797e086 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java +++ b/runtime/src/main/java/dev/cel/runtime/CelStandardFunctions.java @@ -15,12 +15,15 @@ package dev.cel.runtime; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelOptions; +import dev.cel.common.Operator; import dev.cel.common.annotations.Internal; import dev.cel.runtime.standard.AddOperator; import dev.cel.runtime.standard.AddOperator.AddOverload; @@ -104,6 +107,8 @@ import dev.cel.runtime.standard.TimestampFunction.TimestampOverload; import dev.cel.runtime.standard.UintFunction; import dev.cel.runtime.standard.UintFunction.UintOverload; +import java.util.Collection; +import java.util.Map; /** Runtime function bindings for the standard functions in CEL. */ @Immutable @@ -135,7 +140,7 @@ public final class CelStandardFunctions { GreaterEqualsOverload.GREATER_EQUALS_UINT64_DOUBLE, GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_UINT64); - private final ImmutableSet standardOverloads; + private final ImmutableMultimap standardOverloads; public static final ImmutableSet ALL_STANDARD_FUNCTIONS = ImmutableSet.of( @@ -187,13 +192,15 @@ public final class CelStandardFunctions { * special-cased, and does not appear in this enum. */ public enum StandardFunction { - LOGICAL_NOT(LogicalNotOverload.LOGICAL_NOT), - IN(InOverload.IN_LIST, InOverload.IN_MAP), - NOT_STRICTLY_FALSE(NotStrictlyFalseOverload.NOT_STRICTLY_FALSE), - EQUALS(EqualsOverload.EQUALS), - NOT_EQUALS(NotEqualsOverload.NOT_EQUALS), - BOOL(BoolOverload.BOOL_TO_BOOL, BoolOverload.STRING_TO_BOOL), + LOGICAL_NOT(Operator.LOGICAL_NOT.getFunction(), LogicalNotOverload.LOGICAL_NOT), + IN(Operator.IN.getFunction(), InOverload.IN_LIST, InOverload.IN_MAP), + NOT_STRICTLY_FALSE( + Operator.NOT_STRICTLY_FALSE.getFunction(), NotStrictlyFalseOverload.NOT_STRICTLY_FALSE), + EQUALS(Operator.EQUALS.getFunction(), EqualsOverload.EQUALS), + NOT_EQUALS(Operator.NOT_EQUALS.getFunction(), NotEqualsOverload.NOT_EQUALS), + BOOL("bool", BoolOverload.BOOL_TO_BOOL, BoolOverload.STRING_TO_BOOL), ADD( + Operator.ADD.getFunction(), AddOverload.ADD_INT64, AddOverload.ADD_UINT64, AddOverload.ADD_DOUBLE, @@ -204,6 +211,7 @@ public enum StandardFunction { AddOverload.ADD_DURATION_TIMESTAMP, AddOverload.ADD_DURATION_DURATION), SUBTRACT( + Operator.SUBTRACT.getFunction(), SubtractOverload.SUBTRACT_INT64, SubtractOverload.SUBTRACT_TIMESTAMP_TIMESTAMP, SubtractOverload.SUBTRACT_TIMESTAMP_DURATION, @@ -211,14 +219,22 @@ public enum StandardFunction { SubtractOverload.SUBTRACT_DOUBLE, SubtractOverload.SUBTRACT_DURATION_DURATION), MULTIPLY( + Operator.MULTIPLY.getFunction(), MultiplyOverload.MULTIPLY_INT64, MultiplyOverload.MULTIPLY_DOUBLE, MultiplyOverload.MULTIPLY_UINT64), - DIVIDE(DivideOverload.DIVIDE_DOUBLE, DivideOverload.DIVIDE_INT64, DivideOverload.DIVIDE_UINT64), - MODULO(ModuloOverload.MODULO_INT64, ModuloOverload.MODULO_UINT64), - NEGATE(NegateOverload.NEGATE_INT64, NegateOverload.NEGATE_DOUBLE), - INDEX(IndexOverload.INDEX_LIST, IndexOverload.INDEX_MAP), + DIVIDE( + Operator.DIVIDE.getFunction(), + DivideOverload.DIVIDE_DOUBLE, + DivideOverload.DIVIDE_INT64, + DivideOverload.DIVIDE_UINT64), + MODULO( + Operator.MODULO.getFunction(), ModuloOverload.MODULO_INT64, ModuloOverload.MODULO_UINT64), + NEGATE( + Operator.NEGATE.getFunction(), NegateOverload.NEGATE_INT64, NegateOverload.NEGATE_DOUBLE), + INDEX(Operator.INDEX.getFunction(), IndexOverload.INDEX_LIST, IndexOverload.INDEX_MAP), SIZE( + "size", SizeOverload.SIZE_STRING, SizeOverload.SIZE_BYTES, SizeOverload.SIZE_LIST, @@ -228,22 +244,26 @@ public enum StandardFunction { SizeOverload.LIST_SIZE, SizeOverload.MAP_SIZE), INT( + "int", IntOverload.INT64_TO_INT64, IntOverload.UINT64_TO_INT64, IntOverload.DOUBLE_TO_INT64, IntOverload.STRING_TO_INT64, IntOverload.TIMESTAMP_TO_INT64), UINT( + "uint", UintOverload.UINT64_TO_UINT64, UintOverload.INT64_TO_UINT64, UintOverload.DOUBLE_TO_UINT64, UintOverload.STRING_TO_UINT64), DOUBLE( + "double", DoubleOverload.DOUBLE_TO_DOUBLE, DoubleOverload.INT64_TO_DOUBLE, DoubleOverload.STRING_TO_DOUBLE, DoubleOverload.UINT64_TO_DOUBLE), STRING( + "string", StringOverload.STRING_TO_STRING, StringOverload.INT64_TO_STRING, StringOverload.DOUBLE_TO_STRING, @@ -252,51 +272,67 @@ public enum StandardFunction { StringOverload.TIMESTAMP_TO_STRING, StringOverload.DURATION_TO_STRING, StringOverload.UINT64_TO_STRING), - BYTES(BytesOverload.BYTES_TO_BYTES, BytesOverload.STRING_TO_BYTES), - DURATION(DurationOverload.DURATION_TO_DURATION, DurationOverload.STRING_TO_DURATION), + BYTES("bytes", BytesOverload.BYTES_TO_BYTES, BytesOverload.STRING_TO_BYTES), + DURATION( + "duration", DurationOverload.DURATION_TO_DURATION, DurationOverload.STRING_TO_DURATION), TIMESTAMP( + "timestamp", TimestampOverload.STRING_TO_TIMESTAMP, TimestampOverload.TIMESTAMP_TO_TIMESTAMP, TimestampOverload.INT64_TO_TIMESTAMP), - DYN(DynOverload.TO_DYN), - MATCHES(MatchesOverload.MATCHES, MatchesOverload.MATCHES_STRING), - CONTAINS(ContainsOverload.CONTAINS_STRING), - ENDS_WITH(EndsWithOverload.ENDS_WITH_STRING), - STARTS_WITH(StartsWithOverload.STARTS_WITH_STRING), + DYN("dyn", DynOverload.TO_DYN), + MATCHES("matches", MatchesOverload.MATCHES, MatchesOverload.MATCHES_STRING), + CONTAINS("contains", ContainsOverload.CONTAINS_STRING), + ENDS_WITH("endsWith", EndsWithOverload.ENDS_WITH_STRING), + STARTS_WITH("startsWith", StartsWithOverload.STARTS_WITH_STRING), // Date/time Functions GET_FULL_YEAR( - GetFullYearOverload.TIMESTAMP_TO_YEAR, GetFullYearOverload.TIMESTAMP_TO_YEAR_WITH_TZ), - GET_MONTH(GetMonthOverload.TIMESTAMP_TO_MONTH, GetMonthOverload.TIMESTAMP_TO_MONTH_WITH_TZ), + "getFullYear", + GetFullYearOverload.TIMESTAMP_TO_YEAR, + GetFullYearOverload.TIMESTAMP_TO_YEAR_WITH_TZ), + GET_MONTH( + "getMonth", + GetMonthOverload.TIMESTAMP_TO_MONTH, + GetMonthOverload.TIMESTAMP_TO_MONTH_WITH_TZ), GET_DAY_OF_YEAR( + "getDayOfYear", GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR, GetDayOfYearOverload.TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ), GET_DAY_OF_MONTH( + "getDayOfMonth", GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH, GetDayOfMonthOverload.TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ), GET_DATE( + "getDate", GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED, GetDateOverload.TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ), GET_DAY_OF_WEEK( + "getDayOfWeek", GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK, GetDayOfWeekOverload.TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ), GET_HOURS( + "getHours", GetHoursOverload.TIMESTAMP_TO_HOURS, GetHoursOverload.TIMESTAMP_TO_HOURS_WITH_TZ, GetHoursOverload.DURATION_TO_HOURS), GET_MINUTES( + "getMinutes", GetMinutesOverload.TIMESTAMP_TO_MINUTES, GetMinutesOverload.TIMESTAMP_TO_MINUTES_WITH_TZ, GetMinutesOverload.DURATION_TO_MINUTES), GET_SECONDS( + "getSeconds", GetSecondsOverload.TIMESTAMP_TO_SECONDS, GetSecondsOverload.TIMESTAMP_TO_SECONDS_WITH_TZ, GetSecondsOverload.DURATION_TO_SECONDS), GET_MILLISECONDS( + "getMilliseconds", GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS, GetMillisecondsOverload.TIMESTAMP_TO_MILLISECONDS_WITH_TZ, GetMillisecondsOverload.DURATION_TO_MILLISECONDS), LESS( + Operator.LESS.getFunction(), LessOverload.LESS_BOOL, LessOverload.LESS_INT64, LessOverload.LESS_UINT64, @@ -312,6 +348,7 @@ public enum StandardFunction { LessOverload.LESS_UINT64_DOUBLE, LessOverload.LESS_DOUBLE_UINT64), LESS_EQUALS( + Operator.LESS_EQUALS.getFunction(), LessEqualsOverload.LESS_EQUALS_BOOL, LessEqualsOverload.LESS_EQUALS_INT64, LessEqualsOverload.LESS_EQUALS_UINT64, @@ -327,6 +364,7 @@ public enum StandardFunction { LessEqualsOverload.LESS_EQUALS_UINT64_DOUBLE, LessEqualsOverload.LESS_EQUALS_DOUBLE_UINT64), GREATER( + Operator.GREATER.getFunction(), GreaterOverload.GREATER_BOOL, GreaterOverload.GREATER_INT64, GreaterOverload.GREATER_UINT64, @@ -342,6 +380,7 @@ public enum StandardFunction { GreaterOverload.GREATER_UINT64_DOUBLE, GreaterOverload.GREATER_DOUBLE_UINT64), GREATER_EQUALS( + Operator.GREATER_EQUALS.getFunction(), GreaterEqualsOverload.GREATER_EQUALS_BOOL, GreaterEqualsOverload.GREATER_EQUALS_BYTES, GreaterEqualsOverload.GREATER_EQUALS_DOUBLE, @@ -357,9 +396,11 @@ public enum StandardFunction { GreaterEqualsOverload.GREATER_EQUALS_UINT64_DOUBLE, GreaterEqualsOverload.GREATER_EQUALS_DOUBLE_UINT64); + private final String functionName; private final ImmutableSet standardOverloads; - StandardFunction(CelStandardOverload... overloads) { + StandardFunction(String functionName, CelStandardOverload... overloads) { + this.functionName = functionName; this.standardOverloads = ImmutableSet.copyOf(overloads); } @@ -371,15 +412,25 @@ ImmutableSet getOverloads() { @VisibleForTesting ImmutableSet getOverloads() { - return standardOverloads; + return ImmutableSet.copyOf(standardOverloads.values()); } @Internal public ImmutableSet newFunctionBindings( RuntimeEquality runtimeEquality, CelOptions celOptions) { ImmutableSet.Builder builder = ImmutableSet.builder(); - for (CelStandardOverload overload : standardOverloads) { - builder.add(overload.newFunctionBinding(celOptions, runtimeEquality)); + + for (Map.Entry> entry : + standardOverloads.asMap().entrySet()) { + String functionName = entry.getKey(); + Collection overloads = entry.getValue(); + + ImmutableSet bindings = + overloads.stream() + .map(o -> o.newFunctionBinding(celOptions, runtimeEquality)) + .collect(toImmutableSet()); + + builder.addAll(CelFunctionBinding.fromOverloads(functionName, bindings)); } return builder.build(); @@ -454,39 +505,36 @@ public CelStandardFunctions build() { "You may only populate one of the following builder methods: includeFunctions," + " excludeFunctions or filterFunctions"); - ImmutableSet.Builder standardOverloadBuilder = ImmutableSet.builder(); + ImmutableMultimap.Builder standardOverloadBuilder = + ImmutableMultimap.builder(); for (StandardFunction standardFunction : StandardFunction.values()) { if (hasIncludeFunctions) { if (this.includeFunctions.contains(standardFunction)) { - standardOverloadBuilder.addAll(standardFunction.standardOverloads); + standardOverloadBuilder.putAll( + standardFunction.functionName, standardFunction.standardOverloads); } continue; } if (hasExcludeFunctions) { if (!this.excludeFunctions.contains(standardFunction)) { - standardOverloadBuilder.addAll(standardFunction.standardOverloads); + standardOverloadBuilder.putAll( + standardFunction.functionName, standardFunction.standardOverloads); } continue; } if (hasFilterFunction) { - ImmutableSet.Builder filteredOverloadsBuilder = - ImmutableSet.builder(); for (CelStandardOverload standardOverload : standardFunction.standardOverloads) { boolean includeOverload = functionFilter.include(standardFunction, standardOverload); if (includeOverload) { - standardOverloadBuilder.add(standardOverload); + standardOverloadBuilder.put(standardFunction.functionName, standardOverload); } } - ImmutableSet filteredOverloads = filteredOverloadsBuilder.build(); - if (!filteredOverloads.isEmpty()) { - standardOverloadBuilder.addAll(filteredOverloads); - } - continue; } - standardOverloadBuilder.addAll(standardFunction.standardOverloads); + standardOverloadBuilder.putAll( + standardFunction.functionName, standardFunction.standardOverloads); } return new CelStandardFunctions(standardOverloadBuilder.build()); @@ -511,7 +559,7 @@ static boolean isHeterogeneousComparison(CelStandardOverload overload) { return HETEROGENEOUS_COMPARISON_OPERATORS.contains(overload); } - private CelStandardFunctions(ImmutableSet standardOverloads) { + private CelStandardFunctions(ImmutableMultimap standardOverloads) { this.standardOverloads = standardOverloads; } } diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 35ce243b3..b5e5ca55c 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -19,11 +19,13 @@ import com.google.auto.value.AutoBuilder; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelErrorCode; import dev.cel.common.annotations.Internal; +import dev.cel.runtime.FunctionBindingImpl.DynamicDispatchOverload; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -127,7 +129,7 @@ public abstract static class Builder { @CanIgnoreReturnValue public Builder addOverload( String overloadId, - List> argTypes, + ImmutableList> argTypes, boolean isStrict, CelFunctionOverload overload) { checkNotNull(overloadId); @@ -136,13 +138,36 @@ public Builder addOverload( checkNotNull(overload); overloadsBuilder() - .put(overloadId, CelResolvedOverload.of(overloadId, overload, isStrict, argTypes)); + .put( + overloadId, + CelResolvedOverload.of( + overloadId, + args -> guardedOp(overloadId, args, argTypes, isStrict, overload), + isStrict, + argTypes)); return this; } public abstract DefaultDispatcher build(); } + /** Creates an invocation guard around the overload definition. */ + private static Object guardedOp( + String functionName, + Object[] args, + ImmutableList> argTypes, + boolean isStrict, + CelFunctionOverload overload) + throws CelEvaluationException { + // Argument checking for DynamicDispatch is handled inside the overload's apply method itself. + if (overload instanceof DynamicDispatchOverload + || CelFunctionOverload.canHandle(args, argTypes, isStrict)) { + return overload.apply(args); + } + + throw new IllegalArgumentException("No matching overload for function: " + functionName); + } + DefaultDispatcher(ImmutableMap overloads) { this.overloads = overloads; } diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java index b554ce41a..4dcdcb74f 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java @@ -15,6 +15,8 @@ package dev.cel.runtime; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.errorprone.annotations.Immutable; @Immutable @@ -58,4 +60,88 @@ public boolean isStrict() { this.definition = definition; this.isStrict = isStrict; } + + static ImmutableSet groupOverloadsToFunction( + String functionName, ImmutableSet overloadBindings) { + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.addAll(overloadBindings); + + // If there is already a binding with the same name as the function, we treat it as a + // "Singleton" binding and do not create a dynamic dispatch wrapper for it. + // (Ex: "matches" function) + boolean hasSingletonBinding = + overloadBindings.stream().anyMatch(b -> b.getOverloadId().equals(functionName)); + + if (!hasSingletonBinding) { + if (overloadBindings.size() == 1) { + CelFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); + builder.add( + new FunctionBindingImpl( + functionName, + singleBinding.getArgTypes(), + singleBinding.getDefinition(), + singleBinding.isStrict())); + } else { + builder.add(new DynamicDispatchBinding(functionName, overloadBindings)); + } + } + + return builder.build(); + } + + @Immutable + static final class DynamicDispatchBinding implements CelFunctionBinding { + + private final boolean isStrict; + private final DynamicDispatchOverload dynamicDispatchOverload; + + @Override + public String getOverloadId() { + return dynamicDispatchOverload.functionName; + } + + @Override + public ImmutableList> getArgTypes() { + return ImmutableList.of(); + } + + @Override + public CelFunctionOverload getDefinition() { + return dynamicDispatchOverload; + } + + @Override + public boolean isStrict() { + return isStrict; + } + + private DynamicDispatchBinding( + String functionName, ImmutableSet overloadBindings) { + this.isStrict = overloadBindings.stream().allMatch(CelFunctionBinding::isStrict); + this.dynamicDispatchOverload = new DynamicDispatchOverload(functionName, overloadBindings); + } + } + + @Immutable + static final class DynamicDispatchOverload implements CelFunctionOverload { + private final String functionName; + private final ImmutableSet overloadBindings; + + @Override + public Object apply(Object[] args) throws CelEvaluationException { + for (CelFunctionBinding overload : overloadBindings) { + if (CelFunctionOverload.canHandle(args, overload.getArgTypes(), overload.isStrict())) { + return overload.getDefinition().apply(args); + } + } + + throw new IllegalArgumentException("No matching overload for function: " + functionName); + } + + private DynamicDispatchOverload( + String functionName, ImmutableSet overloadBindings) { + this.functionName = functionName; + this.overloadBindings = overloadBindings; + } + } } diff --git a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java index 73d53287c..f9f919413 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/CelStandardFunction.java @@ -15,6 +15,7 @@ package dev.cel.runtime.standard; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; @@ -34,12 +35,12 @@ public abstract class CelStandardFunction { public ImmutableSet newFunctionBindings( CelOptions celOptions, RuntimeEquality runtimeEquality) { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (CelStandardOverload overload : overloads) { - builder.add(overload.newFunctionBinding(celOptions, runtimeEquality)); - } + ImmutableSet overloadBindings = + overloads.stream() + .map(overload -> overload.newFunctionBinding(celOptions, runtimeEquality)) + .collect(toImmutableSet()); - return builder.build(); + return CelFunctionBinding.fromOverloads(name, overloadBindings); } CelStandardFunction(String name, ImmutableSet overloads) { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 2106fa5fe..6749be24f 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -20,7 +20,6 @@ java_library( "//common:compiler_common", "//common:container", "//common:error_codes", - "//common:operator", "//common:options", "//common/ast", "//common/exceptions:divide_by_zero", @@ -44,21 +43,10 @@ java_library( "//runtime:dispatcher", "//runtime:function_binding", "//runtime:program", - "//runtime:resolved_overload", "//runtime:runtime_equality", "//runtime:runtime_helpers", + "//runtime:standard_functions", "//runtime/planner:program_planner", - "//runtime/standard:add", - "//runtime/standard:divide", - "//runtime/standard:dyn", - "//runtime/standard:equals", - "//runtime/standard:greater", - "//runtime/standard:greater_equals", - "//runtime/standard:index", - "//runtime/standard:less", - "//runtime/standard:logical_not", - "//runtime/standard:not_strictly_false", - "//runtime/standard:standard_function", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_testparameterinjector_test_parameter_injector", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 52ab5e417..ba0c8a548 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -25,7 +25,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.primitives.UnsignedLong; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; @@ -36,7 +35,6 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelSource; -import dev.cel.common.Operator; import dev.cel.common.ast.CelExpr; import dev.cel.common.exceptions.CelDivideByZeroException; import dev.cel.common.internal.CelDescriptorPool; @@ -69,23 +67,12 @@ import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; -import dev.cel.runtime.CelFunctionOverload; -import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.CelStandardFunctions; +import dev.cel.runtime.CelStandardFunctions.StandardFunction; import dev.cel.runtime.DefaultDispatcher; import dev.cel.runtime.Program; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; -import dev.cel.runtime.standard.AddOperator; -import dev.cel.runtime.standard.CelStandardFunction; -import dev.cel.runtime.standard.DivideOperator; -import dev.cel.runtime.standard.DynFunction; -import dev.cel.runtime.standard.EqualsOperator; -import dev.cel.runtime.standard.GreaterEqualsOperator; -import dev.cel.runtime.standard.GreaterOperator; -import dev.cel.runtime.standard.IndexOperator; -import dev.cel.runtime.standard.LessOperator; -import dev.cel.runtime.standard.LogicalNotOperator; -import dev.cel.runtime.standard.NotStrictlyFalseFunction; import org.junit.Test; import org.junit.runner.RunWith; @@ -162,136 +149,88 @@ private static DefaultDispatcher newDispatcher() { DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); // Subsetted StdLib - addBindings( - builder, Operator.INDEX.getFunction(), fromStandardFunction(IndexOperator.create())); - addBindings( - builder, - Operator.LOGICAL_NOT.getFunction(), - fromStandardFunction(LogicalNotOperator.create())); - addBindings(builder, Operator.ADD.getFunction(), fromStandardFunction(AddOperator.create())); - addBindings( - builder, Operator.GREATER.getFunction(), fromStandardFunction(GreaterOperator.create())); - addBindings( - builder, - Operator.GREATER_EQUALS.getFunction(), - fromStandardFunction(GreaterEqualsOperator.create())); - addBindings(builder, Operator.LESS.getFunction(), fromStandardFunction(LessOperator.create())); - addBindings( - builder, Operator.DIVIDE.getFunction(), fromStandardFunction(DivideOperator.create())); - addBindings( - builder, Operator.EQUALS.getFunction(), fromStandardFunction(EqualsOperator.create())); - addBindings( - builder, - Operator.NOT_STRICTLY_FALSE.getFunction(), - fromStandardFunction(NotStrictlyFalseFunction.create())); - addBindings(builder, "dyn", fromStandardFunction(DynFunction.create())); + CelStandardFunctions stdFunctions = + CelStandardFunctions.newBuilder() + .includeFunctions( + StandardFunction.INDEX, + StandardFunction.LOGICAL_NOT, + StandardFunction.ADD, + StandardFunction.GREATER, + StandardFunction.GREATER_EQUALS, + StandardFunction.LESS, + StandardFunction.DIVIDE, + StandardFunction.EQUALS, + StandardFunction.NOT_STRICTLY_FALSE, + StandardFunction.DYN) + .build(); + addBindingsToDispatcher( + builder, stdFunctions.newFunctionBindings(RUNTIME_EQUALITY, CEL_OPTIONS)); // Custom functions - addBindings( + addBindingsToDispatcher( builder, - "zero", - CelFunctionBinding.from("zero_overload", ImmutableList.of(), (unused) -> 0L)); - addBindings( + CelFunctionBinding.fromOverloads( + "zero", CelFunctionBinding.from("zero_overload", ImmutableList.of(), (unused) -> 0L))); + + addBindingsToDispatcher( builder, - "error", - CelFunctionBinding.from( - "error_overload", - ImmutableList.of(), - (unused) -> { - throw new IllegalArgumentException("Intentional error"); - })); - addBindings( + CelFunctionBinding.fromOverloads( + "error", + CelFunctionBinding.from( + "error_overload", + ImmutableList.of(), + (unused) -> { + throw new IllegalArgumentException("Intentional error"); + }))); + + addBindingsToDispatcher( builder, - "neg", - CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), - CelFunctionBinding.from("neg_double", Double.class, arg -> -arg)); - addBindings( + CelFunctionBinding.fromOverloads( + "neg", + CelFunctionBinding.from("neg_int", Long.class, arg -> -arg), + CelFunctionBinding.from("neg_double", Double.class, arg -> -arg))); + + addBindingsToDispatcher( builder, - "cel.expr.conformance.proto3.power", - CelFunctionBinding.from( - "power_int_int", - Long.class, - Long.class, - (value, power) -> (long) Math.pow(value, power))); - addBindings( + CelFunctionBinding.fromOverloads( + "cel.expr.conformance.proto3.power", + CelFunctionBinding.from( + "power_int_int", + Long.class, + Long.class, + (value, power) -> (long) Math.pow(value, power)))); + + addBindingsToDispatcher( builder, - "concat", - CelFunctionBinding.from( - "concat_bytes_bytes", - CelByteString.class, - CelByteString.class, - ProgramPlannerTest::concatenateByteArrays), - CelFunctionBinding.from( - "bytes_concat_bytes", - CelByteString.class, - CelByteString.class, - ProgramPlannerTest::concatenateByteArrays)); + CelFunctionBinding.fromOverloads( + "concat", + CelFunctionBinding.from( + "concat_bytes_bytes", + CelByteString.class, + CelByteString.class, + ProgramPlannerTest::concatenateByteArrays), + CelFunctionBinding.from( + "bytes_concat_bytes", + CelByteString.class, + CelByteString.class, + ProgramPlannerTest::concatenateByteArrays))); return builder.build(); } - private static void addBindings( - DefaultDispatcher.Builder builder, - String functionName, - CelFunctionBinding... functionBindings) { - addBindings(builder, functionName, ImmutableSet.copyOf(functionBindings)); - } - - private static void addBindings( - DefaultDispatcher.Builder builder, - String functionName, - ImmutableCollection overloadBindings) { + private static void addBindingsToDispatcher( + DefaultDispatcher.Builder builder, ImmutableCollection overloadBindings) { if (overloadBindings.isEmpty()) { throw new IllegalArgumentException("Invalid bindings"); } - // TODO: Runtime top-level APIs currently does not allow grouping overloads with - // the function name. This capability will have to be added. - if (overloadBindings.size() == 1) { - CelFunctionBinding singleBinding = Iterables.getOnlyElement(overloadBindings); - builder.addOverload( - functionName, - singleBinding.getArgTypes(), - singleBinding.isStrict(), - args -> guardedOp(functionName, args, singleBinding)); - } else { - overloadBindings.forEach( - overload -> - builder.addOverload( - overload.getOverloadId(), - overload.getArgTypes(), - overload.isStrict(), - args -> guardedOp(functionName, args, overload))); - - // Setup dynamic dispatch - CelFunctionOverload dynamicDispatchDef = - args -> { - for (CelFunctionBinding overload : overloadBindings) { - if (CelResolvedOverload.canHandle( - args, overload.getArgTypes(), overload.isStrict())) { - return overload.getDefinition().apply(args); - } - } - - throw new IllegalArgumentException( - "No matching overload for function: " + functionName); - }; - - boolean allOverloadsStrict = overloadBindings.stream().allMatch(CelFunctionBinding::isStrict); - builder.addOverload( - functionName, ImmutableList.of(), /* isStrict= */ allOverloadsStrict, dynamicDispatchDef); - } - } - /** Creates an invocation guard around the overload definition. */ - private static Object guardedOp( - String functionName, Object[] args, CelFunctionBinding singleBinding) - throws CelEvaluationException { - if (!CelResolvedOverload.canHandle( - args, singleBinding.getArgTypes(), singleBinding.isStrict())) { - throw new IllegalArgumentException("No matching overload for function: " + functionName); - } - - return singleBinding.getDefinition().apply(args); + overloadBindings.forEach( + overload -> + builder.addOverload( + overload.getOverloadId(), + overload.getArgTypes(), + overload.isStrict(), + overload.getDefinition())); } @TestParameter boolean isParseOnly; @@ -872,11 +811,6 @@ private static CelByteString concatenateByteArrays(CelByteString bytes1, CelByte return bytes1.concat(bytes2); } - private static ImmutableSet fromStandardFunction( - CelStandardFunction standardFunction) { - return standardFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY); - } - @SuppressWarnings("ImmutableEnumChecker") // Test only private enum ConstantTestCase { NULL("null", NullValue.NULL_VALUE), From b84982e30589eab1394443d436100e5904349132 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 7 Jan 2026 19:09:20 -0800 Subject: [PATCH 054/100] Fix type-checker to search local scopes for identifiers before container resolution PiperOrigin-RevId: 853502183 --- .../src/main/java/dev/cel/checker/Env.java | 75 +++++++++++++------ .../src/test/resources/comprehension.baseline | 8 ++ .../dev/cel/testing/BaseInterpreterTest.java | 5 ++ 3 files changed, 66 insertions(+), 22 deletions(-) diff --git a/checker/src/main/java/dev/cel/checker/Env.java b/checker/src/main/java/dev/cel/checker/Env.java index d5692d48c..15079cff7 100644 --- a/checker/src/main/java/dev/cel/checker/Env.java +++ b/checker/src/main/java/dev/cel/checker/Env.java @@ -454,35 +454,66 @@ public Env add(String name, Type type) { *

    Returns {@code null} if the function cannot be found. */ public @Nullable CelIdentDecl tryLookupCelIdent(CelContainer container, String name) { + // Attempt to find the decl with just the ident name to account for shadowed variables + CelIdentDecl decl = tryLookupCelIdentFromLocalScopes(name); + if (decl != null) { + return decl; + } + for (String cand : container.resolveCandidateNames(name)) { - // First determine whether we know this name already. - CelIdentDecl decl = findIdentDecl(cand); + decl = tryLookupCelIdent(cand); if (decl != null) { return decl; } + } - // Next try to import the name as a reference to a message type. - // This is done via the type provider. - Optional type = typeProvider.lookupCelType(cand); - if (type.isPresent()) { - decl = CelIdentDecl.newIdentDeclaration(cand, type.get()); - decls.get(0).putIdent(decl); - return decl; - } + return null; + } - // Next try to import this as an enum value by splitting the name in a type prefix and - // the enum inside. - Integer enumValue = typeProvider.lookupEnumValue(cand); - if (enumValue != null) { - decl = - CelIdentDecl.newBuilder() - .setName(cand) - .setType(SimpleType.INT) - .setConstant(CelConstant.ofValue(enumValue)) - .build(); + private @Nullable CelIdentDecl tryLookupCelIdent(String cand) { + // First determine whether we know this name already. + CelIdentDecl decl = findIdentDecl(cand); + if (decl != null) { + return decl; + } - decls.get(0).putIdent(decl); - return decl; + // Next try to import the name as a reference to a message type. + // This is done via the type provider. + Optional type = typeProvider.lookupCelType(cand); + if (type.isPresent()) { + decl = CelIdentDecl.newIdentDeclaration(cand, type.get()); + decls.get(0).putIdent(decl); + return decl; + } + + // Next try to import this as an enum value by splitting the name in a type prefix and + // the enum inside. + Integer enumValue = typeProvider.lookupEnumValue(cand); + if (enumValue != null) { + decl = + CelIdentDecl.newBuilder() + .setName(cand) + .setType(SimpleType.INT) + .setConstant(CelConstant.ofValue(enumValue)) + .build(); + + decls.get(0).putIdent(decl); + return decl; + } + return null; + } + + private @Nullable CelIdentDecl tryLookupCelIdentFromLocalScopes(String name) { + int firstUserSpaceScope = 2; + // Iterate from the top of the stack down to the first local scope. + // Note that: + // Scope 0: Standard environment + // Scope 1: User defined environment + // Scope 2 and onwards: comprehension scopes + for (int i = decls.size() - 1; i >= firstUserSpaceScope; i--) { + CelIdentDecl ident = decls.get(i).getIdent(name); + if (ident != null) { + return ident; } } return null; diff --git a/runtime/src/test/resources/comprehension.baseline b/runtime/src/test/resources/comprehension.baseline index 45f9f211c..82aea0ae6 100644 --- a/runtime/src/test/resources/comprehension.baseline +++ b/runtime/src/test/resources/comprehension.baseline @@ -12,3 +12,11 @@ Source: [0, 1, 2].exists(x, x > 2) =====> bindings: {} result: false + +Source: [0].exists(x, x == 0) +declare com.x { + value int +} +=====> +bindings: {com.x=1} +result: true \ No newline at end of file diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index b69e4fb7e..1ee13a70d 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -858,6 +858,11 @@ public void comprehension() throws Exception { source = "[0, 1, 2].exists(x, x > 2)"; runTest(); + + declareVariable("com.x", SimpleType.INT); + container = CelContainer.ofName("com"); + source = "[0].exists(x, x == 0)"; + runTest(ImmutableMap.of("com.x", 1)); } @Test From f7b9112f4366a2f0e6bd01aea47e98602fa939b6 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 8 Jan 2026 10:04:58 -0800 Subject: [PATCH 055/100] Plan late function bindings, add CelOverloadNotFound exception PiperOrigin-RevId: 853788116 --- common/exceptions/BUILD.bazel | 6 ++ .../dev/cel/common/exceptions/BUILD.bazel | 13 +++ .../CelOverloadNotFoundException.java | 43 ++++++++++ runtime/BUILD.bazel | 8 ++ .../src/main/java/dev/cel/runtime/BUILD.bazel | 24 +++++- .../dev/cel/runtime/CelFunctionResolver.java | 17 +++- .../cel/runtime/CelLateFunctionBindings.java | 14 +++- .../main/java/dev/cel/runtime/CelRuntime.java | 10 --- .../dev/cel/runtime/DefaultDispatcher.java | 15 +++- .../dev/cel/runtime/FunctionBindingImpl.java | 9 ++- .../java/dev/cel/runtime/LiteProgramImpl.java | 12 +++ .../main/java/dev/cel/runtime/Program.java | 10 +++ .../java/dev/cel/runtime/planner/BUILD.bazel | 31 ++++++- .../runtime/planner/EvalLateBoundCall.java | 80 +++++++++++++++++++ .../dev/cel/runtime/planner/EvalUnary.java | 24 +++++- .../cel/runtime/planner/EvalVarArgsCall.java | 27 +++++-- .../cel/runtime/planner/EvalZeroArity.java | 19 ++++- .../cel/runtime/planner/ExecutionFrame.java | 29 ++++--- .../cel/runtime/planner/PlannedProgram.java | 46 +++++++++-- .../cel/runtime/planner/ProgramPlanner.java | 61 ++++++++++---- .../runtime/planner/ProgramPlannerTest.java | 43 +++++++++- 21 files changed, 471 insertions(+), 70 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelOverloadNotFoundException.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java diff --git a/common/exceptions/BUILD.bazel b/common/exceptions/BUILD.bazel index e24f2629c..4e52113e7 100644 --- a/common/exceptions/BUILD.bazel +++ b/common/exceptions/BUILD.bazel @@ -52,3 +52,9 @@ java_library( # used_by_android exports = ["//common/src/main/java/dev/cel/common/exceptions:iteration_budget_exceeded"], ) + +java_library( + name = "overload_not_found", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:overload_not_found"], +) diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel index d000ef926..755e037d9 100644 --- a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel @@ -110,3 +110,16 @@ java_library( "//common/annotations", ], ) + +java_library( + name = "overload_not_found", + srcs = ["CelOverloadNotFoundException.java"], + # used_by_android + tags = [ + ], + deps = [ + ":runtime_exception", + "//common:error_codes", + "//common/annotations", + ], +) diff --git a/common/src/main/java/dev/cel/common/exceptions/CelOverloadNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelOverloadNotFoundException.java new file mode 100644 index 000000000..d7c4a01a8 --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelOverloadNotFoundException.java @@ -0,0 +1,43 @@ +// 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; +import java.util.Collection; +import java.util.Collections; + +/** Indicates that a matching overload could not be found during function dispatch. */ +@Internal +public final class CelOverloadNotFoundException extends CelRuntimeException { + + public CelOverloadNotFoundException(String functionName) { + this(functionName, Collections.emptyList()); + } + + public CelOverloadNotFoundException(String functionName, Collection overloadIds) { + super(formatErrorMessage(functionName, overloadIds), CelErrorCode.OVERLOAD_NOT_FOUND); + } + + private static String formatErrorMessage(String functionName, Collection overloadIds) { + StringBuilder sb = new StringBuilder(); + sb.append("No matching overload for function '").append(functionName).append("'."); + if (!overloadIds.isEmpty()) { + sb.append(" Overload candidates: ").append(String.join(", ", overloadIds)); + } + + return sb.toString(); + } +} diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 07bfdebbc..244145960 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -16,6 +16,7 @@ java_library( "//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider", "//runtime/src/main/java/dev/cel/runtime:function_overload", "//runtime/src/main/java/dev/cel/runtime:runtime_type_provider", + "//runtime/src/main/java/dev/cel/runtime:variable_resolver", ], ) @@ -263,3 +264,10 @@ java_library( "//runtime/src/main/java/dev/cel/runtime:concatenated_list_view", ], ) + +java_library( + name = "variable_resolver", + exports = [ + "//runtime/src/main/java/dev/cel/runtime:variable_resolver", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 3b0e2762a..d253edba3 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -129,6 +129,7 @@ java_library( "//:auto_value", "//common:error_codes", "//common/annotations", + "//common/exceptions:overload_not_found", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -149,6 +150,7 @@ cel_android_library( "//:auto_value", "//common:error_codes", "//common/annotations", + "//common/exceptions:overload_not_found", "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", @@ -477,8 +479,6 @@ RUNTIME_SOURCES = [ "CelRuntimeFactory.java", "CelRuntimeLegacyImpl.java", "CelRuntimeLibrary.java", - "CelVariableResolver.java", - "HierarchicalVariableResolver.java", "ProgramImpl.java", "UnknownContext.java", ] @@ -729,6 +729,7 @@ java_library( deps = [ ":evaluation_exception", ":function_overload", + "//common/exceptions:overload_not_found", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -742,6 +743,7 @@ cel_android_library( deps = [ ":evaluation_exception", ":function_overload_android", + "//common/exceptions:overload_not_found", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -836,6 +838,7 @@ java_library( "//common/types:cel_types", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", + "//runtime:variable_resolver", "//runtime/standard:add", "//runtime/standard:int", "//runtime/standard:timestamp", @@ -904,6 +907,7 @@ java_library( ":interpretable", ":program", "//:auto_value", + "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -917,6 +921,7 @@ cel_android_library( ":function_resolver_android", ":interpretable_android", ":program_android", + ":variable_resolver", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", ], @@ -1204,6 +1209,19 @@ cel_android_library( ], ) +java_library( + name = "variable_resolver", + srcs = [ + "CelVariableResolver.java", + "HierarchicalVariableResolver.java", + ], + # used_by_android + tags = [ + ], + deps = [ + ], +) + java_library( name = "program", srcs = ["Program.java"], @@ -1212,6 +1230,7 @@ java_library( deps = [ ":evaluation_exception", ":function_resolver", + ":variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -1224,6 +1243,7 @@ cel_android_library( deps = [ ":evaluation_exception", ":function_resolver_android", + ":variable_resolver", "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", ], diff --git a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java index 1c5491c14..2fb136a1a 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/CelFunctionResolver.java @@ -15,7 +15,7 @@ package dev.cel.runtime; import javax.annotation.concurrent.ThreadSafe; -import java.util.List; +import java.util.Collection; import java.util.Optional; /** @@ -36,5 +36,18 @@ public interface CelFunctionResolver { * @throws CelEvaluationException if the overload resolution is ambiguous, */ Optional findOverloadMatchingArgs( - String functionName, List overloadIds, Object[] args) throws CelEvaluationException; + String functionName, Collection overloadIds, Object[] args) + throws CelEvaluationException; + + /** + * Finds a specific function overload to invoke based on given parameters, scanning all available + * overloads if necessary. + * + * @param functionName the logical name of the function being invoked. + * @param args The arguments to pass to the function. + * @return an optional value of the resolved overload. + * @throws CelEvaluationException if the overload resolution is ambiguous. + */ + Optional findOverloadMatchingArgs(String functionName, Object[] args) + throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java index f533766b2..c1f4b236f 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java +++ b/runtime/src/main/java/dev/cel/runtime/CelLateFunctionBindings.java @@ -19,7 +19,7 @@ import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.Immutable; import java.util.Arrays; -import java.util.List; +import java.util.Collection; import java.util.Optional; /** @@ -37,15 +37,23 @@ private CelLateFunctionBindings(ImmutableMap functi @Override public Optional findOverloadMatchingArgs( - String functionName, List overloadIds, Object[] args) throws CelEvaluationException { + String functionName, Collection overloadIds, Object[] args) + throws CelEvaluationException { return DefaultDispatcher.findOverloadMatchingArgs(functionName, overloadIds, functions, args); } + @Override + public Optional findOverloadMatchingArgs(String functionName, Object[] args) + throws CelEvaluationException { + return DefaultDispatcher.findOverloadMatchingArgs( + functionName, functions.keySet(), functions, args); + } + public static CelLateFunctionBindings from(CelFunctionBinding... functions) { return from(Arrays.asList(functions)); } - public static CelLateFunctionBindings from(List functions) { + public static CelLateFunctionBindings from(Collection functions) { return new CelLateFunctionBindings( functions.stream() .collect( diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java index a42b7f969..416bca132 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntime.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntime.java @@ -42,16 +42,6 @@ interface Program extends dev.cel.runtime.Program { /** Evaluate the expression using {@code message} fields as the source of input variables. */ Object eval(Message message) throws CelEvaluationException; - /** Evaluate a compiled program with a custom variable {@code resolver}. */ - Object eval(CelVariableResolver resolver) throws CelEvaluationException; - - /** - * Evaluate a compiled program with a custom variable {@code resolver} and late-bound functions - * {@code lateBoundFunctionResolver}. - */ - Object eval(CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver) - throws CelEvaluationException; - /** * Trace evaluates a compiled program without any variables and invokes the listener as * evaluation progresses through the AST. diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index b5e5ca55c..0d13f13be 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -25,8 +25,10 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelErrorCode; import dev.cel.common.annotations.Internal; +import dev.cel.common.exceptions.CelOverloadNotFoundException; import dev.cel.runtime.FunctionBindingImpl.DynamicDispatchOverload; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -46,16 +48,23 @@ public Optional findOverload(String functionName) { return Optional.ofNullable(overloads.get(functionName)); } + @Override + public Optional findOverloadMatchingArgs(String functionName, Object[] args) + throws CelEvaluationException { + return findOverloadMatchingArgs(functionName, overloads.keySet(), overloads, args); + } + @Override public Optional findOverloadMatchingArgs( - String functionName, List overloadIds, Object[] args) throws CelEvaluationException { + String functionName, Collection overloadIds, Object[] args) + throws CelEvaluationException { return findOverloadMatchingArgs(functionName, overloadIds, overloads, args); } /** Finds the overload that matches the given function name, overload IDs, and arguments. */ static Optional findOverloadMatchingArgs( String functionName, - List overloadIds, + Collection overloadIds, Map overloads, Object[] args) throws CelEvaluationException { @@ -165,7 +174,7 @@ private static Object guardedOp( return overload.apply(args); } - throw new IllegalArgumentException("No matching overload for function: " + functionName); + throw new CelOverloadNotFoundException(functionName); } DefaultDispatcher(ImmutableMap overloads) { diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java index 4dcdcb74f..48c0eb47a 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java @@ -14,10 +14,13 @@ package dev.cel.runtime; +import static com.google.common.collect.ImmutableList.toImmutableList; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.exceptions.CelOverloadNotFoundException; @Immutable final class FunctionBindingImpl implements CelFunctionBinding { @@ -135,7 +138,11 @@ public Object apply(Object[] args) throws CelEvaluationException { } } - throw new IllegalArgumentException("No matching overload for function: " + functionName); + throw new CelOverloadNotFoundException( + functionName, + overloadBindings.stream() + .map(CelFunctionBinding::getOverloadId) + .collect(toImmutableList())); } private DynamicDispatchOverload( diff --git a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java index e54f848b7..5e57f497b 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java @@ -40,6 +40,18 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio return interpretable().eval(Activation.copyOf(mapValue), lateBoundFunctionResolver); } + @Override + public Object eval(CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver) { + // TODO: Wire in program planner + throw new UnsupportedOperationException("To be implemented"); + } + + @Override + public Object eval(CelVariableResolver resolver) throws CelEvaluationException { + // TODO: Wire in program planner + throw new UnsupportedOperationException("To be implemented"); + } + static Program plan(Interpretable interpretable) { return new AutoValue_LiteProgramImpl(interpretable); } diff --git a/runtime/src/main/java/dev/cel/runtime/Program.java b/runtime/src/main/java/dev/cel/runtime/Program.java index 3eb074317..c0982f1f8 100644 --- a/runtime/src/main/java/dev/cel/runtime/Program.java +++ b/runtime/src/main/java/dev/cel/runtime/Program.java @@ -33,4 +33,14 @@ public interface Program { */ Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException; + + /** Evaluate a compiled program with a custom variable {@code resolver}. */ + Object eval(CelVariableResolver resolver) throws CelEvaluationException; + + /** + * Evaluate a compiled program with a custom variable {@code resolver} and late-bound functions + * {@code lateBoundFunctionResolver}. + */ + Object eval(CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index a6fe64c05..92733b1b8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -23,6 +23,7 @@ java_library( ":eval_create_map", ":eval_create_struct", ":eval_fold", + ":eval_late_bound_call", ":eval_or", ":eval_test_only", ":eval_unary", @@ -40,6 +41,7 @@ java_library( "//common:options", "//common/annotations", "//common/ast", + "//common/exceptions:overload_not_found", "//common/types", "//common/types:type_providers", "//common/values", @@ -74,6 +76,7 @@ java_library( "//runtime:function_resolver", "//runtime:interpretable", "//runtime:program", + "//runtime:resolved_overload", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -191,6 +194,8 @@ java_library( deps = [ ":execution_frame", ":planned_interpretable", + "//common/values", + "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", @@ -204,6 +209,8 @@ java_library( ":eval_helpers", ":execution_frame", ":planned_interpretable", + "//common/values", + "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", @@ -217,9 +224,28 @@ java_library( ":eval_helpers", ":execution_frame", ":planned_interpretable", + "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", + "//runtime:interpretable", + "//runtime:resolved_overload", + ], +) + +java_library( + name = "eval_late_bound_call", + srcs = ["EvalLateBoundCall.java"], + deps = [ + ":eval_helpers", + ":execution_frame", + ":planned_interpretable", + "//common/exceptions:overload_not_found", + "//common/values", + "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", + "@maven//:com_google_guava_guava", ], ) @@ -324,8 +350,9 @@ java_library( deps = [ "//common:options", "//common/exceptions:iteration_budget_exceeded", - "//runtime:interpretable", - "@maven//:org_jspecify_jspecify", + "//runtime:evaluation_exception", + "//runtime:function_resolver", + "//runtime:resolved_overload", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java new file mode 100644 index 000000000..dedef58ca --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java @@ -0,0 +1,80 @@ +// 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.runtime.planner; + +import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; + +import com.google.common.collect.ImmutableList; +import dev.cel.common.exceptions.CelOverloadNotFoundException; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.GlobalResolver; + +final class EvalLateBoundCall extends PlannedInterpretable { + + private final String functionName; + private final ImmutableList overloadIds; + + @SuppressWarnings("Immutable") + private final PlannedInterpretable[] args; + + private final CelValueConverter celValueConverter; + + @Override + public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { + Object[] argVals = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + PlannedInterpretable arg = args[i]; + // Late bound functions are assumed to be strict. + argVals[i] = evalStrictly(arg, resolver, frame); + } + + CelResolvedOverload resolvedOverload = + frame + .findOverload(functionName, overloadIds, argVals) + .orElseThrow(() -> new CelOverloadNotFoundException(functionName, overloadIds)); + + Object result = resolvedOverload.getDefinition().apply(argVals); + Object runtimeValue = celValueConverter.toRuntimeValue(result); + if (runtimeValue instanceof CelValue) { + return celValueConverter.unwrap((CelValue) runtimeValue); + } + return runtimeValue; + } + + static EvalLateBoundCall create( + long exprId, + String functionName, + ImmutableList overloadIds, + PlannedInterpretable[] args, + CelValueConverter celValueConverter) { + return new EvalLateBoundCall(exprId, functionName, overloadIds, args, celValueConverter); + } + + private EvalLateBoundCall( + long exprId, + String functionName, + ImmutableList overloadIds, + PlannedInterpretable[] args, + CelValueConverter celValueConverter) { + super(exprId); + this.functionName = functionName; + this.overloadIds = overloadIds; + this.args = args; + this.celValueConverter = celValueConverter; + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java index d1a33017b..e936c511b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -17,6 +17,8 @@ import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; @@ -25,6 +27,7 @@ final class EvalUnary extends PlannedInterpretable { private final CelResolvedOverload resolvedOverload; private final PlannedInterpretable arg; + private final CelValueConverter celValueConverter; @Override public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { @@ -34,17 +37,30 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval : evalNonstrictly(arg, resolver, frame); Object[] arguments = new Object[] {argVal}; - return resolvedOverload.getDefinition().apply(arguments); + Object result = resolvedOverload.getDefinition().apply(arguments); + Object runtimeValue = celValueConverter.toRuntimeValue(result); + if (runtimeValue instanceof CelValue) { + return celValueConverter.unwrap((CelValue) runtimeValue); + } + return runtimeValue; } static EvalUnary create( - long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) { - return new EvalUnary(exprId, resolvedOverload, arg); + long exprId, + CelResolvedOverload resolvedOverload, + PlannedInterpretable arg, + CelValueConverter celValueConverter) { + return new EvalUnary(exprId, resolvedOverload, arg, celValueConverter); } - private EvalUnary(long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable arg) { + private EvalUnary( + long exprId, + CelResolvedOverload resolvedOverload, + PlannedInterpretable arg, + CelValueConverter celValueConverter) { super(exprId); this.resolvedOverload = resolvedOverload; this.arg = arg; + this.celValueConverter = celValueConverter; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index da2979ad1..a7f2b643d 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -17,16 +17,21 @@ import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; -@SuppressWarnings("Immutable") final class EvalVarArgsCall extends PlannedInterpretable { private final CelResolvedOverload resolvedOverload; + + @SuppressWarnings("Immutable") private final PlannedInterpretable[] args; + private final CelValueConverter celValueConverter; + @Override public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { Object[] argVals = new Object[args.length]; @@ -38,18 +43,30 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval : evalNonstrictly(arg, resolver, frame); } - return resolvedOverload.getDefinition().apply(argVals); + Object result = resolvedOverload.getDefinition().apply(argVals); + Object runtimeValue = celValueConverter.toRuntimeValue(result); + if (runtimeValue instanceof CelValue) { + return celValueConverter.unwrap((CelValue) runtimeValue); + } + return runtimeValue; } static EvalVarArgsCall create( - long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) { - return new EvalVarArgsCall(exprId, resolvedOverload, args); + long exprId, + CelResolvedOverload resolvedOverload, + PlannedInterpretable[] args, + CelValueConverter celValueConverter) { + return new EvalVarArgsCall(exprId, resolvedOverload, args, celValueConverter); } private EvalVarArgsCall( - long exprId, CelResolvedOverload resolvedOverload, PlannedInterpretable[] args) { + long exprId, + CelResolvedOverload resolvedOverload, + PlannedInterpretable[] args, + CelValueConverter celValueConverter) { super(exprId); this.resolvedOverload = resolvedOverload; this.args = args; + this.celValueConverter = celValueConverter; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java index 6bda7619d..1a4218860 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -14,6 +14,8 @@ package dev.cel.runtime.planner; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; @@ -22,18 +24,27 @@ final class EvalZeroArity extends PlannedInterpretable { private static final Object[] EMPTY_ARRAY = new Object[0]; private final CelResolvedOverload resolvedOverload; + private final CelValueConverter celValueConverter; @Override public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { - return resolvedOverload.getDefinition().apply(EMPTY_ARRAY); + Object result = resolvedOverload.getDefinition().apply(EMPTY_ARRAY); + Object runtimeValue = celValueConverter.toRuntimeValue(result); + if (runtimeValue instanceof CelValue) { + return celValueConverter.unwrap((CelValue) runtimeValue); + } + return runtimeValue; } - static EvalZeroArity create(long exprId, CelResolvedOverload resolvedOverload) { - return new EvalZeroArity(exprId, resolvedOverload); + static EvalZeroArity create( + long exprId, CelResolvedOverload resolvedOverload, CelValueConverter celValueConverter) { + return new EvalZeroArity(exprId, resolvedOverload, celValueConverter); } - private EvalZeroArity(long exprId, CelResolvedOverload resolvedOverload) { + private EvalZeroArity( + long exprId, CelResolvedOverload resolvedOverload, CelValueConverter celValueConverter) { super(exprId); this.resolvedOverload = resolvedOverload; + this.celValueConverter = celValueConverter; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java b/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java index a436d397a..80ee4b318 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ExecutionFrame.java @@ -16,19 +16,26 @@ import dev.cel.common.CelOptions; import dev.cel.common.exceptions.CelIterationLimitExceededException; -import dev.cel.runtime.GlobalResolver; -import org.jspecify.annotations.Nullable; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.CelResolvedOverload; +import java.util.Collection; +import java.util.Optional; /** Tracks execution context within a planned program. */ -final class ExecutionFrame implements GlobalResolver { +final class ExecutionFrame { - private final GlobalResolver delegate; private final int comprehensionIterationLimit; + private final CelFunctionResolver functionResolver; private int iterationCount; - @Override - public @Nullable Object resolve(String name) { - return delegate.resolve(name); + Optional findOverload( + String functionName, Collection overloadIds, Object[] args) + throws CelEvaluationException { + if (overloadIds.isEmpty()) { + return functionResolver.findOverloadMatchingArgs(functionName, args); + } + return functionResolver.findOverloadMatchingArgs(functionName, overloadIds, args); } void incrementIterations() { @@ -40,12 +47,12 @@ void incrementIterations() { } } - static ExecutionFrame create(GlobalResolver delegate, CelOptions celOptions) { - return new ExecutionFrame(delegate, celOptions.comprehensionMaxIterations()); + static ExecutionFrame create(CelFunctionResolver functionResolver, CelOptions celOptions) { + return new ExecutionFrame(functionResolver, celOptions.comprehensionMaxIterations()); } - private ExecutionFrame(GlobalResolver delegate, int limit) { - this.delegate = delegate; + private ExecutionFrame(CelFunctionResolver functionResolver, int limit) { this.comprehensionIterationLimit = limit; + this.functionResolver = functionResolver; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index bba1c394f..aaf26ecb0 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -23,13 +23,33 @@ import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelEvaluationExceptionBuilder; import dev.cel.runtime.CelFunctionResolver; +import dev.cel.runtime.CelResolvedOverload; +import dev.cel.runtime.CelVariableResolver; import dev.cel.runtime.GlobalResolver; import dev.cel.runtime.Program; +import java.util.Collection; import java.util.Map; +import java.util.Optional; @Immutable @AutoValue abstract class PlannedProgram implements Program { + + private static final CelFunctionResolver EMPTY_FUNCTION_RESOLVER = + new CelFunctionResolver() { + @Override + public Optional findOverloadMatchingArgs( + String functionName, Collection overloadIds, Object[] args) { + return Optional.empty(); + } + + @Override + public Optional findOverloadMatchingArgs( + String functionName, Object[] args) { + return Optional.empty(); + } + }; + abstract PlannedInterpretable interpretable(); abstract ErrorMetadata metadata(); @@ -38,24 +58,40 @@ abstract class PlannedProgram implements Program { @Override public Object eval() throws CelEvaluationException { - return evalOrThrow(interpretable(), GlobalResolver.EMPTY); + return evalOrThrow(interpretable(), GlobalResolver.EMPTY, EMPTY_FUNCTION_RESOLVER); } @Override public Object eval(Map mapValue) throws CelEvaluationException { - return evalOrThrow(interpretable(), Activation.copyOf(mapValue)); + return evalOrThrow(interpretable(), Activation.copyOf(mapValue), EMPTY_FUNCTION_RESOLVER); } @Override public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) throws CelEvaluationException { - throw new UnsupportedOperationException("Late bound functions not supported yet"); + return evalOrThrow(interpretable(), Activation.copyOf(mapValue), lateBoundFunctionResolver); + } + + @Override + public Object eval(CelVariableResolver resolver) throws CelEvaluationException { + return evalOrThrow( + interpretable(), (name) -> resolver.find(name).orElse(null), EMPTY_FUNCTION_RESOLVER); + } + + @Override + public Object eval(CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return evalOrThrow( + interpretable(), (name) -> resolver.find(name).orElse(null), lateBoundFunctionResolver); } - private Object evalOrThrow(PlannedInterpretable interpretable, GlobalResolver resolver) + private Object evalOrThrow( + PlannedInterpretable interpretable, + GlobalResolver resolver, + CelFunctionResolver functionResolver) throws CelEvaluationException { try { - ExecutionFrame frame = ExecutionFrame.create(resolver, options()); + ExecutionFrame frame = ExecutionFrame.create(functionResolver, options()); Object evalResult = interpretable.eval(resolver, frame); if (evalResult instanceof ErrorValue) { ErrorValue errorValue = (ErrorValue) evalResult; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 1559b8482..d8c29884e 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -17,6 +17,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CheckReturnValue; import javax.annotation.concurrent.ThreadSafe; import dev.cel.common.CelAbstractSyntaxTree; @@ -34,6 +35,7 @@ import dev.cel.common.ast.CelExpr.CelStruct; import dev.cel.common.ast.CelExpr.CelStruct.Entry; import dev.cel.common.ast.CelReference; +import dev.cel.common.exceptions.CelOverloadNotFoundException; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; @@ -63,7 +65,8 @@ public final class ProgramPlanner { private final AttributeFactory attributeFactory; private final CelContainer container; private final CelOptions options; - + private final CelValueConverter celValueConverter; + private final ImmutableSet lateBoundFunctionNames; /** * Plans a {@link Program} from the provided parsed-only or type-checked {@link @@ -71,14 +74,17 @@ public final class ProgramPlanner { */ public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException { PlannedInterpretable plannedInterpretable; + ErrorMetadata errorMetadata = + ErrorMetadata.create(ast.getSource().getPositionsMap(), ast.getSource().getDescription()); try { plannedInterpretable = plan(ast.getExpr(), PlannerContext.create(ast)); } catch (RuntimeException e) { - throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e).build(); + throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage()) + .setMetadata(errorMetadata, ast.getExpr().id()) + .setCause(e) + .build(); } - ErrorMetadata errorMetadata = - ErrorMetadata.create(ast.getSource().getPositionsMap(), ast.getSource().getDescription()); return PlannedProgram.create(plannedInterpretable, errorMetadata, options); } @@ -224,19 +230,36 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) { if (resolvedOverload == null) { // Parsed-only function dispatch - resolvedOverload = - dispatcher - .findOverload(functionName) - .orElseThrow(() -> new NoSuchElementException("Overload not found: " + functionName)); + resolvedOverload = dispatcher.findOverload(functionName).orElse(null); + } + + if (resolvedOverload == null) { + if (!lateBoundFunctionNames.contains(functionName)) { + CelReference reference = ctx.referenceMap().get(expr.id()); + if (reference != null) { + throw new CelOverloadNotFoundException(functionName, reference.overloadIds()); + } else { + throw new CelOverloadNotFoundException(functionName); + } + } + + ImmutableList overloadIds = ImmutableList.of(); + if (resolvedFunction.overloadId().isPresent()) { + overloadIds = ImmutableList.of(resolvedFunction.overloadId().get()); + } + + return EvalLateBoundCall.create( + expr.id(), functionName, overloadIds, evaluatedArgs, celValueConverter); } switch (argCount) { case 0: - return EvalZeroArity.create(expr.id(), resolvedOverload); + return EvalZeroArity.create(expr.id(), resolvedOverload, celValueConverter); case 1: - return EvalUnary.create(expr.id(), resolvedOverload, evaluatedArgs[0]); + return EvalUnary.create(expr.id(), resolvedOverload, evaluatedArgs[0], celValueConverter); default: - return EvalVarArgsCall.create(expr.id(), resolvedOverload, evaluatedArgs); + return EvalVarArgsCall.create( + expr.id(), resolvedOverload, evaluatedArgs, celValueConverter); } } @@ -455,9 +478,16 @@ public static ProgramPlanner newPlanner( DefaultDispatcher dispatcher, CelValueConverter celValueConverter, CelContainer container, - CelOptions options) { + CelOptions options, + ImmutableSet lateBoundFunctionNames) { return new ProgramPlanner( - typeProvider, valueProvider, dispatcher, celValueConverter, container, options); + typeProvider, + valueProvider, + dispatcher, + celValueConverter, + container, + options, + lateBoundFunctionNames); } private ProgramPlanner( @@ -466,12 +496,15 @@ private ProgramPlanner( DefaultDispatcher dispatcher, CelValueConverter celValueConverter, CelContainer container, - CelOptions options) { + CelOptions options, + ImmutableSet lateBoundFunctionNames) { this.typeProvider = typeProvider; this.valueProvider = valueProvider; this.dispatcher = dispatcher; + this.celValueConverter = celValueConverter; this.container = container; this.options = options; + this.lateBoundFunctionNames = lateBoundFunctionNames; this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider, celValueConverter); } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index ba0c8a548..44fb16417 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -67,6 +67,7 @@ import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelLateFunctionBindings; import dev.cel.runtime.CelStandardFunctions; import dev.cel.runtime.CelStandardFunctions.StandardFunction; import dev.cel.runtime.DefaultDispatcher; @@ -109,11 +110,17 @@ public final class ProgramPlannerTest { newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER, - CEL_OPTIONS); + CEL_OPTIONS, + ImmutableSet.of("late_bound_func")); private static final CelCompiler CEL_COMPILER = CelCompilerFactory.standardCelCompilerBuilder() .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addFunctionDeclarations( + newFunctionDeclaration( + "late_bound_func", + newGlobalOverload( + "late_bound_func_overload", SimpleType.STRING, SimpleType.STRING))) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.DYN)) .addVar("int_var", SimpleType.INT) @@ -440,12 +447,21 @@ public void plan_call_mapIndex() throws Exception { public void plan_call_noMatchingOverload_throws() throws Exception { CelAbstractSyntaxTree ast = compile("concat(b'abc', dyn_var)"); Program program = PLANNER.plan(ast); + String errorMsg; + if (isParseOnly) { + errorMsg = + "No matching overload for function 'concat'. Overload candidates: concat_bytes_bytes," + + " bytes_concat_bytes"; + } else { + errorMsg = "No matching overload for function 'concat_bytes_bytes'"; + } CelEvaluationException e = assertThrows( CelEvaluationException.class, () -> program.eval(ImmutableMap.of("dyn_var", "Impossible Overload"))); - assertThat(e).hasMessageThat().contains("No matching overload for function: concat"); + + assertThat(e).hasMessageThat().contains(errorMsg); } @Test @@ -557,6 +573,23 @@ public void plan_call_withContainer(String expression) throws Exception { assertThat(result).isEqualTo(8); } + @Test + public void plan_call_lateBoundFunction() throws Exception { + CelAbstractSyntaxTree ast = compile("late_bound_func('test')"); + + Program program = PLANNER.plan(ast); + + String result = + (String) + program.eval( + ImmutableMap.of(), + CelLateFunctionBindings.from( + CelFunctionBinding.from( + "late_bound_func_overload", String.class, (arg) -> arg + "_resolved"))); + + assertThat(result).isEqualTo("test_resolved"); + } + @Test public void plan_select_protoMessageField() throws Exception { CelAbstractSyntaxTree ast = compile("msg.single_string"); @@ -758,7 +791,8 @@ public void plan_comprehension_iterationLimit_throws(String expression) throws E newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER, - options); + options, + ImmutableSet.of()); CelAbstractSyntaxTree ast = compile(expression); Program program = planner.plan(ast); @@ -778,7 +812,8 @@ public void plan_comprehension_iterationLimit_success() throws Exception { newDispatcher(), CEL_VALUE_CONVERTER, CEL_CONTAINER, - options); + options, + ImmutableSet.of()); CelAbstractSyntaxTree ast = compile("[1, 2, 3].map(x, [1, 2].map(y, x + y))"); Program program = planner.plan(ast); From efba831593f3b01d6ca14823fbc4baf202a335fe Mon Sep 17 00:00:00 2001 From: CEL Dev Team Date: Fri, 9 Jan 2026 10:54:56 -0800 Subject: [PATCH 056/100] Fix context propagation in AsyncProgramImpl to preserve resolved attributes. PiperOrigin-RevId: 854258691 --- .../cel/runtime/async/AsyncProgramImpl.java | 2 +- .../async/CelAsyncRuntimeImplTest.java | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/runtime/src/main/java/dev/cel/runtime/async/AsyncProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/async/AsyncProgramImpl.java index 245dba691..b53c6254b 100644 --- a/runtime/src/main/java/dev/cel/runtime/async/AsyncProgramImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/async/AsyncProgramImpl.java @@ -160,7 +160,7 @@ private ListenableFuture evalPass( new CelEvaluationException("Max Evaluation iterations exceeded: " + iteration)); } return resolveAndReevaluate( - (CelUnknownSet) result, startingUnknownContext, resolvableAttributePatterns, iteration); + (CelUnknownSet) result, ctx, resolvableAttributePatterns, iteration); } return immediateFuture(result); diff --git a/runtime/src/test/java/dev/cel/runtime/async/CelAsyncRuntimeImplTest.java b/runtime/src/test/java/dev/cel/runtime/async/CelAsyncRuntimeImplTest.java index ab2ba2004..d24e99860 100644 --- a/runtime/src/test/java/dev/cel/runtime/async/CelAsyncRuntimeImplTest.java +++ b/runtime/src/test/java/dev/cel/runtime/async/CelAsyncRuntimeImplTest.java @@ -103,6 +103,56 @@ public void asyncProgram_basicUnknownResolution() throws Exception { assertThat(result).isEqualTo(true); } + @Test + public void asyncProgram_sequentialUnknownResolution() throws Exception { + // Arrange + CelUnknownAttributeValueResolver resolveName = + CelUnknownAttributeValueResolver.fromResolver( + (attr) -> { + Thread.sleep(500); + return attr.toString(); + }); + Cel cel = + CelFactory.standardCelBuilder() + .setOptions(CelOptions.current().enableUnknownTracking(true).build()) + .addMessageTypes(TestAllTypes.getDescriptor()) + .addVar("com.google.var1", SimpleType.BOOL) + .addVar("com.google.var2", SimpleType.STRING) + .addVar("com.google.var3", SimpleType.STRING) + .setResultType(SimpleType.STRING) + .setContainer(CelContainer.ofName("com.google")) + .build(); + + CelAsyncRuntime asyncRuntime = + CelAsyncRuntimeFactory.defaultAsyncRuntime() + .setRuntime(cel) + .setExecutorService(newDirectExecutorService()) + .build(); + + CelAbstractSyntaxTree ast = + cel.compile( + "var1 ? var2 : var3") + .getAst(); + + AsyncProgram program = asyncRuntime.createProgram(ast); + + // Act + ListenableFuture future = + program.evaluateToCompletion( + CelResolvableAttributePattern.of( + CelAttributePattern.fromQualifiedIdentifier("com.google.var1"), + CelUnknownAttributeValueResolver.fromResolver(unused -> true)), + CelResolvableAttributePattern.of( + CelAttributePattern.fromQualifiedIdentifier("com.google.var2"), resolveName), + CelResolvableAttributePattern.of( + CelAttributePattern.fromQualifiedIdentifier("com.google.var3"), resolveName)); + Object result = future.get(2, SECONDS); + + // Assert + assertThat(result).isInstanceOf(String.class); + assertThat(result).isEqualTo("com.google.var2"); + } + @Test public void asyncProgram_basicAsyncResolver() throws Exception { // Arrange From a1508fd484c3990f90896a5e9559457ce7c6d2e8 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 9 Jan 2026 21:02:58 -0800 Subject: [PATCH 057/100] Improve the error message in planned CreateMap when duplicate key exists PiperOrigin-RevId: 854452438 --- common/exceptions/BUILD.bazel | 6 ++ .../dev/cel/common/exceptions/BUILD.bazel | 13 ++++ .../exceptions/CelDuplicateKeyException.java | 31 +++++++++ .../java/dev/cel/runtime/planner/BUILD.bazel | 12 ++-- .../dev/cel/runtime/planner/EvalConstant.java | 69 ++----------------- .../cel/runtime/planner/EvalCreateMap.java | 17 ++++- .../dev/cel/runtime/planner/EvalHelpers.java | 10 +-- ...java => LocalizedEvaluationException.java} | 18 +++-- .../cel/runtime/planner/PlannedProgram.java | 9 ++- .../cel/runtime/planner/ProgramPlanner.java | 22 +++--- .../runtime/planner/ProgramPlannerTest.java | 11 +++ 11 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 common/src/main/java/dev/cel/common/exceptions/CelDuplicateKeyException.java rename runtime/src/main/java/dev/cel/runtime/planner/{StrictErrorException.java => LocalizedEvaluationException.java} (55%) diff --git a/common/exceptions/BUILD.bazel b/common/exceptions/BUILD.bazel index 4e52113e7..3464632d9 100644 --- a/common/exceptions/BUILD.bazel +++ b/common/exceptions/BUILD.bazel @@ -53,6 +53,12 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/exceptions:iteration_budget_exceeded"], ) +java_library( + name = "duplicate_key", + # used_by_android + exports = ["//common/src/main/java/dev/cel/common/exceptions:duplicate_key"], +) + java_library( name = "overload_not_found", # used_by_android diff --git a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel index 755e037d9..672238cf0 100644 --- a/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/exceptions/BUILD.bazel @@ -111,6 +111,19 @@ java_library( ], ) +java_library( + name = "duplicate_key", + srcs = ["CelDuplicateKeyException.java"], + # used_by_android + tags = [ + ], + deps = [ + ":runtime_exception", + "//common:error_codes", + "//common/annotations", + ], +) + java_library( name = "overload_not_found", srcs = ["CelOverloadNotFoundException.java"], diff --git a/common/src/main/java/dev/cel/common/exceptions/CelDuplicateKeyException.java b/common/src/main/java/dev/cel/common/exceptions/CelDuplicateKeyException.java new file mode 100644 index 000000000..f4c99d516 --- /dev/null +++ b/common/src/main/java/dev/cel/common/exceptions/CelDuplicateKeyException.java @@ -0,0 +1,31 @@ +// 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.common.exceptions; + +import dev.cel.common.CelErrorCode; +import dev.cel.common.annotations.Internal; + +/** Indicates an attempt to create a map using duplicate keys. */ +@Internal +public final class CelDuplicateKeyException extends CelRuntimeException { + + public static CelDuplicateKeyException of(Object key) { + return new CelDuplicateKeyException(String.format("duplicate map key [%s]", key)); + } + + private CelDuplicateKeyException(String message) { + super(message, CelErrorCode.DUPLICATE_ATTRIBUTE); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 92733b1b8..d15d9af92 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -63,8 +63,8 @@ java_library( deps = [ ":error_metadata", ":execution_frame", + ":localized_evaluation_exception", ":planned_interpretable", - ":strict_error_exception", "//:auto_value", "//common:options", "//common/exceptions:runtime_exception", @@ -87,8 +87,6 @@ java_library( deps = [ ":execution_frame", ":planned_interpretable", - "//common/values", - "//common/values:cel_byte_string", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -321,7 +319,9 @@ java_library( srcs = ["EvalCreateMap.java"], deps = [ ":execution_frame", + ":localized_evaluation_exception", ":planned_interpretable", + "//common/exceptions:duplicate_key", "//runtime:evaluation_exception", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", @@ -361,8 +361,8 @@ java_library( srcs = ["EvalHelpers.java"], deps = [ ":execution_frame", + ":localized_evaluation_exception", ":planned_interpretable", - ":strict_error_exception", "//common:error_codes", "//common/exceptions:runtime_exception", "//common/values", @@ -371,8 +371,8 @@ java_library( ) java_library( - name = "strict_error_exception", - srcs = ["StrictErrorException.java"], + name = "localized_evaluation_exception", + srcs = ["LocalizedEvaluationException.java"], deps = [ "//common:error_codes", "//common/exceptions:runtime_exception", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java index 74d2811ea..2bebb059b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConstant.java @@ -14,26 +14,12 @@ package dev.cel.runtime.planner; -import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.values.CelByteString; -import dev.cel.common.values.NullValue; import dev.cel.runtime.GlobalResolver; @Immutable final class EvalConstant extends PlannedInterpretable { - // Pre-allocation of common constants - private static final EvalConstant NULL_VALUE = new EvalConstant(NullValue.NULL_VALUE); - private static final EvalConstant TRUE = new EvalConstant(true); - private static final EvalConstant FALSE = new EvalConstant(false); - private static final EvalConstant ZERO = new EvalConstant(0L); - private static final EvalConstant ONE = new EvalConstant(1L); - private static final EvalConstant UNSIGNED_ZERO = new EvalConstant(UnsignedLong.ZERO); - private static final EvalConstant UNSIGNED_ONE = new EvalConstant(UnsignedLong.ONE); - private static final EvalConstant EMPTY_STRING = new EvalConstant(""); - private static final EvalConstant EMPTY_BYTES = new EvalConstant(CelByteString.EMPTY); - @SuppressWarnings("Immutable") // Known CEL constants that aren't mutated are stored private final Object constant; @@ -42,59 +28,12 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) { return constant; } - static EvalConstant create(boolean value) { - return value ? TRUE : FALSE; - } - - static EvalConstant create(String value) { - if (value.isEmpty()) { - return EMPTY_STRING; - } - - return new EvalConstant(value); - } - - static EvalConstant create(long value) { - if (value == 0L) { - return ZERO; - } else if (value == 1L) { - return ONE; - } - - return new EvalConstant(Long.valueOf(value)); - } - - static EvalConstant create(double value) { - return new EvalConstant(Double.valueOf(value)); - } - - static EvalConstant create(UnsignedLong unsignedLong) { - if (unsignedLong.longValue() == 0L) { - return UNSIGNED_ZERO; - } else if (unsignedLong.longValue() == 1L) { - return UNSIGNED_ONE; - } - - return new EvalConstant(unsignedLong); - } - - static EvalConstant create(NullValue unused) { - return NULL_VALUE; - } - - static EvalConstant create(CelByteString byteString) { - if (byteString.isEmpty()) { - return EMPTY_BYTES; - } - return new EvalConstant(byteString); - } - - static EvalConstant create(Object value) { - return new EvalConstant(value); + static EvalConstant create(long exprId, Object value) { + return new EvalConstant(exprId, value); } - private EvalConstant(Object constant) { - super(/* exprId= */ -1); // It's not possible to throw while evaluating a constant + private EvalConstant(long exprId, Object constant) { + super(exprId); this.constant = constant; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java index abdba90db..4c5a1f0bf 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -16,9 +16,12 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.exceptions.CelDuplicateKeyException; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.GlobalResolver; +import java.util.HashSet; @Immutable final class EvalCreateMap extends PlannedInterpretable { @@ -35,13 +38,23 @@ final class EvalCreateMap extends PlannedInterpretable { public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(keys.length); + HashSet keysSeen = Sets.newHashSetWithExpectedSize(keys.length); + for (int i = 0; i < keys.length; i++) { - builder.put(keys[i].eval(resolver, frame), values[i].eval(resolver, frame)); + Object key = keys[i].eval(resolver, frame); + + if (!keysSeen.add(key)) { + throw new LocalizedEvaluationException(CelDuplicateKeyException.of(key), keys[i].exprId()); + } + + builder.put(key, values[i].eval(resolver, frame)); } + return builder.buildOrThrow(); } - static EvalCreateMap create(long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) { + static EvalCreateMap create( + long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) { return new EvalCreateMap(exprId, keys, values); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index de034d035..6e7fd7e68 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -25,8 +25,8 @@ static Object evalNonstrictly( PlannedInterpretable interpretable, GlobalResolver resolver, ExecutionFrame frame) { try { return interpretable.eval(resolver, frame); - } catch (StrictErrorException e) { - // Intercept the strict exception to get a more localized expr ID for error reporting purposes + } catch (LocalizedEvaluationException e) { + // Intercept the localized exception to get a more specific expr ID for error reporting // Example: foo [1] && strict_err [2] -> ID 2 is propagated. return ErrorValue.create(e.exprId(), e); } catch (Exception e) { @@ -39,9 +39,11 @@ static Object evalStrictly( try { return interpretable.eval(resolver, frame); } catch (CelRuntimeException e) { - throw new StrictErrorException(e, interpretable.exprId()); + // Wrap with current interpretable's location + throw new LocalizedEvaluationException(e, interpretable.exprId()); } catch (Exception e) { - throw new StrictErrorException( + // Wrap generic exceptions with location + throw new LocalizedEvaluationException( e.getCause(), CelErrorCode.INTERNAL_ERROR, interpretable.exprId()); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java b/runtime/src/main/java/dev/cel/runtime/planner/LocalizedEvaluationException.java similarity index 55% rename from runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java rename to runtime/src/main/java/dev/cel/runtime/planner/LocalizedEvaluationException.java index 441a1f27c..603f0b0a5 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/StrictErrorException.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/LocalizedEvaluationException.java @@ -1,4 +1,4 @@ -// Copyright 2025 Google LLC +// 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. @@ -18,12 +18,16 @@ import dev.cel.common.exceptions.CelRuntimeException; /** - * An exception that's raised when a strict call failed to invoke, which includes the source of - * expression ID, along with canonical CelErrorCode. + * Wraps a {@link CelRuntimeException} with its source expression ID for error reporting. * - *

    Note that StrictErrorException should not be surfaced directly back to the user. + *

    This is the ONLY exception type that propagates through evaluation in the planner. All + * CelRuntimeExceptions from runtime helpers are immediately wrapped with location information to + * track where the error occurred in the expression tree. + * + *

    Note: This exception should not be surfaced directly to users - it's unwrapped in {@link + * PlannedProgram}. */ -final class StrictErrorException extends CelRuntimeException { +final class LocalizedEvaluationException extends CelRuntimeException { private final long exprId; @@ -31,11 +35,11 @@ long exprId() { return exprId; } - StrictErrorException(CelRuntimeException cause, long exprId) { + LocalizedEvaluationException(CelRuntimeException cause, long exprId) { this(cause, cause.getErrorCode(), exprId); } - StrictErrorException(Throwable cause, CelErrorCode errorCode, long exprId) { + LocalizedEvaluationException(Throwable cause, CelErrorCode errorCode, long exprId) { super(cause, errorCode); this.exprId = exprId; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index aaf26ecb0..22aff43c8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -106,9 +106,12 @@ private Object evalOrThrow( private CelEvaluationException newCelEvaluationException(long exprId, Exception e) { CelEvaluationExceptionBuilder builder; - if (e instanceof StrictErrorException) { - // Preserve detailed error, including error codes if one exists. - builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e.getCause()); + if (e instanceof LocalizedEvaluationException) { + // Use the localized expr ID (most specific error location) + LocalizedEvaluationException localized = (LocalizedEvaluationException) e; + exprId = localized.exprId(); + builder = + CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) localized.getCause()); } else if (e instanceof CelRuntimeException) { builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e); } else { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index d8c29884e..1c2bae9c2 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -91,7 +91,7 @@ public Program plan(CelAbstractSyntaxTree ast) throws CelEvaluationException { private PlannedInterpretable plan(CelExpr celExpr, PlannerContext ctx) { switch (celExpr.getKind()) { case CONSTANT: - return planConstant(celExpr.constant()); + return planConstant(celExpr.id(), celExpr.constant()); case IDENT: return planIdent(celExpr, ctx); case SELECT: @@ -134,22 +134,22 @@ private PlannedInterpretable planSelect(CelExpr celExpr, PlannerContext ctx) { return attribute.addQualifier(celExpr.id(), qualifier); } - private PlannedInterpretable planConstant(CelConstant celConstant) { + private PlannedInterpretable planConstant(long exprId, CelConstant celConstant) { switch (celConstant.getKind()) { case NULL_VALUE: - return EvalConstant.create(celConstant.nullValue()); + return EvalConstant.create(exprId, celConstant.nullValue()); case BOOLEAN_VALUE: - return EvalConstant.create(celConstant.booleanValue()); + return EvalConstant.create(exprId, celConstant.booleanValue()); case INT64_VALUE: - return EvalConstant.create(celConstant.int64Value()); + return EvalConstant.create(exprId, celConstant.int64Value()); case UINT64_VALUE: - return EvalConstant.create(celConstant.uint64Value()); + return EvalConstant.create(exprId, celConstant.uint64Value()); case DOUBLE_VALUE: - return EvalConstant.create(celConstant.doubleValue()); + return EvalConstant.create(exprId, celConstant.doubleValue()); case STRING_VALUE: - return EvalConstant.create(celConstant.stringValue()); + return EvalConstant.create(exprId, celConstant.stringValue()); case BYTES_VALUE: - return EvalConstant.create(celConstant.bytesValue()); + return EvalConstant.create(exprId, celConstant.bytesValue()); default: throw new IllegalStateException("Unsupported kind: " + celConstant.getKind()); } @@ -168,7 +168,7 @@ private PlannedInterpretable planIdent(CelExpr celExpr, PlannerContext ctx) { private PlannedInterpretable planCheckedIdent( long id, CelReference identRef, ImmutableMap typeMap) { if (identRef.value().isPresent()) { - return planConstant(identRef.value().get()); + return planConstant(id, identRef.value().get()); } CelType type = typeMap.get(id); @@ -181,7 +181,7 @@ private PlannedInterpretable planCheckedIdent( () -> new NoSuchElementException( "Reference to an undefined type: " + identRef.name())); - return EvalConstant.create(identType); + return EvalConstant.create(id, identType); } return EvalAttribute.create(id, attributeFactory.newAbsoluteAttribute(identRef.name())); diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 44fb16417..7b9e1920b 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -326,6 +326,17 @@ public void plan_createMap() throws Exception { assertThat(result).containsExactly("foo", 1L, true, "bar").inOrder(); } + @Test + public void plan_createMap_containsDuplicateKey_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("{true: 1, false: 2, true: 3}"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :20: duplicate map key [true]"); + } + @Test public void plan_createStruct() throws Exception { CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{}"); From 777d0890f35a773eca75821af15107fe9b818d2a Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Sat, 10 Jan 2026 11:38:28 -0800 Subject: [PATCH 058/100] Enable conformance tests for two-var comprehensions. Add 2-var existsOne, alternative to exists_one that is more consistent with standard naming conventions. PiperOrigin-RevId: 854632470 --- .../bundle/CelEnvironmentExporterTest.java | 2 +- .../main/java/dev/cel/common/Operator.java | 3 + .../test/java/dev/cel/conformance/BUILD.bazel | 3 + .../dev/cel/conformance/ConformanceTest.java | 55 +++++++++--------- .../CelComprehensionsExtensions.java | 4 ++ .../java/dev/cel/parser/CelStandardMacro.java | 8 +++ .../dev/cel/parser/CelParserImplTest.java | 10 +++- .../parser/CelParserParameterizedTest.java | 8 ++- .../dev/cel/parser/CelStandardMacroTest.java | 17 ++++++ parser/src/test/resources/parser.baseline | 57 +++++++++++++++++++ 10 files changed, 135 insertions(+), 32 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java index b9a81a662..d6608a9d4 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java @@ -110,7 +110,7 @@ public void standardLibrarySubset_favorExclusion() throws Exception { FunctionSelector.create("matches", ImmutableSet.of()), FunctionSelector.create( "timestamp", ImmutableSet.of("string_to_timestamp")))) - .setExcludedMacros(ImmutableSet.of("map", "filter")) + .setExcludedMacros(ImmutableSet.of("map", "existsOne", "filter")) .build()); } diff --git a/common/src/main/java/dev/cel/common/Operator.java b/common/src/main/java/dev/cel/common/Operator.java index 0c059d989..c49c78267 100644 --- a/common/src/main/java/dev/cel/common/Operator.java +++ b/common/src/main/java/dev/cel/common/Operator.java @@ -45,7 +45,9 @@ public enum Operator { HAS("has"), ALL("all"), EXISTS("exists"), + @Deprecated // Prefer EXISTS_ONE_NEW. EXISTS_ONE("exists_one"), + EXISTS_ONE_NEW("existsOne"), MAP("map"), FILTER("filter"), NOT_STRICTLY_FALSE("@not_strictly_false"), @@ -109,6 +111,7 @@ public static Optional find(String text) { .put(EQUALS.getFunction(), EQUALS) .put(EXISTS.getFunction(), EXISTS) .put(EXISTS_ONE.getFunction(), EXISTS_ONE) + .put(EXISTS_ONE_NEW.getFunction(), EXISTS_ONE_NEW) .put(FILTER.getFunction(), FILTER) .put(GREATER.getFunction(), GREATER) .put(GREATER_EQUALS.getFunction(), GREATER_EQUALS) diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index 7283e2977..248bc7fe2 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -22,6 +22,7 @@ java_library( "//common:options", "//common/types:cel_proto_types", "//compiler", + "//compiler:compiler_builder", "//extensions", "//extensions:optional_library", "//parser:macro", @@ -49,6 +50,7 @@ java_library( tags = ["conformance_maven"], deps = MAVEN_JAR_DEPS + [ "//:java_truth", + "//compiler:compiler_builder", "//testing:expr_value_utils", "@cel_spec//proto/cel/expr:expr_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", @@ -75,6 +77,7 @@ _ALL_TESTS = [ "@cel_spec//tests/simple:testdata/lists.textproto", "@cel_spec//tests/simple:testdata/logic.textproto", "@cel_spec//tests/simple:testdata/macros.textproto", + "@cel_spec//tests/simple:testdata/macros2.textproto", "@cel_spec//tests/simple:testdata/math_ext.textproto", "@cel_spec//tests/simple:testdata/namespace.textproto", "@cel_spec//tests/simple:testdata/optionals.textproto", diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java index bc5ecfd12..dcd226fa5 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java @@ -35,6 +35,7 @@ import dev.cel.common.CelValidationResult; import dev.cel.common.types.CelProtoTypes; import dev.cel.compiler.CelCompilerFactory; +import dev.cel.compiler.CelCompilerLibrary; import dev.cel.expr.conformance.test.SimpleTest; import dev.cel.extensions.CelExtensions; import dev.cel.extensions.CelOptionalLibrary; @@ -45,6 +46,7 @@ import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntime.Program; import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.runtime.CelRuntimeLibrary; import java.util.Map; import org.junit.runners.model.Statement; @@ -61,31 +63,37 @@ public final class ConformanceTest extends Statement { .enableQuotedIdentifierSyntax(true) .build(); + private static final ImmutableList CANONICAL_COMPILER_EXTENSIONS = + ImmutableList.of( + CelExtensions.bindings(), + CelExtensions.comprehensions(), + CelExtensions.encoders(OPTIONS), + CelExtensions.math(OPTIONS), + CelExtensions.protos(), + CelExtensions.sets(OPTIONS), + CelExtensions.strings(), + CelOptionalLibrary.INSTANCE); + + private static final ImmutableList CANONICAL_RUNTIME_EXTENSIONS = + ImmutableList.of( + CelExtensions.comprehensions(), + CelExtensions.encoders(OPTIONS), + CelExtensions.math(OPTIONS), + CelExtensions.sets(OPTIONS), + CelExtensions.strings(), + CelOptionalLibrary.INSTANCE); + private static final CelParser PARSER_WITH_MACROS = CelParserFactory.standardCelParserBuilder() .setOptions(OPTIONS) - .addLibraries( - CelExtensions.bindings(), - CelExtensions.encoders(OPTIONS), - CelExtensions.math(OPTIONS), - CelExtensions.protos(), - CelExtensions.sets(OPTIONS), - CelExtensions.strings(), - CelOptionalLibrary.INSTANCE) + .addLibraries(CANONICAL_COMPILER_EXTENSIONS) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .build(); private static final CelParser PARSER_WITHOUT_MACROS = CelParserFactory.standardCelParserBuilder() .setOptions(OPTIONS) - .addLibraries( - CelExtensions.bindings(), - CelExtensions.encoders(OPTIONS), - CelExtensions.math(OPTIONS), - CelExtensions.protos(), - CelExtensions.sets(OPTIONS), - CelExtensions.strings(), - CelOptionalLibrary.INSTANCE) + .addLibraries(CANONICAL_COMPILER_EXTENSIONS) .setStandardMacros() .build(); @@ -104,13 +112,7 @@ private static CelChecker getChecker(SimpleTest test) throws Exception { .setContainer(CelContainer.ofName(test.getContainer())) .addDeclarations(decls.build()) .addFileTypes(dev.cel.expr.conformance.proto2.TestAllTypesExtensions.getDescriptor()) - .addLibraries( - CelExtensions.bindings(), - CelExtensions.encoders(OPTIONS), - CelExtensions.math(OPTIONS), - CelExtensions.sets(OPTIONS), - CelExtensions.strings(), - CelOptionalLibrary.INSTANCE) + .addLibraries(CANONICAL_COMPILER_EXTENSIONS) .addMessageTypes(dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor()) .addMessageTypes(dev.cel.expr.conformance.proto3.TestAllTypes.getDescriptor()) .build(); @@ -119,12 +121,7 @@ private static CelChecker getChecker(SimpleTest test) throws Exception { private static final CelRuntime RUNTIME = CelRuntimeFactory.standardCelRuntimeBuilder() .setOptions(OPTIONS) - .addLibraries( - CelExtensions.encoders(OPTIONS), - CelExtensions.math(OPTIONS), - CelExtensions.sets(OPTIONS), - CelExtensions.strings(), - CelOptionalLibrary.INSTANCE) + .addLibraries(CANONICAL_RUNTIME_EXTENSIONS) .setExtensionRegistry(DEFAULT_EXTENSION_REGISTRY) .addMessageTypes(dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor()) .addMessageTypes(dev.cel.expr.conformance.proto3.TestAllTypes.getDescriptor()) diff --git a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java index ddea801b6..462e1d008 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java @@ -159,6 +159,10 @@ public ImmutableSet macros() { Operator.EXISTS_ONE.getFunction(), 3, CelComprehensionsExtensions::expandExistsOneMacro), + CelMacro.newReceiverMacro( + Operator.EXISTS_ONE_NEW.getFunction(), + 3, + CelComprehensionsExtensions::expandExistsOneMacro), CelMacro.newReceiverMacro( "transformList", 3, CelComprehensionsExtensions::transformListMacro), CelMacro.newReceiverMacro( diff --git a/parser/src/main/java/dev/cel/parser/CelStandardMacro.java b/parser/src/main/java/dev/cel/parser/CelStandardMacro.java index a90acce72..20d30bc17 100644 --- a/parser/src/main/java/dev/cel/parser/CelStandardMacro.java +++ b/parser/src/main/java/dev/cel/parser/CelStandardMacro.java @@ -54,6 +54,14 @@ public enum CelStandardMacro { CelMacro.newReceiverMacro( Operator.EXISTS_ONE.getFunction(), 2, CelStandardMacro::expandExistsOneMacro)), + /** + * Boolean comprehension which asserts that a predicate holds true for exactly one element in the + * input range. + */ + EXISTS_ONE_NEW( + CelMacro.newReceiverMacro( + Operator.EXISTS_ONE_NEW.getFunction(), 2, CelStandardMacro::expandExistsOneMacro)), + /** * Comprehension which applies a transform to each element in the input range and produces a list * of equivalent size as output. diff --git a/parser/src/test/java/dev/cel/parser/CelParserImplTest.java b/parser/src/test/java/dev/cel/parser/CelParserImplTest.java index f12fef292..1e7b44fab 100644 --- a/parser/src/test/java/dev/cel/parser/CelParserImplTest.java +++ b/parser/src/test/java/dev/cel/parser/CelParserImplTest.java @@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableSet; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; @@ -260,10 +261,17 @@ public void parse_exprUnderMaxRecursionLimit_doesNotThrow( @TestParameters("{expression: 'A.all(a?b, c)'}") @TestParameters("{expression: 'A.exists(a?b, c)'}") @TestParameters("{expression: 'A.exists_one(a?b, c)'}") + @TestParameters("{expression: 'A.existsOne(a?b, c)'}") @TestParameters("{expression: 'A.filter(a?b, c)'}") public void parse_macroArgumentContainsSyntaxError_throws(String expression) { CelParser parser = - CelParserImpl.newBuilder().setStandardMacros(CelStandardMacro.STANDARD_MACROS).build(); + CelParserImpl.newBuilder() + .setStandardMacros( + ImmutableSet.builder() + .addAll(CelStandardMacro.STANDARD_MACROS) + .add(CelStandardMacro.EXISTS_ONE_NEW) + .build()) + .build(); CelValidationResult parseResult = parser.parse(expression); diff --git a/parser/src/test/java/dev/cel/parser/CelParserParameterizedTest.java b/parser/src/test/java/dev/cel/parser/CelParserParameterizedTest.java index 0e4490aa6..58b45ddab 100644 --- a/parser/src/test/java/dev/cel/parser/CelParserParameterizedTest.java +++ b/parser/src/test/java/dev/cel/parser/CelParserParameterizedTest.java @@ -26,6 +26,7 @@ import com.google.auto.value.AutoValue; import com.google.common.base.Ascii; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.EnumDescriptor; @@ -57,7 +58,11 @@ public final class CelParserParameterizedTest extends BaselineTestCase { private static final CelParser PARSER = CelParserFactory.standardCelParserBuilder() - .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .setStandardMacros( + ImmutableSet.builder() + .addAll(CelStandardMacro.STANDARD_MACROS) + .add(CelStandardMacro.EXISTS_ONE_NEW) + .build()) .addLibraries(CelOptionalLibrary.INSTANCE) .addMacros( CelMacro.newGlobalVarArgMacro("noop_macro", (a, b, c) -> Optional.empty()), @@ -162,6 +167,7 @@ public void parser() { runTest(PARSER, "aaa.bbb(ccc)"); runTest(PARSER, "has(m.f)"); runTest(PARSER, "m.exists_one(v, f)"); + runTest(PARSER, "m.existsOne(v, f)"); runTest(PARSER, "m.map(v, f)"); runTest(PARSER, "m.map(v, p, f)"); runTest(PARSER, "m.filter(v, p)"); diff --git a/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java b/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java index 80538930a..7ab96180c 100644 --- a/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java +++ b/parser/src/test/java/dev/cel/parser/CelStandardMacroTest.java @@ -32,6 +32,8 @@ public void getFunction() { assertThat(CelStandardMacro.EXISTS.getFunction()).isEqualTo(Operator.EXISTS.getFunction()); assertThat(CelStandardMacro.EXISTS_ONE.getFunction()) .isEqualTo(Operator.EXISTS_ONE.getFunction()); + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getFunction()) + .isEqualTo(Operator.EXISTS_ONE_NEW.getFunction()); assertThat(CelStandardMacro.FILTER.getFunction()).isEqualTo(Operator.FILTER.getFunction()); assertThat(CelStandardMacro.MAP.getFunction()).isEqualTo(Operator.MAP.getFunction()); assertThat(CelStandardMacro.MAP_FILTER.getFunction()).isEqualTo(Operator.MAP.getFunction()); @@ -90,6 +92,21 @@ public void testExistsOne() { .isEqualTo(CelStandardMacro.EXISTS_ONE.getDefinition().getKey().hashCode()); } + @Test + public void testExistsOneNew() { + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getFunction()) + .isEqualTo(Operator.EXISTS_ONE_NEW.getFunction()); + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().getArgumentCount()).isEqualTo(2); + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().isReceiverStyle()).isTrue(); + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().getKey()) + .isEqualTo("existsOne:2:true"); + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().isVariadic()).isFalse(); + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().toString()) + .isEqualTo(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().getKey()); + assertThat(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().hashCode()) + .isEqualTo(CelStandardMacro.EXISTS_ONE_NEW.getDefinition().getKey().hashCode()); + } + @Test public void testMap2() { assertThat(CelStandardMacro.MAP.getFunction()).isEqualTo(Operator.MAP.getFunction()); diff --git a/parser/src/test/resources/parser.baseline b/parser/src/test/resources/parser.baseline index 9b509f61e..37b8ef3cc 100644 --- a/parser/src/test/resources/parser.baseline +++ b/parser/src/test/resources/parser.baseline @@ -784,6 +784,63 @@ M: m^#1:Expr.Ident#.exists_one( f^#4:Expr.Ident# )^#0:Expr.Call# +I: m.existsOne(v, f) +=====> +P: __comprehension__( + // Variable + v, + // Target + m^#1:Expr.Ident#, + // Accumulator + @result, + // Init + 0^#5:int64#, + // LoopCondition + true^#6:bool#, + // LoopStep + _?_:_( + f^#4:Expr.Ident#, + _+_( + @result^#7:Expr.Ident#, + 1^#8:int64# + )^#9:Expr.Call#, + @result^#10:Expr.Ident# + )^#11:Expr.Call#, + // Result + _==_( + @result^#12:Expr.Ident#, + 1^#13:int64# + )^#14:Expr.Call#)^#15:Expr.Comprehension# +L: __comprehension__( + // Variable + v, + // Target + m^#1[1,0]#, + // Accumulator + @result, + // Init + 0^#5[1,11]#, + // LoopCondition + true^#6[1,11]#, + // LoopStep + _?_:_( + f^#4[1,15]#, + _+_( + @result^#7[1,11]#, + 1^#8[1,11]# + )^#9[1,11]#, + @result^#10[1,11]# + )^#11[1,11]#, + // Result + _==_( + @result^#12[1,11]#, + 1^#13[1,11]# + )^#14[1,11]#)^#15[1,11]# +M: m^#1:Expr.Ident#.existsOne( + v^#3:Expr.Ident#, + f^#4:Expr.Ident# +)^#0:Expr.Call# + I: m.map(v, f) =====> P: __comprehension__( From e91cb909d31d000ff3bff67b50baa462edca4d45 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 15 Jan 2026 11:41:12 -0800 Subject: [PATCH 059/100] Fix type-checker and runtime to properly resolve global escaped identifiers PiperOrigin-RevId: 856748731 --- .gitignore | 2 + .../src/main/java/dev/cel/checker/Env.java | 32 +++++++++++---- .../java/dev/cel/checker/ExprChecker.java | 39 +++++++++++++++---- .../java/dev/cel/checker/ExprCheckerTest.java | 4 ++ .../resources/referenceTypeAbsolute.baseline | 33 ++++++++++++++++ .../cel/runtime/RuntimeUnknownResolver.java | 9 +++++ .../src/test/resources/comprehension.baseline | 34 +++++++++++++++- .../dev/cel/testing/BaseInterpreterTest.java | 20 ++++++++++ 8 files changed, 157 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index e8052913c..3a2e0015d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ mvn-artifacts *.swp *.lock +.eclipse +.vscode diff --git a/checker/src/main/java/dev/cel/checker/Env.java b/checker/src/main/java/dev/cel/checker/Env.java index 15079cff7..7029781e5 100644 --- a/checker/src/main/java/dev/cel/checker/Env.java +++ b/checker/src/main/java/dev/cel/checker/Env.java @@ -451,17 +451,29 @@ public Env add(String name, Type type) { * until the root package is reached. If {@code container} starts with {@code .}, the resolution * is in the root container only. * - *

    Returns {@code null} if the function cannot be found. + *

    Returns {@code null} if the ident cannot be found. */ public @Nullable CelIdentDecl tryLookupCelIdent(CelContainer container, String name) { - // Attempt to find the decl with just the ident name to account for shadowed variables - CelIdentDecl decl = tryLookupCelIdentFromLocalScopes(name); - if (decl != null) { - return decl; + // A name with a leading '.' always resolves in the root scope, bypassing local scopes. + if (!name.startsWith(".")) { + // Check if this is a qualified ident, or a field selection. + String simpleName = name; + int dotIndex = name.indexOf('.'); + if (dotIndex > 0) { + simpleName = name.substring(0, dotIndex); + } + + // Attempt to find the decl with just the ident name to account for shadowed variables. + CelIdentDecl decl = tryLookupCelIdentFromLocalScopes(simpleName); + if (decl != null) { + // Appears to be a field selection on a local. + // Return null instead of attempting to resolve qualified name at the root scope + return dotIndex > 0 ? null : decl; + } } for (String cand : container.resolveCandidateNames(name)) { - decl = tryLookupCelIdent(cand); + CelIdentDecl decl = tryLookupCelIdent(cand); if (decl != null) { return decl; } @@ -503,7 +515,13 @@ public Env add(String name, Type type) { return null; } - private @Nullable CelIdentDecl tryLookupCelIdentFromLocalScopes(String name) { + /** + * Lookup a local identifier by name. This searches only comprehension scopes, bypassing standard + * environment or user-defined environment. + * + *

    Returns {@code null} if not found in local scopes. + */ + @Nullable CelIdentDecl tryLookupCelIdentFromLocalScopes(String name) { int firstUserSpaceScope = 2; // Iterate from the top of the stack down to the first local scope. // Note that: diff --git a/checker/src/main/java/dev/cel/checker/ExprChecker.java b/checker/src/main/java/dev/cel/checker/ExprChecker.java index 9ed121a20..37b692ecf 100644 --- a/checker/src/main/java/dev/cel/checker/ExprChecker.java +++ b/checker/src/main/java/dev/cel/checker/ExprChecker.java @@ -237,15 +237,18 @@ private CelExpr visit(CelExpr expr, CelExpr.CelIdent ident) { if (decl.equals(Env.ERROR_IDENT_DECL)) { // error reported env.setType(expr, SimpleType.ERROR); - env.setRef(expr, makeReference(decl)); + env.setRef(expr, makeReference(decl.name(), decl)); return expr; } - if (!decl.name().equals(ident.name())) { + + String refName = maybeDisambiguate(ident.name(), decl.name()); + + if (!refName.equals(ident.name())) { // Overwrite the identifier with its fully qualified name. - expr = replaceIdentSubtree(expr, decl.name()); + expr = replaceIdentSubtree(expr, refName); } env.setType(expr, decl.type()); - env.setRef(expr, makeReference(decl)); + env.setRef(expr, makeReference(refName, decl)); return expr; } @@ -260,13 +263,15 @@ private CelExpr visit(CelExpr expr, CelExpr.CelSelect select) { env.reportError(expr.id(), getPosition(expr), "expression does not select a field"); env.setType(expr, SimpleType.BOOL); } else { + String refName = maybeDisambiguate(qname, decl.name()); + if (namespacedDeclarations) { // Rewrite the node to be a variable reference to the resolved fully-qualified // variable name. - expr = replaceIdentSubtree(expr, decl.name()); + expr = replaceIdentSubtree(expr, refName); } env.setType(expr, decl.type()); - env.setRef(expr, makeReference(decl)); + env.setRef(expr, makeReference(refName, decl)); } return expr; } @@ -595,14 +600,32 @@ private CelExpr visit(CelExpr expr, CelExpr.CelComprehension compre) { return expr; } - private CelReference makeReference(CelIdentDecl decl) { - CelReference.Builder ref = CelReference.newBuilder().setName(decl.name()); + private CelReference makeReference(String name, CelIdentDecl decl) { + CelReference.Builder ref = CelReference.newBuilder().setName(name); if (decl.constant().isPresent()) { ref.setValue(decl.constant().get()); } return ref.build(); } + /** + * Returns the reference name, prefixed with a leading dot only if disambiguation is needed. + * Disambiguation is needed when: the original name had a leading dot, and there's a local + * variable that would shadow the resolved name. + */ + private String maybeDisambiguate(String originalName, String resolvedName) { + if (!originalName.startsWith(".")) { + return resolvedName; + } + String simpleName = originalName.substring(1); + int dotIndex = simpleName.indexOf('.'); + String localName = dotIndex > 0 ? simpleName.substring(0, dotIndex) : simpleName; + if (env.tryLookupCelIdentFromLocalScopes(localName) != null) { + return "." + resolvedName; + } + return resolvedName; + } + private OverloadResolution resolveOverload( long callExprId, int position, diff --git a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java index c3b26785a..d5d5d9a3a 100644 --- a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java +++ b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java @@ -199,6 +199,10 @@ public void referenceTypeRelative() throws Exception { public void referenceTypeAbsolute() throws Exception { source = ".cel.expr.conformance.proto3.TestAllTypes"; runTest(); + + declareVariable("app.config", SimpleType.INT); + source = "[0].exists(app, .app.config == 1)"; + runTest(); } @Test diff --git a/checker/src/test/resources/referenceTypeAbsolute.baseline b/checker/src/test/resources/referenceTypeAbsolute.baseline index 7680854d4..401e6fe0c 100644 --- a/checker/src/test/resources/referenceTypeAbsolute.baseline +++ b/checker/src/test/resources/referenceTypeAbsolute.baseline @@ -1,3 +1,36 @@ Source: .cel.expr.conformance.proto3.TestAllTypes =====> cel.expr.conformance.proto3.TestAllTypes~type(cel.expr.conformance.proto3.TestAllTypes)^cel.expr.conformance.proto3.TestAllTypes + +Source: [0].exists(app, .app.config == 1) +declare app.config { + value int +} +=====> +__comprehension__( + // Variable + app, + // Target + [ + 0~int + ]~list(int), + // Accumulator + @result, + // Init + false~bool, + // LoopCondition + @not_strictly_false( + !_( + @result~bool^@result + )~bool^logical_not + )~bool^not_strictly_false, + // LoopStep + _||_( + @result~bool^@result, + _==_( + .app.config~int^.app.config, + 1~int + )~bool^equals + )~bool^logical_or, + // Result + @result~bool^@result)~bool diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java b/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java index b28729417..f14e75dd7 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeUnknownResolver.java @@ -98,6 +98,11 @@ Optional maybePartialUnknown(CelAttribute attribute) { /** Resolve a simple name to a value. */ DefaultInterpreter.IntermediateResult resolveSimpleName(String name, Long exprId) { + // Strip leading dot if present (for global disambiguation). + if (name.startsWith(".")) { + name = name.substring(1); + } + CelAttribute attr = CelAttribute.EMPTY; if (attributeTrackingEnabled) { @@ -154,6 +159,10 @@ private ScopedResolver( @Override DefaultInterpreter.IntermediateResult resolveSimpleName(String name, Long exprId) { + // A name with a leading '.' always resolves in the root scope + if (name.startsWith(".")) { + return parent.resolveSimpleName(name, exprId); + } DefaultInterpreter.IntermediateResult result = lazyEvalResultCache.get(name); if (result != null) { return copyIfMutable(result); diff --git a/runtime/src/test/resources/comprehension.baseline b/runtime/src/test/resources/comprehension.baseline index 82aea0ae6..aa0ecc3f9 100644 --- a/runtime/src/test/resources/comprehension.baseline +++ b/runtime/src/test/resources/comprehension.baseline @@ -19,4 +19,36 @@ declare com.x { } =====> bindings: {com.x=1} -result: true \ No newline at end of file +result: true + +Source: [{'z': 0}].exists(y, y.z == 0) +declare cel.example.y { + value int +} +=====> +bindings: {cel.example.y={z=1}} +result: true + +Source: [{'z': 0}].exists(y, y.z == 0 && .y.z == 1) +declare y.z { + value int +} +=====> +bindings: {y.z=1} +result: true + +Source: [0].exists(x, x == 0 && .x == 1) +declare x { + value int +} +=====> +bindings: {x=1} +result: true + +Source: [0].exists(x, [x+1].exists(x, x == .x)) +declare x { + value int +} +=====> +bindings: {x=1} +result: true diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 1ee13a70d..44fb84433 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -863,6 +863,26 @@ public void comprehension() throws Exception { container = CelContainer.ofName("com"); source = "[0].exists(x, x == 0)"; runTest(ImmutableMap.of("com.x", 1)); + + clearAllDeclarations(); + declareVariable("cel.example.y", SimpleType.INT); + container = CelContainer.ofName("cel.example"); + source = "[{'z': 0}].exists(y, y.z == 0)"; + runTest(ImmutableMap.of("cel.example.y", ImmutableMap.of("z", 1))); + + clearAllDeclarations(); + declareVariable("y.z", SimpleType.INT); + container = CelContainer.ofName("y"); + source = "[{'z': 0}].exists(y, y.z == 0 && .y.z == 1)"; + runTest(ImmutableMap.of("y.z", 1)); + + clearAllDeclarations(); + declareVariable("x", SimpleType.INT); + source = "[0].exists(x, x == 0 && .x == 1)"; + runTest(ImmutableMap.of("x", 1)); + + source = "[0].exists(x, [x+1].exists(x, x == .x))"; + runTest(ImmutableMap.of("x", 1)); } @Test From 63013400bfeb4ce34d2d6c85f3eae5b920968781 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Sat, 17 Jan 2026 23:46:56 -0800 Subject: [PATCH 060/100] Fix source location in error messages for maps and comprehensions PiperOrigin-RevId: 857746208 --- .../main/java/dev/cel/runtime/DefaultInterpreter.java | 9 +++++---- runtime/src/test/resources/maps.baseline | 4 ++-- runtime/src/test/resources/maxComprehension.baseline | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index ba5d05915..e49658190 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -864,7 +864,7 @@ private IntermediateResult evalMap(ExecutionFrame frame, CelMap mapExpr) throw CelEvaluationExceptionBuilder.newBuilder( "duplicate map key [%s]", keyResult.value()) .setErrorCode(CelErrorCode.DUPLICATE_ATTRIBUTE) - .setMetadata(metadata, entry.id()) + .setMetadata(metadata, entry.key().id()) .build(); } @@ -967,7 +967,7 @@ private IntermediateResult maybeAdaptViewToList(IntermediateResult accuValue) { @SuppressWarnings("unchecked") private IntermediateResult evalComprehension( - ExecutionFrame frame, CelExpr unusedExpr, CelComprehension compre) + ExecutionFrame frame, CelExpr compreExpr, CelComprehension compre) throws CelEvaluationException { String accuVar = compre.accuVar(); String iterVar = compre.iterVar(); @@ -1000,7 +1000,7 @@ private IntermediateResult evalComprehension( } int i = 0; for (Object elem : iterRange) { - frame.incrementIterations(); + frame.incrementIterations(metadata, compreExpr.id()); CelAttribute iterAttr = CelAttribute.EMPTY; if (iterRange instanceof List) { @@ -1183,13 +1183,14 @@ private Optional findOverload( return Optional.empty(); } - private void incrementIterations() throws CelEvaluationException { + private void incrementIterations(Metadata metadata, long exprId) throws CelEvaluationException { if (maxIterations < 0) { return; } if (++iterations > maxIterations) { throw CelEvaluationExceptionBuilder.newBuilder( String.format("Iteration budget exceeded: %d", maxIterations)) + .setMetadata(metadata, exprId) .setErrorCode(CelErrorCode.ITERATION_BUDGET_EXCEEDED) .build(); } diff --git a/runtime/src/test/resources/maps.baseline b/runtime/src/test/resources/maps.baseline index 1e79b815e..ec89b09c6 100644 --- a/runtime/src/test/resources/maps.baseline +++ b/runtime/src/test/resources/maps.baseline @@ -121,7 +121,7 @@ declare map { } =====> bindings: {} -error: evaluation error at test_location:24: duplicate map key [true] +error: evaluation error at test_location:20: duplicate map key [true] error_code: DUPLICATE_ATTRIBUTE Source: {b: 1, !b: 2, b: 3}[true] @@ -139,5 +139,5 @@ declare b { } =====> bindings: {b=true} -error: evaluation error at test_location:15: duplicate map key [true] +error: evaluation error at test_location:14: duplicate map key [true] error_code: DUPLICATE_ATTRIBUTE diff --git a/runtime/src/test/resources/maxComprehension.baseline b/runtime/src/test/resources/maxComprehension.baseline index fc0cc7555..cad955d0f 100644 --- a/runtime/src/test/resources/maxComprehension.baseline +++ b/runtime/src/test/resources/maxComprehension.baseline @@ -4,7 +4,7 @@ declare longlist { } =====> bindings: {longlist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]} -error: evaluation error: Iteration budget exceeded: 1000 +error: evaluation error at test_location:17: Iteration budget exceeded: 1000 error_code: ITERATION_BUDGET_EXCEEDED Source: longlist.filter(i, i % 2 == 0).map(i, i * 2).map(i, i / 2).size() == 250 @@ -21,7 +21,7 @@ declare longlist { } =====> bindings: {longlist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499]} -error: evaluation error: Iteration budget exceeded: 1000 +error: evaluation error at test_location:56: Iteration budget exceeded: 1000 error_code: ITERATION_BUDGET_EXCEEDED Source: longlist.map(i, longlist.map(j, longlist.map(k, [i, j, k]))).size() == 9 @@ -38,5 +38,5 @@ declare longlist { } =====> bindings: {longlist=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]} -error: evaluation error: Iteration budget exceeded: 1000 +error: evaluation error at test_location:28: Iteration budget exceeded: 1000 error_code: ITERATION_BUDGET_EXCEEDED From b564c4802d58c8a9f8e0af9bcb458661f2f50e23 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 20 Jan 2026 11:39:20 -0800 Subject: [PATCH 061/100] Internal Changes PiperOrigin-RevId: 858683968 --- bundle/src/main/java/dev/cel/bundle/CelFactory.java | 1 + bundle/src/test/java/dev/cel/bundle/CelImplTest.java | 6 ++++++ .../java/dev/cel/extensions/CelProtoExtensionsTest.java | 1 + .../src/main/java/dev/cel/protobuf/JavaFileGenerator.java | 2 +- .../src/main/java/dev/cel/runtime/CelRuntimeFactory.java | 1 + .../src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java | 6 ++++-- runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java | 6 ++++++ .../java/dev/cel/runtime/DescriptorMessageProviderTest.java | 2 +- 8 files changed, 21 insertions(+), 4 deletions(-) diff --git a/bundle/src/main/java/dev/cel/bundle/CelFactory.java b/bundle/src/main/java/dev/cel/bundle/CelFactory.java index a2080bc25..6cc6d8192 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelFactory.java +++ b/bundle/src/main/java/dev/cel/bundle/CelFactory.java @@ -40,6 +40,7 @@ public static CelBuilder standardCelBuilder() { CelParserImpl.newBuilder(), CelCheckerLegacyImpl.newBuilder()), CelRuntimeLegacyImpl.newBuilder()) .setOptions(CelOptions.current().build()) + // CEL-Internal-2 .setStandardEnvironmentEnabled(true); } diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 00b47494d..13c3392d3 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -372,6 +372,7 @@ public void program_setTypeFactoryOnAnyPackedMessage_fieldSelectionSuccess() thr CelAbstractSyntaxTree ast = celCompiler.compile("input.expression").getAst(); CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder() + // CEL-Internal-2 .setTypeFactory( (typeName) -> typeName.equals("google.type.Expr") ? com.google.type.Expr.newBuilder() : null) @@ -401,6 +402,7 @@ public void program_setTypeFactoryOnAnyPackedMessage_messageConstructionSucceeds CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder() + // CEL-Internal-2 .setTypeFactory( (typeName) -> typeName.equals("google.type.Expr") ? com.google.type.Expr.newBuilder() : null) @@ -832,6 +834,7 @@ public void program_duplicateTypeDescriptor() throws Exception { public void program_hermeticDescriptors_wellKnownProtobuf() throws Exception { Cel cel = standardCelBuilderWithMacros() + // CEL-Internal-2 .addMessageTypes(Timestamp.getDescriptor()) .setContainer(CelContainer.ofName("google")) .setResultType(SimpleType.TIMESTAMP) @@ -914,6 +917,7 @@ public void program_deepTypeResolutionEnabledForRuntime_success() throws Excepti CelRuntimeFactory.standardCelRuntimeBuilder() .addFileTypes(CheckedExpr.getDescriptor().getFile()) .setOptions(CelOptions.current().resolveTypeDependencies(true).build()) + // CEL-Internal-2 .build(); CelRuntime.Program program = celRuntime.createProgram(ast); @@ -942,6 +946,7 @@ public void program_deepTypeResolutionDisabledForRuntime_fails() throws Exceptio CelRuntimeFactory.standardCelRuntimeBuilder() .addFileTypes(CheckedExpr.getDescriptor().getFile()) .setOptions(CelOptions.current().resolveTypeDependencies(false).build()) + // CEL-Internal-2 .build(); CelRuntime.Program program = celRuntime.createProgram(ast); @@ -2154,6 +2159,7 @@ public void program_fdsContainsWktDependency_descriptorInstancesMatch() throws E Cel cel = standardCelBuilderWithMacros() .addMessageTypes(descriptors) + // CEL-Internal-2 .setOptions(CelOptions.current().enableTimestampEpoch(true).build()) .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) .build(); diff --git a/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java index f9ec584aa..15f6df5be 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelProtoExtensionsTest.java @@ -295,6 +295,7 @@ public void getExt_onAnyPackedExtensionField_success() throws Exception { TestAllTypesExtensions.registerAllExtensions(extensionRegistry); Cel cel = CelFactory.standardCelBuilder() + // CEL-Internal-2 .addCompilerLibraries(CelExtensions.protos()) .addFileTypes(TestAllTypesExtensions.getDescriptor()) .setExtensionRegistry(extensionRegistry) diff --git a/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java b/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java index be1e07cc2..0eb177a96 100644 --- a/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java +++ b/protobuf/src/main/java/dev/cel/protobuf/JavaFileGenerator.java @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; -// CEL-Internal-5 +// CEL-Internal-3 import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapperBuilder; import freemarker.template.Template; diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java index e19f9d765..322985b22 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeFactory.java @@ -28,6 +28,7 @@ public final class CelRuntimeFactory { public static CelRuntimeBuilder standardCelRuntimeBuilder() { return CelRuntimeLegacyImpl.newBuilder() .setOptions(CelOptions.current().build()) + // CEL-Internal-2 .setStandardEnvironmentEnabled(true); } diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 15591e680..983f68055 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -38,7 +38,7 @@ import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; -// CEL-Internal-3 +// CEL-Internal-1 import dev.cel.common.internal.ProtoMessageFactory; import dev.cel.common.types.CelTypes; import dev.cel.common.values.CelValueProvider; @@ -97,6 +97,7 @@ public CelRuntimeBuilder toRuntimeBuilder() { CelRuntimeBuilder builder = new Builder() .setOptions(options) + // CEL-Internal-2 .setStandardEnvironmentEnabled(standardEnvironmentEnabled) .setExtensionRegistry(extensionRegistry) .addFileTypes(fileDescriptors) @@ -366,7 +367,8 @@ private ImmutableSet newStandardFunctionBindings( break; default: if (!options.enableHeterogeneousNumericComparisons()) { - return !CelStandardFunctions.isHeterogeneousComparison(standardOverload); + return !CelStandardFunctions.isHeterogeneousComparison( + standardOverload); } break; } diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java index 7d7243384..c7f142602 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java @@ -171,6 +171,7 @@ public void newWellKnownTypeMessage_withDifferentDescriptorInstance() throws Exc .build(); CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder() + // CEL-Internal-2 .addFileTypes( FileDescriptorSet.newBuilder() .addFile( @@ -192,6 +193,7 @@ public void newWellKnownTypeMessage_inDynamicMessage_withSetTypeFactory() throws .build(); CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder() + // CEL-Internal-2 .setTypeFactory( (typeName) -> typeName.equals("google.protobuf.BoolValue") @@ -218,6 +220,8 @@ public void newWellKnownTypeMessage_inAnyMessage_withDifferentDescriptorInstance CelCompilerFactory.standardCelCompilerBuilder().addFileTypes(fds).build(); CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder() + // CEL-Internal-2 + .addFileTypes(fds) .build(); CelAbstractSyntaxTree ast = @@ -241,6 +245,8 @@ public void newWellKnownTypeMessage_inAnyMessage_withSetTypeFactory() throws Exc CelCompilerFactory.standardCelCompilerBuilder().addFileTypes(fds).build(); CelRuntime celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder() + // CEL-Internal-2 + .addFileTypes(fds) .setTypeFactory( (typeName) -> typeName.equals("google.protobuf.Any") diff --git a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java index 6ab1638b1..02c8f2b51 100644 --- a/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java +++ b/runtime/src/test/java/dev/cel/runtime/DescriptorMessageProviderTest.java @@ -35,7 +35,7 @@ import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; -// CEL-Internal-3 +// CEL-Internal-1 import dev.cel.common.internal.ProtoMessageFactory; import dev.cel.common.internal.WellKnownProto; import dev.cel.expr.conformance.proto2.TestAllTypes; From b8eafbf6d0a6228e42d2f837406f11a9715ec88d Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 21 Jan 2026 09:51:49 -0800 Subject: [PATCH 062/100] OSS Fix for Bazel 9 PiperOrigin-RevId: 859145330 --- .bazelrc | 3 +++ BUILD.bazel | 7 +++++++ MODULE.bazel | 6 +++--- .../src/test/java/dev/cel/conformance/conformance_test.bzl | 3 ++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index 31bb2d7bc..7bec7cc95 100644 --- a/.bazelrc +++ b/.bazelrc @@ -4,3 +4,6 @@ build --java_language_version=11 # Hide Java 8 deprecation warnings. common --javacopt=-Xlint:-options + +# Remove flag once https://github.com/google/cel-spec/issues/508 and rules_jvm_external is fixed. +common --incompatible_autoload_externally=proto_library,cc_proto_library,java_proto_library,java_test diff --git a/BUILD.bazel b/BUILD.bazel index 06942bc50..024908625 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -22,6 +22,13 @@ load( "DEFAULT_TOOLCHAIN_CONFIGURATION", "default_java_toolchain", ) +load( + "@rules_java//java:defs.bzl", + "java_binary", + "java_library", + "java_package_configuration", + "java_plugin", +) load("@rules_license//rules:license.bzl", "license") licenses(["notice"]) # Apache License 2.0 diff --git a/MODULE.bazel b/MODULE.bazel index 8e0e79068..b3a78627c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -17,14 +17,14 @@ module( ) bazel_dep(name = "bazel_skylib", version = "1.7.1") -bazel_dep(name = "rules_jvm_external", version = "6.7") +bazel_dep(name = "rules_jvm_external", version = "6.9") bazel_dep(name = "protobuf", version = "29.3", repo_name = "com_google_protobuf") # see https://github.com/bazelbuild/rules_android/issues/373 bazel_dep(name = "googleapis", version = "0.0.0-20241220-5e258e33.bcr.1", repo_name = "com_google_googleapis") bazel_dep(name = "rules_pkg", version = "1.0.1") bazel_dep(name = "rules_license", version = "1.0.0") bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "rules_java", version = "8.12.0") -bazel_dep(name = "rules_android", version = "0.6.4") +bazel_dep(name = "rules_android", version = "0.7.1") bazel_dep(name = "rules_shell", version = "0.5.1") bazel_dep(name = "googleapis-java", version = "1.0.0") bazel_dep(name = "cel-spec", version = "0.24.0", repo_name = "cel_spec") @@ -39,7 +39,7 @@ GUAVA_VERSION = "33.5.0" TRUTH_VERSION = "1.4.4" -PROTOBUF_JAVA_VERSION = "4.33.0" +PROTOBUF_JAVA_VERSION = "4.33.4" # Compile only artifacts [ diff --git a/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl b/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl index fbae91b00..f884a2a84 100644 --- a/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl +++ b/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl @@ -17,6 +17,7 @@ This module contains build rules for generating the conformance test targets. """ load("@rules_java//java:defs.bzl", "java_test") +load("@rules_shell//shell:sh_test.bzl", "sh_test") # Converts the list of tests to skip from the format used by the original Go test runner to a single # flag value where each test is separated by a comma. It also performs expansion, for example @@ -79,7 +80,7 @@ def conformance_test(name, data, mode = MODE.TEST, skip_tests = []): ], ) - native.sh_test( + sh_test( name = name, size = "small", srcs = ["//conformance/src/test/java/dev/cel/conformance:conformance_test.sh"], From e50e7f8bd35e12a7b60701aa6f0e751bbc9062f5 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 28 Jan 2026 09:52:15 -0800 Subject: [PATCH 063/100] Namespace resolution fix for planner PiperOrigin-RevId: 862278837 --- .../runtime/planner/ActivationWrapper.java | 22 ++++ .../cel/runtime/planner/AttributeFactory.java | 13 ++- .../java/dev/cel/runtime/planner/BUILD.bazel | 10 ++ .../dev/cel/runtime/planner/EvalFold.java | 9 +- .../runtime/planner/NamespacedAttribute.java | 47 +++++++- .../cel/runtime/planner/ProgramPlanner.java | 72 ++++++++++-- .../runtime/planner/ProgramPlannerTest.java | 107 +++++++++++++++++- 7 files changed, 258 insertions(+), 22 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/ActivationWrapper.java diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ActivationWrapper.java b/runtime/src/main/java/dev/cel/runtime/planner/ActivationWrapper.java new file mode 100644 index 000000000..f844ab232 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/ActivationWrapper.java @@ -0,0 +1,22 @@ +// 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.runtime.planner; + +import dev.cel.runtime.GlobalResolver; + +/** Identifies a resolver that can be unwrapped to bypass local variable state. */ +public interface ActivationWrapper extends GlobalResolver { + GlobalResolver unwrap(); +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java index 632c6cd91..fabf7ade5 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/AttributeFactory.java @@ -29,7 +29,7 @@ final class AttributeFactory { private final CelValueConverter celValueConverter; NamespacedAttribute newAbsoluteAttribute(String... names) { - return new NamespacedAttribute(typeProvider, celValueConverter, ImmutableSet.copyOf(names)); + return NamespacedAttribute.create(typeProvider, celValueConverter, ImmutableSet.copyOf(names)); } RelativeAttribute newRelativeAttribute(PlannedInterpretable operand) { @@ -37,11 +37,14 @@ RelativeAttribute newRelativeAttribute(PlannedInterpretable operand) { } MaybeAttribute newMaybeAttribute(String name) { + // When there's a single name with a dot prefix, it indicates that the 'maybe' attribute is a + // globally namespaced identifier. + // Otherwise, the candidate names resolved from the container should be inferred. + ImmutableSet names = + name.startsWith(".") ? ImmutableSet.of(name) : container.resolveCandidateNames(name); + return new MaybeAttribute( - this, - ImmutableList.of( - new NamespacedAttribute( - typeProvider, celValueConverter, container.resolveCandidateNames(name)))); + this, ImmutableList.of(NamespacedAttribute.create(typeProvider, celValueConverter, names))); } static AttributeFactory newAttributeFactory( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index d15d9af92..b6c90dc92 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -114,6 +114,7 @@ java_library( "RelativeAttribute.java", ], deps = [ + ":activation_wrapper", ":eval_helpers", ":execution_frame", ":planned_interpretable", @@ -130,6 +131,14 @@ java_library( ], ) +java_library( + name = "activation_wrapper", + srcs = ["ActivationWrapper.java"], + deps = [ + "//runtime:interpretable", + ], +) + java_library( name = "qualifier", srcs = ["Qualifier.java"], @@ -333,6 +342,7 @@ java_library( name = "eval_fold", srcs = ["EvalFold.java"], deps = [ + ":activation_wrapper", ":execution_frame", ":planned_interpretable", "//runtime:concatenated_list_view", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java index 49047f3a4..3545ee4f7 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalFold.java @@ -97,7 +97,7 @@ private Object evalMap(Map iterRange, Folder folder, ExecutionFrame frame) if (!iterVar2.isEmpty()) { folder.iterVar2Val = entry.getValue(); } - + boolean cond = (boolean) condition.eval(folder, frame); if (!cond) { return result.eval(folder, frame); @@ -149,7 +149,7 @@ private static Object maybeUnwrapAccumulator(Object val) { return val; } - private static class Folder implements GlobalResolver { + private static class Folder implements ActivationWrapper { private final GlobalResolver resolver; private final String accuVar; private final String iterVar; @@ -166,6 +166,11 @@ private Folder(GlobalResolver resolver, String accuVar, String iterVar, String i this.iterVar2 = iterVar2; } + @Override + public GlobalResolver unwrap() { + return resolver; + } + @Override public @Nullable Object resolve(String name) { if (name.equals(accuVar)) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index d513bc7ba..12b56049a 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -28,6 +28,7 @@ @Immutable final class NamespacedAttribute implements Attribute { + private final boolean disambiguateNames; private final ImmutableSet namespacedNames; private final ImmutableList qualifiers; private final CelValueConverter celValueConverter; @@ -35,8 +36,21 @@ final class NamespacedAttribute implements Attribute { @Override public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { + GlobalResolver inputVars = ctx; + // Unwrap any local activations to ensure that we reach the variables provided as input + // to the expression in the event that we need to disambiguate between global and local + // variables. + if (disambiguateNames) { + inputVars = unwrapToNonLocal(ctx); + } + for (String name : namespacedNames) { - Object value = ctx.resolve(name); + GlobalResolver resolver = ctx; + if (disambiguateNames) { + resolver = inputVars; + } + + Object value = resolver.resolve(name); if (value != null) { if (!qualifiers.isEmpty()) { return applyQualifiers(value, celValueConverter, qualifiers); @@ -82,12 +96,20 @@ ImmutableSet candidateVariableNames() { return namespacedNames; } + private GlobalResolver unwrapToNonLocal(GlobalResolver resolver) { + while (resolver instanceof ActivationWrapper) { + resolver = ((ActivationWrapper) resolver).unwrap(); + } + return resolver; + } + @Override public NamespacedAttribute addQualifier(Qualifier qualifier) { return new NamespacedAttribute( typeProvider, celValueConverter, namespacedNames, + disambiguateNames, ImmutableList.builder().addAll(qualifiers).add(qualifier).build()); } @@ -106,21 +128,38 @@ private static Object applyQualifiers( return obj; } - NamespacedAttribute( + static NamespacedAttribute create( CelTypeProvider typeProvider, CelValueConverter celValueConverter, ImmutableSet namespacedNames) { - this(typeProvider, celValueConverter, namespacedNames, ImmutableList.of()); + ImmutableSet.Builder namesBuilder = ImmutableSet.builder(); + boolean disambiguateNames = false; + for (String name : namespacedNames) { + if (name.startsWith(".")) { + disambiguateNames = true; + namesBuilder.add(name.substring(1)); + } else { + namesBuilder.add(name); + } + } + return new NamespacedAttribute( + typeProvider, + celValueConverter, + namesBuilder.build(), + disambiguateNames, + ImmutableList.of()); } - private NamespacedAttribute( + NamespacedAttribute( CelTypeProvider typeProvider, CelValueConverter celValueConverter, ImmutableSet namespacedNames, + boolean disambiguateNames, ImmutableList qualifiers) { this.typeProvider = typeProvider; this.celValueConverter = celValueConverter; this.namespacedNames = namespacedNames; + this.disambiguateNames = disambiguateNames; this.qualifiers = qualifiers; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 1c2bae9c2..113ee05e8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -15,6 +15,7 @@ package dev.cel.runtime.planner; import com.google.auto.value.AutoValue; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -48,6 +49,7 @@ import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.DefaultDispatcher; import dev.cel.runtime.Program; +import java.util.HashMap; import java.util.NoSuchElementException; import java.util.Optional; @@ -161,8 +163,12 @@ private PlannedInterpretable planIdent(CelExpr celExpr, PlannerContext ctx) { return planCheckedIdent(celExpr.id(), ref, ctx.typeMap()); } - return EvalAttribute.create( - celExpr.id(), attributeFactory.newMaybeAttribute(celExpr.ident().name())); + String identName = celExpr.ident().name(); + if (ctx.isLocalVar(identName)) { + return EvalAttribute.create(celExpr.id(), attributeFactory.newAbsoluteAttribute(identName)); + } + + return EvalAttribute.create(celExpr.id(), attributeFactory.newMaybeAttribute(identName)); } private PlannedInterpretable planCheckedIdent( @@ -314,10 +320,18 @@ private PlannedInterpretable planComprehension(CelExpr expr, PlannerContext ctx) PlannedInterpretable accuInit = plan(comprehension.accuInit(), ctx); PlannedInterpretable iterRange = plan(comprehension.iterRange(), ctx); + + ctx.pushLocalVars(comprehension.accuVar(), comprehension.iterVar(), comprehension.iterVar2()); + PlannedInterpretable loopCondition = plan(comprehension.loopCondition(), ctx); PlannedInterpretable loopStep = plan(comprehension.loopStep(), ctx); + + ctx.popLocalVars(comprehension.iterVar(), comprehension.iterVar2()); + PlannedInterpretable result = plan(comprehension.result(), ctx); + ctx.popLocalVars(comprehension.accuVar()); + return EvalFold.create( expr.id(), comprehension.accuVar(), @@ -460,15 +474,57 @@ private static Builder newBuilder() { } } - @AutoValue - abstract static class PlannerContext { + static final class PlannerContext { + private final ImmutableMap referenceMap; + private final ImmutableMap typeMap; + private final HashMap localVars = new HashMap<>(); + + ImmutableMap referenceMap() { + return referenceMap; + } - abstract ImmutableMap referenceMap(); + ImmutableMap typeMap() { + return typeMap; + } - abstract ImmutableMap typeMap(); + private void pushLocalVars(String... names) { + for (String name : names) { + if (Strings.isNullOrEmpty(name)) { + continue; + } + localVars.merge(name, 1, Integer::sum); + } + } + + private void popLocalVars(String... names) { + for (String name : names) { + if (Strings.isNullOrEmpty(name)) { + continue; + } + Integer count = localVars.get(name); + if (count != null) { + if (count == 1) { + localVars.remove(name); + } else { + localVars.put(name, count - 1); + } + } + } + } + + /** Checks if the given name is a local variable in the current scope. */ + private boolean isLocalVar(String name) { + return localVars.containsKey(name); + } + + private PlannerContext( + ImmutableMap referenceMap, ImmutableMap typeMap) { + this.referenceMap = referenceMap; + this.typeMap = typeMap; + } - private static PlannerContext create(CelAbstractSyntaxTree ast) { - return new AutoValue_ProgramPlanner_PlannerContext(ast.getReferenceMap(), ast.getTypeMap()); + static PlannerContext create(CelAbstractSyntaxTree ast) { + return new PlannerContext(ast.getReferenceMap(), ast.getTypeMap()); } } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 7b9e1920b..ca862ed27 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -824,7 +824,7 @@ public void plan_comprehension_iterationLimit_success() throws Exception { CEL_VALUE_CONVERTER, CEL_CONTAINER, options, - ImmutableSet.of()); + /* lateBoundFunctionNames= */ ImmutableSet.of()); CelAbstractSyntaxTree ast = compile("[1, 2, 3].map(x, [1, 2].map(y, x + y))"); Program program = planner.plan(ast); @@ -836,13 +836,114 @@ public void plan_comprehension_iterationLimit_success() throws Exception { ImmutableList.of(2L, 3L), ImmutableList.of(3L, 4L), ImmutableList.of(4L, 5L))); } + @Test + public void localShadowIdentifier_inSelect() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addVar("cel.example.y", SimpleType.INT) + .build(); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO), + newDispatcher(), + CEL_VALUE_CONVERTER, + CelContainer.ofName("cel.example"), + CEL_OPTIONS, + /* lateBoundFunctionNames= */ ImmutableSet.of()); + CelAbstractSyntaxTree ast = compile(celCompiler, "[{'z': 0}].exists(y, y.z == 0)"); + + Program program = planner.plan(ast); + + boolean result = + (boolean) program.eval(ImmutableMap.of("cel.example.y", ImmutableMap.of("z", 1))); + assertThat(result).isTrue(); + } + + @Test + public void localShadowIdentifier_inSelect_globalDisambiguation() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addVar("y.z", SimpleType.INT) + .build(); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO), + newDispatcher(), + CEL_VALUE_CONVERTER, + CelContainer.ofName("y"), + CEL_OPTIONS, + /* lateBoundFunctionNames= */ ImmutableSet.of()); + CelAbstractSyntaxTree ast = compile(celCompiler, "[{'z': 0}].exists(y, y.z == 0 && .y.z == 1)"); + + Program program = planner.plan(ast); + + boolean result = (boolean) program.eval(ImmutableMap.of("y.z", 1)); + assertThat(result).isTrue(); + } + + @Test + public void localShadowIdentifier_withGlobalDisambiguation() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addVar("x", SimpleType.INT) + .build(); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO), + newDispatcher(), + CEL_VALUE_CONVERTER, + CelContainer.newBuilder().build(), + CEL_OPTIONS, + /* lateBoundFunctionNames= */ ImmutableSet.of()); + CelAbstractSyntaxTree ast = compile(celCompiler, "[0].exists(x, x == 0 && .x == 1)"); + + Program program = planner.plan(ast); + + boolean result = (boolean) program.eval(ImmutableMap.of("x", 1)); + assertThat(result).isTrue(); + } + + @Test + public void localDoubleShadowIdentifier_withGlobalDisambiguation() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addVar("x", SimpleType.INT) + .build(); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO), + newDispatcher(), + CEL_VALUE_CONVERTER, + CelContainer.newBuilder().build(), + CEL_OPTIONS, + /* lateBoundFunctionNames= */ ImmutableSet.of()); + CelAbstractSyntaxTree ast = compile(celCompiler, "[0].exists(x, [x+1].exists(x, x == .x))"); + + Program program = planner.plan(ast); + + boolean result = (boolean) program.eval(ImmutableMap.of("x", 1)); + assertThat(result).isTrue(); + } + private CelAbstractSyntaxTree compile(String expression) throws Exception { - CelAbstractSyntaxTree ast = CEL_COMPILER.parse(expression).getAst(); + return compile(CEL_COMPILER, expression); + } + + private CelAbstractSyntaxTree compile(CelCompiler compiler, String expression) throws Exception { + CelAbstractSyntaxTree ast = compiler.parse(expression).getAst(); if (isParseOnly) { return ast; } - return CEL_COMPILER.check(ast).getAst(); + return compiler.check(ast).getAst(); } private static CelByteString concatenateByteArrays(CelByteString bytes1, CelByteString bytes2) { From ff8cd306da1b53ffc45ca5ae13779d95edf0b87b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 28 Jan 2026 10:54:45 -0800 Subject: [PATCH 064/100] Fix repeated wrapper field selections PiperOrigin-RevId: 862306605 --- .../runtime/DescriptorMessageProvider.java | 4 +- .../cel/runtime/CelValueInterpreterTest.java | 7 ++ runtime/src/test/resources/wrappers.baseline | 71 ++++++++++++++++++- .../dev/cel/testing/BaseInterpreterTest.java | 33 ++++++++- 4 files changed, 110 insertions(+), 5 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java index f2ea85b9b..2980aea96 100644 --- a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java @@ -126,7 +126,9 @@ public DescriptorMessageProvider(ProtoMessageFactory protoMessageFactory, CelOpt MessageOrBuilder typedMessage = assertFullProtoMessage(message, fieldName); FieldDescriptor fieldDescriptor = findField(typedMessage.getDescriptorForType(), fieldName); // check whether the field is a wrapper type, then test has and return null - if (isWrapperType(fieldDescriptor) && !typedMessage.hasField(fieldDescriptor)) { + if (isWrapperType(fieldDescriptor) + && !fieldDescriptor.isRepeated() + && !typedMessage.hasField(fieldDescriptor)) { return NullValue.NULL_VALUE; } Object value = typedMessage.getField(fieldDescriptor); diff --git a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java index f56bb3012..ad3fae082 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java @@ -27,4 +27,11 @@ public class CelValueInterpreterTest extends BaseInterpreterTest { public CelValueInterpreterTest() { super(newBaseCelOptions().toBuilder().enableCelValue(true).build()); } + + @Override + public void wrappers() throws Exception { + // Field selection on repeated wrappers broken. + // This test along with CelValue adapter will be removed in a separate CL + skipBaselineVerification(); + } } diff --git a/runtime/src/test/resources/wrappers.baseline b/runtime/src/test/resources/wrappers.baseline index fc5d93654..a971dcb29 100644 --- a/runtime/src/test/resources/wrappers.baseline +++ b/runtime/src/test/resources/wrappers.baseline @@ -66,6 +66,75 @@ single_bytes_wrapper { } result: true +Source: x.repeated_int32_wrapper == [1,2] && x.repeated_int64_wrapper == [3] && x.repeated_float_wrapper == [1.5, 2.5] && x.repeated_double_wrapper == [3.5, 4.5] && x.repeated_string_wrapper == ['foo', 'bar'] && x.repeated_bool_wrapper == [true] && x.repeated_uint32_wrapper == [1u, 2u] && x.repeated_uint64_wrapper == [] +declare x { + value cel.expr.conformance.proto3.TestAllTypes +} +=====> +bindings: {x=single_int64_wrapper { + value: -34 +} +single_int32_wrapper { + value: -12 +} +single_double_wrapper { + value: -3.0 +} +single_float_wrapper { + value: 1.5 +} +single_uint64_wrapper { + value: 34 +} +single_uint32_wrapper { + value: 12 +} +single_string_wrapper { +} +single_bool_wrapper { +} +single_bytes_wrapper { + value: "hi" +} +repeated_int64_wrapper { + value: 3 +} +repeated_int32_wrapper { + value: 1 +} +repeated_int32_wrapper { + value: 2 +} +repeated_double_wrapper { + value: 3.5 +} +repeated_double_wrapper { + value: 4.5 +} +repeated_float_wrapper { + value: 1.5 +} +repeated_float_wrapper { + value: 2.5 +} +repeated_uint32_wrapper { + value: 1 +} +repeated_uint32_wrapper { + value: 2 +} +repeated_string_wrapper { + value: "foo" +} +repeated_string_wrapper { + value: "bar" +} +repeated_bool_wrapper { + value: true +} +} +result: true + Source: x.single_bool_wrapper == null && x.single_bytes_wrapper == null && x.single_double_wrapper == null && x.single_float_wrapper == null && x.single_int32_wrapper == null && x.single_int64_wrapper == null && x.single_string_wrapper == null && x.single_uint32_wrapper == null && x.single_uint64_wrapper == null declare x { value cel.expr.conformance.proto3.TestAllTypes @@ -88,4 +157,4 @@ result: NULL_VALUE Source: google.protobuf.Timestamp{ seconds: 253402300800 } =====> bindings: {} -result: +10000-01-01T00:00:00Z \ No newline at end of file +result: +10000-01-01T00:00:00Z diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 44fb84433..425271e1b 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -2015,6 +2015,33 @@ public void wrappers() throws Exception { .setSingleBoolWrapper(BoolValue.getDefaultInstance()) .setSingleStringWrapper(StringValue.getDefaultInstance()))); + source = + "x.repeated_int32_wrapper == [1,2] && " + + "x.repeated_int64_wrapper == [3] && " + + "x.repeated_float_wrapper == [1.5, 2.5] && " + + "x.repeated_double_wrapper == [3.5, 4.5] && " + + "x.repeated_string_wrapper == ['foo', 'bar'] && " + + "x.repeated_bool_wrapper == [true] && " + + "x.repeated_uint32_wrapper == [1u, 2u] && " + + "x.repeated_uint64_wrapper == []"; + + runTest( + ImmutableMap.of( + "x", + wrapperBindings + .addRepeatedInt32Wrapper(Int32Value.of(1)) + .addRepeatedInt32Wrapper(Int32Value.of(2)) + .addRepeatedInt64Wrapper(Int64Value.of(3)) + .addRepeatedFloatWrapper(FloatValue.of(1.5f)) + .addRepeatedFloatWrapper(FloatValue.of(2.5f)) + .addRepeatedDoubleWrapper(DoubleValue.of(3.5f)) + .addRepeatedDoubleWrapper(DoubleValue.of(4.5f)) + .addRepeatedStringWrapper(StringValue.of("foo")) + .addRepeatedStringWrapper(StringValue.of("bar")) + .addRepeatedBoolWrapper(BoolValue.of(true)) + .addRepeatedUint32Wrapper(UInt32Value.of(1)) + .addRepeatedUint32Wrapper(UInt32Value.of(2)))); + source = "x.single_bool_wrapper == null && " + "x.single_bytes_wrapper == null && " @@ -2041,7 +2068,7 @@ public void wrappers() throws Exception { @Test public void longComprehension() { ImmutableList l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList()); - addFunctionBinding(CelFunctionBinding.from("constantLongList", ImmutableList.of(), args -> l)); + addFunctionBinding(CelFunctionBinding.from("constantLongList", ImmutableList.of(), unused -> l)); // Comprehension over compile-time constant long list. declareFunction( @@ -2290,8 +2317,8 @@ public void dynamicMessage_dynamicDescriptor() throws Exception { StructTypeReference.create(TEST_ALL_TYPE_DYNAMIC_DESCRIPTOR.getFullName())), SimpleType.BOOL)); addFunctionBinding( - CelFunctionBinding.from("f_msg_generated", TestAllTypes.class, x -> true), - CelFunctionBinding.from("f_msg_dynamic", DynamicMessage.class, x -> true)); + CelFunctionBinding.from("f_msg_generated", TestAllTypes.class, unused -> true), + CelFunctionBinding.from("f_msg_dynamic", DynamicMessage.class, unused -> true)); input = ImmutableMap.of( "dynamic_msg", dynamicMessageBuilder.build(), From 83e91ccc33758f4ad0837506cbccd236d704eda4 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 28 Jan 2026 11:33:22 -0800 Subject: [PATCH 065/100] Fix extension class visibility to address Kotlin compilation warnings PiperOrigin-RevId: 862324115 --- .../java/dev/cel/extensions/CelComprehensionsExtensions.java | 2 +- .../src/main/java/dev/cel/extensions/CelListsExtensions.java | 3 ++- .../src/main/java/dev/cel/extensions/CelMathExtensions.java | 2 +- .../src/main/java/dev/cel/extensions/CelProtoExtensions.java | 5 ++++- .../src/main/java/dev/cel/extensions/CelRegexExtensions.java | 2 +- .../src/main/java/dev/cel/extensions/CelSetsExtensions.java | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java index 462e1d008..23663f02e 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelComprehensionsExtensions.java @@ -41,7 +41,7 @@ import java.util.Optional; /** Internal implementation of CEL two variable comprehensions extensions. */ -final class CelComprehensionsExtensions +public final class CelComprehensionsExtensions implements CelCompilerLibrary, CelInternalRuntimeLibrary, CelExtensionLibrary.FeatureSet { private static final String MAP_INSERT_FUNCTION = "cel.@mapInsert"; diff --git a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java index fb46d4747..a91edd822 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelListsExtensions.java @@ -51,9 +51,10 @@ import java.util.Set; /** Internal implementation of CEL lists extensions. */ -final class CelListsExtensions +public final class CelListsExtensions implements CelCompilerLibrary, CelInternalRuntimeLibrary, CelExtensionLibrary.FeatureSet { + /** Supported functions for Lists extension library. */ @SuppressWarnings({"unchecked"}) // Unchecked: Type-checker guarantees casting safety. public enum Function { // Note! Creating dependencies on the outer class may cause circular initialization issues. diff --git a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java index e74177bf1..57c8c1378 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java @@ -57,7 +57,7 @@ */ @SuppressWarnings({"rawtypes", "unchecked"}) // Use of raw Comparables. @Immutable -final class CelMathExtensions +public final class CelMathExtensions implements CelCompilerLibrary, CelRuntimeLibrary, CelExtensionLibrary.FeatureSet { private static final String MATH_NAMESPACE = "math"; diff --git a/extensions/src/main/java/dev/cel/extensions/CelProtoExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelProtoExtensions.java index f92265782..6fe3c4c0c 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelProtoExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelProtoExtensions.java @@ -31,7 +31,8 @@ /** Internal implementation of CEL proto extensions. */ @Immutable -final class CelProtoExtensions implements CelCompilerLibrary, CelExtensionLibrary.FeatureSet { +public final class CelProtoExtensions + implements CelCompilerLibrary, CelExtensionLibrary.FeatureSet { private static final String PROTO_NAMESPACE = "proto"; private static final CelExpr ERROR = CelExpr.newBuilder().setConstant(Constants.ERROR).build(); @@ -148,4 +149,6 @@ private static boolean isTargetInNamespace(CelExpr target) { return target.exprKind().getKind().equals(CelExpr.ExprKind.Kind.IDENT) && target.ident().name().equals(PROTO_NAMESPACE); } + + CelProtoExtensions() {} } diff --git a/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java index 368301ee8..f1ed3b478 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java @@ -37,7 +37,7 @@ /** Internal implementation of CEL regex extensions. */ @Immutable -final class CelRegexExtensions +public final class CelRegexExtensions implements CelCompilerLibrary, CelRuntimeLibrary, CelExtensionLibrary.FeatureSet { private static final String REGEX_REPLACE_FUNCTION = "regex.replace"; diff --git a/extensions/src/main/java/dev/cel/extensions/CelSetsExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelSetsExtensions.java index 35739ddc9..324528b05 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelSetsExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelSetsExtensions.java @@ -41,7 +41,7 @@ * rewrite the AST into a map to achieve a O(1) lookup. */ @Immutable -final class CelSetsExtensions +public final class CelSetsExtensions implements CelCompilerLibrary, CelRuntimeLibrary, CelExtensionLibrary.FeatureSet { private static final String SET_CONTAINS_OVERLOAD_DOC = From 8d6bff68f108e6f15554d8d9d1afa55d2ecc330e Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 2 Feb 2026 16:29:35 -0800 Subject: [PATCH 066/100] Introduce top-level runtime APIs based on Program Planner PiperOrigin-RevId: 864571744 --- .../java/dev/cel/common/values/BUILD.bazel | 6 +- .../cel/common/values/CelValueConverter.java | 8 +- .../cel/common/values/CelValueProvider.java | 4 + .../common/values/ProtoCelValueConverter.java | 9 +- .../values/ProtoMessageValueProvider.java | 4 +- .../cel/common/values/StructValueTest.java | 35 +- runtime/BUILD.bazel | 9 + .../src/main/java/dev/cel/runtime/BUILD.bazel | 44 ++ .../dev/cel/runtime/CelRuntimeBuilder.java | 28 +- .../java/dev/cel/runtime/CelRuntimeImpl.java | 467 ++++++++++++++++++ .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 32 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../cel/runtime/planner/EvalCreateList.java | 2 +- .../dev/cel/runtime/planner/EvalHelpers.java | 3 + .../cel/runtime/planner/ProgramPlanner.java | 4 +- .../runtime/planner/RelativeAttribute.java | 5 +- .../src/test/java/dev/cel/runtime/BUILD.bazel | 13 +- .../cel/runtime/CelValueInterpreterTest.java | 37 -- .../cel/runtime/PlannerInterpreterTest.java | 60 +++ 19 files changed, 682 insertions(+), 89 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java delete mode 100644 runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java create mode 100644 runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index e9b4be4f1..3a78091bf 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -57,6 +57,7 @@ java_library( tags = [ ], deps = [ + "//common/values", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -68,6 +69,7 @@ cel_android_library( tags = [ ], deps = [ + "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -207,13 +209,13 @@ java_library( tags = [ ], deps = [ - ":base_proto_message_value_provider", ":proto_message_value", "//common:options", "//common/annotations", "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", - "//common/values:base_proto_cel_value_converter", + "//common/values", + "//common/values:cel_value_provider", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_protobuf_protobuf_java", ], diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index c3f3727a1..ae0b40ef7 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -33,7 +33,13 @@ @SuppressWarnings("unchecked") // Unchecked cast of generics due to type-erasure (ex: MapValue). @Internal @Immutable -public abstract class CelValueConverter { +public class CelValueConverter { + + private static final CelValueConverter DEFAULT_INSTANCE = new CelValueConverter(); + + public static CelValueConverter getDefaultInstance() { + return DEFAULT_INSTANCE; + } /** Adapts a {@link CelValue} to a plain old Java Object. */ public Object unwrap(CelValue celValue) { diff --git a/common/src/main/java/dev/cel/common/values/CelValueProvider.java b/common/src/main/java/dev/cel/common/values/CelValueProvider.java index 717834660..20ae865e7 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/CelValueProvider.java @@ -27,4 +27,8 @@ public interface CelValueProvider { * a wrapper. */ Optional newValue(String structType, Map fields); + + default CelValueConverter celValueConverter() { + return CelValueConverter.getDefaultInstance(); + } } diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 08f30b9d1..14ff87f1b 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -82,7 +82,13 @@ public Object toRuntimeValue(Object value) { } if (value instanceof MessageOrBuilder) { - MessageOrBuilder message = (MessageOrBuilder) value; + Message message; + if (value instanceof Message.Builder) { + message = ((Message.Builder) value).build(); + } else { + message = (Message) value; + } + // Attempt to convert the proto from a dynamic message into a concrete message if possible. if (message instanceof DynamicMessage) { message = dynamicProto.maybeAdaptDynamicMessage((DynamicMessage) message); @@ -110,6 +116,7 @@ public Object fromProtoMessageFieldToCelValue(Message message, FieldDescriptor f switch (fieldDescriptor.getType()) { case MESSAGE: if (WellKnownProto.isWrapperType(fieldDescriptor.getMessageType().getFullName()) + && !fieldDescriptor.isRepeated() && !message.hasField(fieldDescriptor)) { // Special semantics for wrapper types per CEL specification. These all convert into null // instead of the default value. diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java index 5bf2927ab..a05658c8f 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java @@ -34,13 +34,13 @@ */ @Immutable @Internal -public class ProtoMessageValueProvider extends BaseProtoMessageValueProvider { +public class ProtoMessageValueProvider implements CelValueProvider { private final ProtoAdapter protoAdapter; private final ProtoMessageFactory protoMessageFactory; private final ProtoCelValueConverter protoCelValueConverter; @Override - public BaseProtoCelValueConverter protoCelValueConverter() { + public CelValueConverter celValueConverter() { return protoCelValueConverter; } diff --git a/common/src/test/java/dev/cel/common/values/StructValueTest.java b/common/src/test/java/dev/cel/common/values/StructValueTest.java index 1db147882..b8d6371a8 100644 --- a/common/src/test/java/dev/cel/common/values/StructValueTest.java +++ b/common/src/test/java/dev/cel/common/values/StructValueTest.java @@ -31,7 +31,6 @@ import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructType; -import dev.cel.expr.conformance.proto3.TestAllTypes; import java.util.Map; import java.util.Optional; import org.junit.Test; @@ -185,7 +184,7 @@ public void evaluate_usingMultipleProviders_selectFieldFromCustomClass() throws .setValueProvider( CombinedCelValueProvider.combine( ProtoMessageValueProvider.newInstance( - CelOptions.DEFAULT, DynamicProto.create(typeName -> Optional.empty())), + CelOptions.DEFAULT, DynamicProto.create(unused -> Optional.empty())), CUSTOM_STRUCT_VALUE_PROVIDER)) .build(); CelAbstractSyntaxTree ast = cel.compile("custom_struct{data: 5}.data").getAst(); @@ -195,36 +194,8 @@ public void evaluate_usingMultipleProviders_selectFieldFromCustomClass() throws assertThat(result).isEqualTo(5L); } - @Test - public void evaluate_usingMultipleProviders_selectFieldFromProtobufMessage() throws Exception { - Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableCelValue(true).build()) - .addMessageTypes(TestAllTypes.getDescriptor()) - .setTypeProvider(CUSTOM_STRUCT_TYPE_PROVIDER) - .setValueProvider( - CombinedCelValueProvider.combine( - ProtoMessageValueProvider.newInstance( - CelOptions.DEFAULT, - // Note: this is unideal. Future iterations should make DynamicProto - // completely an internal concern, and not expose it at all. - DynamicProto.create( - typeName -> { - if (typeName.equals(TestAllTypes.getDescriptor().getFullName())) { - return Optional.of(TestAllTypes.newBuilder()); - } - return Optional.empty(); - })), - CUSTOM_STRUCT_VALUE_PROVIDER)) - .build(); - CelAbstractSyntaxTree ast = - cel.compile("cel.expr.conformance.proto3.TestAllTypes{single_string: 'foo'}.single_string") - .getAst(); - - String result = (String) cel.createProgram(ast).eval(); - - assertThat(result).isEqualTo("foo"); - } + // TODO: Bring back evaluate_usingMultipleProviders_selectFieldFromProtobufMessage + // once planner is exposed from factory @SuppressWarnings("Immutable") // Test only private static class CelCustomStructValue extends StructValue { diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 244145960..a42ee7fb4 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -271,3 +271,12 @@ java_library( "//runtime/src/main/java/dev/cel/runtime:variable_resolver", ], ) + +java_library( + name = "runtime_planner_impl", + testonly = 1, # TODO: Move to factory when ready for exposure + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:runtime_planner_impl", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index d253edba3..f77c48d96 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -801,6 +801,48 @@ cel_android_library( ], ) +java_library( + name = "runtime_planner_impl", + testonly = 1, + srcs = ["CelRuntimeImpl.java"], + tags = [ + ], + deps = [ + "//:auto_value", + "//common:cel_ast", + "//common:cel_descriptor_util", + "//common:cel_descriptors", + "//common:container", + "//common:options", + "//common/annotations", + "//common/internal:cel_descriptor_pools", + "//common/internal:default_message_factory", + "//common/internal:dynamic_proto", + "//common/types:default_type_provider", + "//common/types:message_type_provider", + "//common/types:type_providers", + "//common/values", + "//common/values:cel_value_provider", + "//common/values:combined_cel_value_provider", + "//common/values:proto_message_value_provider", + "//runtime", + "//runtime:dispatcher", + "//runtime:evaluation_listener", + "//runtime:function_binding", + "//runtime:function_resolver", + "//runtime:program", + "//runtime:proto_message_runtime_helpers", + "//runtime:runtime_equality", + "//runtime:standard_functions", + "//runtime:variable_resolver", + "//runtime/planner:program_planner", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "runtime", srcs = RUNTIME_SOURCES, @@ -829,6 +871,7 @@ java_library( "//common:cel_ast", "//common:cel_descriptor_util", "//common:cel_descriptors", + "//common:container", "//common:options", "//common/annotations", "//common/internal:cel_descriptor_pools", @@ -836,6 +879,7 @@ java_library( "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", "//common/types:cel_types", + "//common/types:type_providers", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", "//runtime:variable_resolver", diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java index e1e3c1b51..87f11fde2 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeBuilder.java @@ -21,7 +21,9 @@ import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.values.CelValueProvider; import java.util.function.Function; @@ -48,6 +50,14 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder addFunctionBindings(Iterable bindings); + /** Adds bindings for functions that are allowed to be late-bound (resolved at execution time). */ + @CanIgnoreReturnValue + CelRuntimeBuilder addLateBoundFunctions(String... lateBoundFunctionNames); + + /** Adds bindings for functions that are allowed to be late-bound (resolved at execution time). */ + @CanIgnoreReturnValue + CelRuntimeBuilder addLateBoundFunctions(Iterable lateBoundFunctionNames); + /** * Add message {@link Descriptor}s to the builder for type-checking and object creation at * interpretation time. @@ -123,6 +133,13 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder addFileTypes(FileDescriptorSet fileDescriptorSet); + /** + * Sets the {@link CelTypeProvider} for resolving CEL types during evaluation, such as a fully + * qualified type name to a struct or an enum value. + */ + @CanIgnoreReturnValue + CelRuntimeBuilder setTypeProvider(CelTypeProvider celTypeProvider); + /** * Set a custom type factory for the runtime. * @@ -145,7 +162,7 @@ public interface CelRuntimeBuilder { * support proto messages in addition to custom struct values, protobuf value provider must be * configured first before the custom value provider. * - *

    Note {@link CelOptions#enableCelValue()} must be enabled or this method will be a no-op. + *

    Note that this option is only supported for planner-based runtime. */ @CanIgnoreReturnValue CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider); @@ -179,6 +196,15 @@ public interface CelRuntimeBuilder { @CanIgnoreReturnValue CelRuntimeBuilder setExtensionRegistry(ExtensionRegistry extensionRegistry); + + /** + * Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and + * functions. + */ + @CanIgnoreReturnValue + CelRuntimeBuilder setContainer(CelContainer container); + + /** Build a new instance of the {@code CelRuntime}. */ @CheckReturnValue CelRuntime build(); diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java new file mode 100644 index 000000000..1417007e0 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -0,0 +1,467 @@ +// 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.runtime; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.errorprone.annotations.Immutable; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Descriptors; +import com.google.protobuf.ExtensionRegistry; +import com.google.protobuf.Message; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; +import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelDescriptors; +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Internal; +import dev.cel.common.internal.CelDescriptorPool; +import dev.cel.common.internal.CombinedDescriptorPool; +import dev.cel.common.internal.DefaultDescriptorPool; +import dev.cel.common.internal.DefaultMessageFactory; +import dev.cel.common.internal.DynamicProto; +// CEL-Internal-1 +import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.DefaultTypeProvider; +import dev.cel.common.types.ProtoMessageTypeProvider; +import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.CelValueProvider; +import dev.cel.common.values.CombinedCelValueProvider; +import dev.cel.common.values.ProtoMessageValueProvider; +import dev.cel.runtime.planner.ProgramPlanner; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import org.jspecify.annotations.Nullable; + +@AutoValue +@Internal +@Immutable +abstract class CelRuntimeImpl implements CelRuntime { + + abstract ProgramPlanner planner(); + + abstract CelOptions options(); + + abstract CelContainer container(); + + abstract ImmutableMap functionBindings(); + + abstract ImmutableSet fileDescriptors(); + + // Callers must guarantee that a custom runtime library is immutable. CEL provided ones are + // immutable by default. + @SuppressWarnings("Immutable") + @AutoValue.CopyAnnotations + abstract ImmutableSet runtimeLibraries(); + + abstract ImmutableSet lateBoundFunctionNames(); + + abstract CelStandardFunctions standardFunctions(); + + abstract @Nullable CelTypeProvider typeProvider(); + + abstract @Nullable CelValueProvider valueProvider(); + + // Extension registry is unmodifiable. Just not marked as such from Protobuf's implementation. + @SuppressWarnings("Immutable") + @AutoValue.CopyAnnotations + abstract @Nullable ExtensionRegistry extensionRegistry(); + + @Override + public Program createProgram(CelAbstractSyntaxTree ast) throws CelEvaluationException { + return toRuntimeProgram(planner().plan(ast)); + } + + public Program toRuntimeProgram(dev.cel.runtime.Program program) { + return new Program() { + + @Override + public Object eval() throws CelEvaluationException { + return program.eval(); + } + + @Override + public Object eval(Map mapValue) throws CelEvaluationException { + return program.eval(mapValue); + } + + @Override + public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return program.eval(mapValue, lateBoundFunctionResolver); + } + + @Override + public Object eval(Message message) throws CelEvaluationException { + throw new UnsupportedOperationException("Not yet supported."); + } + + @Override + public Object eval(CelVariableResolver resolver) throws CelEvaluationException { + return program.eval(resolver); + } + + @Override + public Object eval( + CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver) + throws CelEvaluationException { + return program.eval(resolver, lateBoundFunctionResolver); + } + + @Override + public Object trace(CelEvaluationListener listener) throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(Map mapValue, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(Message message, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace(CelVariableResolver resolver, CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace( + CelVariableResolver resolver, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object trace( + Map mapValue, + CelFunctionResolver lateBoundFunctionResolver, + CelEvaluationListener listener) + throws CelEvaluationException { + throw new UnsupportedOperationException("Trace is not yet supported."); + } + + @Override + public Object advanceEvaluation(UnknownContext context) throws CelEvaluationException { + throw new UnsupportedOperationException("Unsupported operation."); + } + }; + } + + @Override + public abstract Builder toRuntimeBuilder(); + + static Builder newBuilder() { + return new AutoValue_CelRuntimeImpl.Builder() + .setStandardFunctions(CelStandardFunctions.newBuilder().build()) + .setContainer(CelContainer.newBuilder().build()) + .setExtensionRegistry(ExtensionRegistry.getEmptyRegistry()); + } + + @AutoValue.Builder + abstract static class Builder implements CelRuntimeBuilder { + + public abstract Builder setPlanner(ProgramPlanner planner); + + @Override + public abstract Builder setOptions(CelOptions options); + + @Override + public abstract Builder setStandardFunctions(CelStandardFunctions standardFunctions); + + @Override + public abstract Builder setExtensionRegistry(ExtensionRegistry extensionRegistry); + + @Override + public abstract Builder setTypeProvider(CelTypeProvider celTypeProvider); + + @Override + public abstract Builder setValueProvider(CelValueProvider celValueProvider); + + @Override + public abstract Builder setContainer(CelContainer container); + + abstract CelOptions options(); + + abstract CelContainer container(); + + abstract CelTypeProvider typeProvider(); + + abstract CelValueProvider valueProvider(); + + abstract CelStandardFunctions standardFunctions(); + + abstract ExtensionRegistry extensionRegistry(); + + abstract ImmutableSet.Builder fileDescriptorsBuilder(); + + abstract ImmutableSet.Builder runtimeLibrariesBuilder(); + + abstract ImmutableSet.Builder lateBoundFunctionNamesBuilder(); + + private final Map mutableFunctionBindings = new HashMap<>(); + + @Override + @CanIgnoreReturnValue + public Builder addFunctionBindings(CelFunctionBinding... bindings) { + checkNotNull(bindings); + return addFunctionBindings(Arrays.asList(bindings)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFunctionBindings(Iterable bindings) { + checkNotNull(bindings); + bindings.forEach(o -> mutableFunctionBindings.putIfAbsent(o.getOverloadId(), o)); + return this; + } + + @Override + @CanIgnoreReturnValue + public Builder addLateBoundFunctions(String... lateBoundFunctionNames) { + checkNotNull(lateBoundFunctionNames); + return addLateBoundFunctions(Arrays.asList(lateBoundFunctionNames)); + } + + @Override + @CanIgnoreReturnValue + public Builder addLateBoundFunctions(Iterable lateBoundFunctionNames) { + checkNotNull(lateBoundFunctionNames); + this.lateBoundFunctionNamesBuilder().addAll(lateBoundFunctionNames); + return this; + } + + @Override + @CanIgnoreReturnValue + public Builder addMessageTypes(Descriptors.Descriptor... descriptors) { + checkNotNull(descriptors); + return addMessageTypes(Arrays.asList(descriptors)); + } + + @Override + @CanIgnoreReturnValue + public Builder addMessageTypes(Iterable descriptors) { + checkNotNull(descriptors); + return addFileTypes(CelDescriptorUtil.getFileDescriptorsForDescriptors(descriptors)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFileTypes(DescriptorProtos.FileDescriptorSet fileDescriptorSet) { + checkNotNull(fileDescriptorSet); + return addFileTypes( + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fileDescriptorSet)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFileTypes(Descriptors.FileDescriptor... fileDescriptors) { + checkNotNull(fileDescriptors); + return addFileTypes(Arrays.asList(fileDescriptors)); + } + + @Override + @CanIgnoreReturnValue + public Builder addFileTypes(Iterable fileDescriptors) { + checkNotNull(fileDescriptors); + this.fileDescriptorsBuilder().addAll(fileDescriptors); + return this; + } + + @Override + @CanIgnoreReturnValue + public Builder addLibraries(CelRuntimeLibrary... libraries) { + checkNotNull(libraries); + return this.addLibraries(Arrays.asList(libraries)); + } + + @Override + @CanIgnoreReturnValue + public Builder addLibraries(Iterable libraries) { + checkNotNull(libraries); + this.runtimeLibrariesBuilder().addAll(libraries); + return this; + } + + abstract Builder setFunctionBindings(ImmutableMap value); + + @Override + public Builder setTypeFactory(Function typeFactory) { + throw new UnsupportedOperationException("Unsupported. Use a custom value provider instead."); + } + + @Override + public Builder setStandardEnvironmentEnabled(boolean value) { + throw new UnsupportedOperationException( + "Unsupported. Subset the environment using setStandardFunctions instead."); + } + + /** Throws if an unsupported flag in CelOptions is toggled. */ + private static void assertAllowedCelOptions(CelOptions celOptions) { + String prefix = "Misconfigured CelOptions: "; + if (!celOptions.enableUnsignedLongs()) { + throw new IllegalArgumentException(prefix + "enableUnsignedLongs cannot be disabled."); + } + if (!celOptions.unwrapWellKnownTypesOnFunctionDispatch()) { + throw new IllegalArgumentException( + prefix + "unwrapWellKnownTypesOnFunctionDispatch cannot be disabled."); + } + + // Disallowed options in favor of subsetting + String subsettingError = "Subset the environment instead using setStandardFunctions method."; + if (!celOptions.enableStringConcatenation()) { + throw new IllegalArgumentException( + prefix + "enableStringConcatenation cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableStringConversion()) { + throw new IllegalArgumentException( + prefix + "enableStringConversion cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableListConcatenation()) { + throw new IllegalArgumentException( + prefix + "enableListConcatenation cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableTimestampEpoch()) { + throw new IllegalArgumentException( + prefix + "enableTimestampEpoch cannot be disabled. " + subsettingError); + } + + if (!celOptions.enableHeterogeneousNumericComparisons()) { + throw new IllegalArgumentException( + prefix + + "enableHeterogeneousNumericComparisons cannot be disabled. " + + subsettingError); + } + } + + abstract CelRuntimeImpl autoBuild(); + + private static DefaultDispatcher newDispatcher( + CelStandardFunctions standardFunctions, + Collection customFunctionBindings, + RuntimeEquality runtimeEquality, + CelOptions options) { + DefaultDispatcher.Builder builder = DefaultDispatcher.newBuilder(); + for (CelFunctionBinding binding : + standardFunctions.newFunctionBindings(runtimeEquality, options)) { + builder.addOverload( + binding.getOverloadId(), + binding.getArgTypes(), + binding.isStrict(), + binding.getDefinition()); + } + + for (CelFunctionBinding binding : customFunctionBindings) { + builder.addOverload( + binding.getOverloadId(), + binding.getArgTypes(), + binding.isStrict(), + binding.getDefinition()); + } + + return builder.build(); + } + + private static CelDescriptorPool newDescriptorPool( + CelDescriptors celDescriptors, + ExtensionRegistry extensionRegistry) { + ImmutableList.Builder descriptorPools = new ImmutableList.Builder<>(); + + descriptorPools.add(DefaultDescriptorPool.create(celDescriptors, extensionRegistry)); + + return CombinedDescriptorPool.create(descriptorPools.build()); + } + + @Override + public CelRuntime build() { + assertAllowedCelOptions(options()); + CelDescriptors celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptorsBuilder().build()); + + CelDescriptorPool descriptorPool = + newDescriptorPool( + celDescriptors, + extensionRegistry()); + DefaultMessageFactory defaultMessageFactory = DefaultMessageFactory.create(descriptorPool); + DynamicProto dynamicProto = DynamicProto.create(defaultMessageFactory); + CelValueProvider protoMessageValueProvider = + ProtoMessageValueProvider.newInstance(options(), dynamicProto); + CelValueConverter celValueConverter = protoMessageValueProvider.celValueConverter(); + if (valueProvider() != null) { + protoMessageValueProvider = + CombinedCelValueProvider.combine(protoMessageValueProvider, valueProvider()); + } + + RuntimeEquality runtimeEquality = + RuntimeEquality.create( + ProtoMessageRuntimeHelpers.create(dynamicProto, options()), options()); + ImmutableSet runtimeLibraries = runtimeLibrariesBuilder().build(); + // Add libraries, such as extensions + for (CelRuntimeLibrary celLibrary : runtimeLibraries) { + if (celLibrary instanceof CelInternalRuntimeLibrary) { + ((CelInternalRuntimeLibrary) celLibrary) + .setRuntimeOptions(this, runtimeEquality, options()); + } else { + celLibrary.setRuntimeOptions(this); + } + } + + CelTypeProvider combinedTypeProvider = + new CelTypeProvider.CombinedCelTypeProvider( + new ProtoMessageTypeProvider(celDescriptors), DefaultTypeProvider.getInstance()); + if (typeProvider() != null) { + combinedTypeProvider = + new CelTypeProvider.CombinedCelTypeProvider(combinedTypeProvider, typeProvider()); + } + + DefaultDispatcher dispatcher = + newDispatcher( + standardFunctions(), mutableFunctionBindings.values(), runtimeEquality, options()); + + ProgramPlanner planner = + ProgramPlanner.newPlanner( + combinedTypeProvider, + protoMessageValueProvider, + dispatcher, + celValueConverter, + container(), + options(), + lateBoundFunctionNamesBuilder().build()); + setPlanner(planner); + + setFunctionBindings(ImmutableMap.copyOf(mutableFunctionBindings)); + return autoBuild(); + } + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 983f68055..60c2642d7 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -29,6 +29,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelDescriptors; import dev.cel.common.CelOptions; @@ -40,6 +41,7 @@ import dev.cel.common.internal.DynamicProto; // CEL-Internal-1 import dev.cel.common.internal.ProtoMessageFactory; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.CelTypes; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.ProtoMessageValueProvider; @@ -161,6 +163,18 @@ public CelRuntimeBuilder addFunctionBindings(Iterable bindin return this; } + @Override + public CelRuntimeBuilder addLateBoundFunctions(String... lateBoundFunctionNames) { + throw new UnsupportedOperationException( + "This method is not supported for the legacy runtime"); + } + + @Override + public CelRuntimeBuilder addLateBoundFunctions(Iterable lateBoundFunctionNames) { + throw new UnsupportedOperationException( + "This method is not supported for the legacy runtime"); + } + @Override public CelRuntimeBuilder addMessageTypes(Descriptor... descriptors) { return addMessageTypes(Arrays.asList(descriptors)); @@ -189,9 +203,9 @@ public CelRuntimeBuilder addFileTypes(FileDescriptorSet fileDescriptorSet) { } @Override - public CelRuntimeBuilder setTypeFactory(Function typeFactory) { - this.customTypeFactory = typeFactory; - return this; + public CelRuntimeBuilder setTypeProvider(CelTypeProvider celTypeProvider) { + throw new UnsupportedOperationException( + "setTypeProvider is not supported for legacy runtime"); } @Override @@ -200,6 +214,12 @@ public CelRuntimeBuilder setValueProvider(CelValueProvider celValueProvider) { return this; } + @Override + public CelRuntimeBuilder setTypeFactory(Function typeFactory) { + this.customTypeFactory = typeFactory; + return this; + } + @Override public CelRuntimeBuilder setStandardEnvironmentEnabled(boolean value) { standardEnvironmentEnabled = value; @@ -232,6 +252,12 @@ public CelRuntimeBuilder setExtensionRegistry(ExtensionRegistry extensionRegistr return this; } + @Override + public CelRuntimeBuilder setContainer(CelContainer container) { + throw new UnsupportedOperationException( + "This method is not supported for the legacy runtime"); + } + /** Build a new {@code CelRuntimeLegacyImpl} instance from the builder config. */ @Override public CelRuntimeLegacyImpl build() { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index b6c90dc92..59f39fc91 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -314,6 +314,7 @@ java_library( name = "eval_create_list", srcs = ["EvalCreateList.java"], deps = [ + ":eval_helpers", ":execution_frame", ":planned_interpretable", "//runtime:evaluation_exception", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java index e519b968c..389a21a82 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -30,7 +30,7 @@ final class EvalCreateList extends PlannedInterpretable { public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(values.length); for (PlannedInterpretable value : values) { - builder.add(value.eval(resolver, frame)); + builder.add(EvalHelpers.evalStrictly(value, resolver, frame)); } return builder.build(); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index 6e7fd7e68..ee86c9dd6 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -38,6 +38,9 @@ static Object evalStrictly( PlannedInterpretable interpretable, GlobalResolver resolver, ExecutionFrame frame) { try { return interpretable.eval(resolver, frame); + } catch (LocalizedEvaluationException e) { + // Already localized - propagate as-is to preserve inner expression ID + throw e; } catch (CelRuntimeException e) { // Wrap with current interpretable's location throw new LocalizedEvaluationException(e, interpretable.exprId()); diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 113ee05e8..b5d43728b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CheckReturnValue; -import javax.annotation.concurrent.ThreadSafe; +import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; @@ -57,7 +57,7 @@ * {@code ProgramPlanner} resolves functions, types, and identifiers at plan time given a * parsed-only or a type-checked expression. */ -@ThreadSafe +@Immutable @Internal public final class ProgramPlanner { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java index a913849f6..54eb26f21 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.GlobalResolver; @@ -40,7 +41,9 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { } // TODO: Handle unknowns - + if (obj instanceof CelValue) { + obj = celValueConverter.unwrap((CelValue) obj); + } return obj; } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index ed76fdbe7..30691fa4a 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -19,8 +19,8 @@ java_library( # keep sorted exclude = [ "CelLiteInterpreterTest.java", - "CelValueInterpreterTest.java", "InterpreterTest.java", + "PlannerInterpreterTest.java", ] + ANDROID_TESTS, ), deps = [ @@ -125,16 +125,17 @@ java_library( ) java_library( - name = "cel_value_interpreter_test", + name = "planner_interpreter_test", testonly = 1, srcs = [ - "CelValueInterpreterTest.java", + "PlannerInterpreterTest.java", ], deps = [ - # "//java/com/google/testing/testsize:annotations", + "//extensions", + "//runtime:runtime_planner_impl", "//testing:base_interpreter_test", - "@maven//:junit_junit", "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", ], ) @@ -203,8 +204,8 @@ junit4_test_suites( src_dir = "src/test/java", deps = [ ":cel_lite_interpreter_test", - ":cel_value_interpreter_test", ":interpreter_test", + ":planner_interpreter_test", ":tests", ], ) diff --git a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java deleted file mode 100644 index ad3fae082..000000000 --- a/runtime/src/test/java/dev/cel/runtime/CelValueInterpreterTest.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2023 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.runtime; - -import com.google.testing.junit.testparameterinjector.TestParameterInjector; -// import com.google.testing.testsize.MediumTest; -import dev.cel.testing.BaseInterpreterTest; -import org.junit.runner.RunWith; - -/** Tests for {@link Interpreter} and related functionality using {@code CelValue}. */ -// @MediumTest -@RunWith(TestParameterInjector.class) -public class CelValueInterpreterTest extends BaseInterpreterTest { - - public CelValueInterpreterTest() { - super(newBaseCelOptions().toBuilder().enableCelValue(true).build()); - } - - @Override - public void wrappers() throws Exception { - // Field selection on repeated wrappers broken. - // This test along with CelValue adapter will be removed in a separate CL - skipBaselineVerification(); - } -} diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java new file mode 100644 index 000000000..4e3a00a34 --- /dev/null +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -0,0 +1,60 @@ +// 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.runtime; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.extensions.CelExtensions; +import dev.cel.testing.BaseInterpreterTest; +import org.junit.runner.RunWith; + +/** Interpreter tests using ProgramPlanner */ +@RunWith(TestParameterInjector.class) +public class PlannerInterpreterTest extends BaseInterpreterTest { + + public PlannerInterpreterTest() { + super( + CelRuntimeImpl.newBuilder() + .addLateBoundFunctions("record") + // CEL-Internal-2 + .setOptions(newBaseCelOptions()) + .addLibraries(CelExtensions.optional()) + .addFileTypes(TEST_FILE_DESCRIPTORS) + .build()); + } + + @Override + public void unknownField() { + // TODO: Unknown support not implemented yet + skipBaselineVerification(); + } + + @Override + public void unknownResultSet() { + // TODO: Unknown support not implemented yet + skipBaselineVerification(); + } + + @Override + public void typeComparisons() { + // TODO: type() standard function needs to be implemented first. + skipBaselineVerification(); + } + + @Override + public void optional_errors() { + // TODO: Fix error message for function dispatch failures + skipBaselineVerification(); + } +} From 026cae62c0dcfe2afbbc61fb4475fc3f867ed662 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 3 Feb 2026 13:33:53 -0800 Subject: [PATCH 067/100] Use mutable expressions for AST rewrites during type-check PiperOrigin-RevId: 865023381 --- .../src/main/java/dev/cel/checker/BUILD.bazel | 2 + .../src/main/java/dev/cel/checker/Env.java | 26 +- .../java/dev/cel/checker/ExprChecker.java | 360 ++++++------------ .../src/main/java/dev/cel/common/BUILD.bazel | 1 + .../java/dev/cel/common/CelMutableAst.java | 13 +- .../java/dev/cel/common/CelMutableSource.java | 37 +- .../main/java/dev/cel/common/CelSource.java | 9 +- 7 files changed, 184 insertions(+), 264 deletions(-) diff --git a/checker/src/main/java/dev/cel/checker/BUILD.bazel b/checker/src/main/java/dev/cel/checker/BUILD.bazel index 6c486bd92..8c6a8b89d 100644 --- a/checker/src/main/java/dev/cel/checker/BUILD.bazel +++ b/checker/src/main/java/dev/cel/checker/BUILD.bazel @@ -179,12 +179,14 @@ java_library( "//common:cel_ast", "//common:compiler_common", "//common:container", + "//common:mutable_ast", "//common:operator", "//common:options", "//common:proto_ast", "//common/annotations", "//common/ast", "//common/ast:expr_converter", + "//common/ast:mutable_expr", "//common/internal:errors", "//common/internal:file_descriptor_converter", "//common/types", diff --git a/checker/src/main/java/dev/cel/checker/Env.java b/checker/src/main/java/dev/cel/checker/Env.java index 7029781e5..75ed7cf1f 100644 --- a/checker/src/main/java/dev/cel/checker/Env.java +++ b/checker/src/main/java/dev/cel/checker/Env.java @@ -38,6 +38,7 @@ import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExprConverter; +import dev.cel.common.ast.CelMutableExpr; import dev.cel.common.ast.CelReference; import dev.cel.common.internal.Errors; import dev.cel.common.types.CelKind; @@ -288,28 +289,31 @@ public Map getTypeMap() { * Returns the type associated with an expression by expression id. It's an error to call this * method if the type is not present. * - * @deprecated Use {@link #getType(CelExpr)} instead. + * @deprecated Do not use. Migrate to CEL-Java fluent APIs. */ @Deprecated public Type getType(Expr expr) { Preconditions.checkNotNull(expr); - return CelProtoTypes.celTypeToType(getType(CelExprConverter.fromExpr(expr))); + CelExpr celExpr = CelExprConverter.fromExpr(expr); + CelType celType = + Preconditions.checkNotNull(typeMap.get(celExpr.id()), "expression has no type"); + return CelProtoTypes.celTypeToType(celType); } /** - * Returns the type associated with an expression by expression id. It's an error to call this - * method if the type is not present. + * Returns the type associated with a mutable expression by expression id. It's an error to call + * this method if the type is not present. */ - public CelType getType(CelExpr expr) { + CelType getType(CelMutableExpr expr) { return Preconditions.checkNotNull(typeMap.get(expr.id()), "expression has no type"); } /** - * Sets the type associated with an expression by id. It's an error if the type is already set and - * is different than the provided one. Returns the expression parameter. + * Sets the type associated with a mutable expression by id. It's an error if the type is already + * set and is different than the provided one. Returns the expression parameter. */ @CanIgnoreReturnValue - public CelExpr setType(CelExpr expr, CelType type) { + CelMutableExpr setType(CelMutableExpr expr, CelType type) { CelType oldType = typeMap.put(expr.id(), type); Preconditions.checkState( oldType == null || oldType.equals(type), @@ -320,10 +324,10 @@ public CelExpr setType(CelExpr expr, CelType type) { } /** - * Sets the reference associated with an expression. It's an error if the reference is already set - * and is different. + * Sets the reference associated with a mutable expression. It's an error if the reference is + * already set and is different. */ - public void setRef(CelExpr expr, CelReference reference) { + void setRef(CelMutableExpr expr, CelReference reference) { CelReference oldReference = referenceMap.put(expr.id(), reference); Preconditions.checkState( oldReference == null || oldReference.equals(reference), diff --git a/checker/src/main/java/dev/cel/checker/ExprChecker.java b/checker/src/main/java/dev/cel/checker/ExprChecker.java index 37b692ecf..b6885d1aa 100644 --- a/checker/src/main/java/dev/cel/checker/ExprChecker.java +++ b/checker/src/main/java/dev/cel/checker/ExprChecker.java @@ -29,12 +29,21 @@ import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; +import dev.cel.common.CelMutableAst; import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelProtoAbstractSyntaxTree; import dev.cel.common.Operator; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelMutableExpr; +import dev.cel.common.ast.CelMutableExpr.CelMutableCall; +import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension; +import dev.cel.common.ast.CelMutableExpr.CelMutableIdent; +import dev.cel.common.ast.CelMutableExpr.CelMutableList; +import dev.cel.common.ast.CelMutableExpr.CelMutableMap; +import dev.cel.common.ast.CelMutableExpr.CelMutableSelect; +import dev.cel.common.ast.CelMutableExpr.CelMutableStruct; import dev.cel.common.ast.CelReference; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelProtoTypes; @@ -130,16 +139,20 @@ public static CelAbstractSyntaxTree typecheck( env.enableCompileTimeOverloadResolution(), env.enableHomogeneousLiterals(), env.enableNamespacedDeclarations()); - CelExpr expr = checker.visit(ast.getExpr()); + + CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast); + checker.visit(mutableAst.expr()); if (expectedResultType.isPresent()) { - checker.assertType(expr, expectedResultType.get()); + checker.assertType(mutableAst.expr(), expectedResultType.get()); } // Walk over the final type map substituting any type parameters either by their bound value or // by DYN. Map typeMap = Maps.transformValues(env.getTypeMap(), checker.inferenceContext::finalize); - return CelAbstractSyntaxTree.newCheckedAst(expr, ast.getSource(), env.getRefMap(), typeMap); + CelAbstractSyntaxTree parsedAst = mutableAst.toParsedAst(/* retainSourcePositions= */ true); + return CelAbstractSyntaxTree.newCheckedAst( + parsedAst.getExpr(), parsedAst.getSource(), env.getRefMap(), typeMap); } private final Env env; @@ -170,32 +183,38 @@ private ExprChecker( } /** Visit the {@code expr} value, routing to overloads based on the kind of expression. */ - @CheckReturnValue - public CelExpr visit(CelExpr expr) { - switch (expr.exprKind().getKind()) { + public void visit(CelMutableExpr expr) { + switch (expr.getKind()) { case CONSTANT: - return visit(expr, expr.constant()); + visit(expr, expr.constant()); + break; case IDENT: - return visit(expr, expr.ident()); + visit(expr, expr.ident()); + break; case SELECT: - return visit(expr, expr.select()); + visit(expr, expr.select()); + break; case CALL: - return visit(expr, expr.call()); + visit(expr, expr.call()); + break; case LIST: - return visit(expr, expr.list()); + visit(expr, expr.list()); + break; case STRUCT: - return visit(expr, expr.struct()); + visit(expr, expr.struct()); + break; case MAP: - return visit(expr, expr.map()); + visit(expr, expr.map()); + break; case COMPREHENSION: - return visit(expr, expr.comprehension()); + visit(expr, expr.comprehension()); + break; default: throw new IllegalArgumentException("unexpected expr kind"); } } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelConstant constant) { + private void visit(CelMutableExpr expr, CelConstant constant) { switch (constant.getKind()) { case INT64_VALUE: env.setType(expr, SimpleType.INT); @@ -227,33 +246,29 @@ private CelExpr visit(CelExpr expr, CelConstant constant) { default: throw new IllegalArgumentException("unexpected constant case: " + constant.getKind()); } - return expr; } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelExpr.CelIdent ident) { + private void visit(CelMutableExpr expr, CelMutableIdent ident) { CelIdentDecl decl = env.lookupIdent(expr.id(), getPosition(expr), container, ident.name()); checkNotNull(decl); if (decl.equals(Env.ERROR_IDENT_DECL)) { // error reported env.setType(expr, SimpleType.ERROR); env.setRef(expr, makeReference(decl.name(), decl)); - return expr; + return; } String refName = maybeDisambiguate(ident.name(), decl.name()); if (!refName.equals(ident.name())) { // Overwrite the identifier with its fully qualified name. - expr = replaceIdentSubtree(expr, refName); + expr.setIdent(CelMutableIdent.create(refName)); } env.setType(expr, decl.type()); env.setRef(expr, makeReference(refName, decl)); - return expr; } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelExpr.CelSelect select) { + private void visit(CelMutableExpr expr, CelMutableSelect select) { // Before traversing down the tree, try to interpret as qualified name. String qname = asQualifiedName(expr); if (qname != null) { @@ -268,44 +283,34 @@ private CelExpr visit(CelExpr expr, CelExpr.CelSelect select) { if (namespacedDeclarations) { // Rewrite the node to be a variable reference to the resolved fully-qualified // variable name. - expr = replaceIdentSubtree(expr, refName); + expr.setIdent(CelMutableIdent.create(refName)); } env.setType(expr, decl.type()); env.setRef(expr, makeReference(refName, decl)); } - return expr; + return; } } // Interpret as field selection, first traversing down the operand. - CelExpr visitedOperand = visit(select.operand()); - if (namespacedDeclarations && !select.operand().equals(visitedOperand)) { - // Subtree has been rewritten. Replace the operand. - expr = replaceSelectOperandSubtree(expr, visitedOperand); - } - CelType resultType = visitSelectField(expr, visitedOperand, select.field(), false); + visit(select.operand()); + + CelType resultType = visitSelectField(expr, select.operand(), select.field(), false); if (select.testOnly()) { resultType = SimpleType.BOOL; } env.setType(expr, resultType); - return expr; } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelExpr.CelCall call) { + private void visit(CelMutableExpr expr, CelMutableCall call) { String functionName = call.function(); if (Operator.OPTIONAL_SELECT.getFunction().equals(functionName)) { - return visitOptionalCall(expr, call); + visitOptionalCall(expr, call); + return; } // Traverse arguments. - ImmutableList argsList = call.args(); - for (int i = 0; i < argsList.size(); i++) { - CelExpr arg = argsList.get(i); - CelExpr visitedArg = visit(arg); - if (namespacedDeclarations && !visitedArg.equals(arg)) { - // Argument has been overwritten. - expr = replaceCallArgumentSubtree(expr, visitedArg, i); - } + for (CelMutableExpr arg : call.args()) { + visit(arg); } int position = getPosition(expr); @@ -319,7 +324,7 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCall call) { if (!decl.name().equals(call.function())) { if (namespacedDeclarations) { // Overwrite the function name with its fully qualified resolved name. - expr = replaceCallSubtree(expr, decl.name()); + expr.setCall(CelMutableCall.create(decl.name(), call.args())); } } } else { @@ -334,17 +339,12 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCall call) { // The function name is namespaced and so preserving the target operand would // be an inaccurate representation of the desired evaluation behavior. // Overwrite with fully-qualified resolved function name sans receiver target. - expr = replaceCallSubtree(expr, decl.name()); + expr.setCall(CelMutableCall.create(decl.name(), call.args())); } } else { // Regular instance call. - CelExpr target = call.target().get(); - CelExpr visitedTargetExpr = visit(target); - if (namespacedDeclarations && !visitedTargetExpr.equals(target)) { - // Visiting target contained a namespaced function. Rewrite the call expression here by - // setting the target to the new subtree. - expr = replaceCallSubtree(expr, visitedTargetExpr); - } + CelMutableExpr target = call.target().get(); + visit(target); resolution = resolveOverload( expr.id(), @@ -357,21 +357,15 @@ private CelExpr visit(CelExpr expr, CelExpr.CelCall call) { env.setType(expr, resolution.type()); env.setRef(expr, resolution.reference()); - - return expr; } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelExpr.CelStruct struct) { + private void visit(CelMutableExpr expr, CelMutableStruct struct) { // Determine the type of the message. CelType messageType = SimpleType.ERROR; CelIdentDecl decl = env.lookupIdent(expr.id(), getPosition(expr), container, struct.messageName()); if (!struct.messageName().equals(decl.name())) { - expr = - expr.toBuilder() - .setStruct(struct.toBuilder().setMessageName(decl.name()).build()) - .build(); + struct.setMessageName(decl.name()); } env.setRef(expr, CelReference.newBuilder().setName(decl.name()).build()); @@ -401,26 +395,20 @@ private CelExpr visit(CelExpr expr, CelExpr.CelStruct struct) { } // Check the field initializers. - ImmutableList entriesList = struct.entries(); - for (int i = 0; i < entriesList.size(); i++) { - CelExpr.CelStruct.Entry entry = entriesList.get(i); - CelExpr visitedValueExpr = visit(entry.value()); - if (namespacedDeclarations && !visitedValueExpr.equals(entry.value())) { - // Subtree has been rewritten. Replace the struct value. - expr = replaceStructEntryValueSubtree(expr, visitedValueExpr, i); - } + List entriesList = struct.entries(); + for (CelMutableStruct.Entry entry : entriesList) { + CelMutableExpr value = entry.value(); + visit(value); + CelType fieldType = getFieldType(entry.id(), getPosition(entry), messageType, entry.fieldKey()).celType(); - CelType valueType = env.getType(visitedValueExpr); + CelType valueType = env.getType(value); if (entry.optionalEntry()) { if (valueType instanceof OptionalType) { valueType = unwrapOptional(valueType); } else { assertIsAssignable( - visitedValueExpr.id(), - getPosition(visitedValueExpr), - valueType, - OptionalType.create(valueType)); + value.id(), getPosition(value), valueType, OptionalType.create(valueType)); } } if (!inferenceContext.isAssignable(fieldType, valueType)) { @@ -433,48 +421,32 @@ private CelExpr visit(CelExpr expr, CelExpr.CelStruct struct) { CelTypes.format(valueType)); } } - return expr; } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelExpr.CelMap map) { + private void visit(CelMutableExpr expr, CelMutableMap map) { CelType mapKeyType = null; CelType mapValueType = null; - ImmutableList entriesList = map.entries(); - for (int i = 0; i < entriesList.size(); i++) { - CelExpr.CelMap.Entry entry = entriesList.get(i); - CelExpr visitedMapKeyExpr = visit(entry.key()); - if (namespacedDeclarations && !visitedMapKeyExpr.equals(entry.key())) { - // Subtree has been rewritten. Replace the map key. - expr = replaceMapEntryKeySubtree(expr, visitedMapKeyExpr, i); - } - mapKeyType = - joinTypes( - visitedMapKeyExpr.id(), - getPosition(visitedMapKeyExpr), - mapKeyType, - env.getType(visitedMapKeyExpr)); - - CelExpr visitedValueExpr = visit(entry.value()); - if (namespacedDeclarations && !visitedValueExpr.equals(entry.value())) { - // Subtree has been rewritten. Replace the map value. - expr = replaceMapEntryValueSubtree(expr, visitedValueExpr, i); - } - CelType valueType = env.getType(visitedValueExpr); + List entriesList = map.entries(); + for (CelMutableMap.Entry entry : entriesList) { + CelMutableExpr key = entry.key(); + visit(key); + + mapKeyType = joinTypes(key.id(), getPosition(key), mapKeyType, env.getType(key)); + + CelMutableExpr value = entry.value(); + visit(value); + + CelType valueType = env.getType(value); if (entry.optionalEntry()) { if (valueType instanceof OptionalType) { valueType = unwrapOptional(valueType); } else { assertIsAssignable( - visitedValueExpr.id(), - getPosition(visitedValueExpr), - valueType, - OptionalType.create(valueType)); + value.id(), getPosition(value), valueType, OptionalType.create(valueType)); } } - mapValueType = - joinTypes(visitedValueExpr.id(), getPosition(visitedValueExpr), mapValueType, valueType); + mapValueType = joinTypes(value.id(), getPosition(value), mapValueType, valueType); } if (mapKeyType == null) { // If the map is empty, assign free type variables to key and value type. @@ -482,46 +454,39 @@ private CelExpr visit(CelExpr expr, CelExpr.CelMap map) { mapValueType = inferenceContext.newTypeVar("value"); } env.setType(expr, MapType.create(mapKeyType, mapValueType)); - return expr; } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelExpr.CelList list) { + private void visit(CelMutableExpr expr, CelMutableList list) { CelType elemsType = null; - ImmutableList elementsList = list.elements(); + List elementsList = list.elements(); HashSet optionalIndices = new HashSet<>(list.optionalIndices()); for (int i = 0; i < elementsList.size(); i++) { - CelExpr visitedElem = visit(elementsList.get(i)); - if (namespacedDeclarations && !visitedElem.equals(elementsList.get(i))) { - // Subtree has been rewritten. Replace the list element - expr = replaceListElementSubtree(expr, visitedElem, i); - } - CelType elemType = env.getType(visitedElem); + CelMutableExpr elem = elementsList.get(i); + visit(elem); + + CelType elemType = env.getType(elem); if (optionalIndices.contains(i)) { if (elemType instanceof OptionalType) { elemType = unwrapOptional(elemType); } else { - assertIsAssignable( - visitedElem.id(), getPosition(visitedElem), elemType, OptionalType.create(elemType)); + assertIsAssignable(elem.id(), getPosition(elem), elemType, OptionalType.create(elemType)); } } - elemsType = joinTypes(visitedElem.id(), getPosition(visitedElem), elemsType, elemType); + elemsType = joinTypes(elem.id(), getPosition(elem), elemsType, elemType); } if (elemsType == null) { // If the list is empty, assign free type var to elem type. elemsType = inferenceContext.newTypeVar("elem"); } env.setType(expr, ListType.create(elemsType)); - return expr; } - @CheckReturnValue - private CelExpr visit(CelExpr expr, CelExpr.CelComprehension compre) { - CelExpr visitedRange = visit(compre.iterRange()); - CelExpr visitedInit = visit(compre.accuInit()); - CelType accuType = env.getType(visitedInit); - CelType rangeType = inferenceContext.specialize(env.getType(visitedRange)); + private void visit(CelMutableExpr expr, CelMutableComprehension compre) { + visit(compre.iterRange()); + visit(compre.accuInit()); + CelType accuType = env.getType(compre.accuInit()); + CelType rangeType = inferenceContext.specialize(env.getType(compre.iterRange())); CelType varType; CelType varType2 = null; switch (rangeType.kind()) { @@ -556,7 +521,7 @@ private CelExpr visit(CelExpr expr, CelExpr.CelComprehension compre) { default: env.reportError( expr.id(), - getPosition(visitedRange), + getPosition(compre.iterRange()), "expression of type '%s' cannot be range of a comprehension " + "(must be list, map, or dynamic)", CelTypes.format(rangeType)); @@ -574,30 +539,16 @@ private CelExpr visit(CelExpr expr, CelExpr.CelComprehension compre) { if (!Strings.isNullOrEmpty(compre.iterVar2())) { env.add(CelIdentDecl.newIdentDeclaration(compre.iterVar2(), varType2)); } - CelExpr condition = visit(compre.loopCondition()); - assertType(condition, SimpleType.BOOL); - CelExpr visitedStep = visit(compre.loopStep()); - assertType(visitedStep, accuType); + visit(compre.loopCondition()); + assertType(compre.loopCondition(), SimpleType.BOOL); + visit(compre.loopStep()); + assertType(compre.loopStep(), accuType); // Forget iteration variable, as result expression must only depend on accu. env.exitScope(); - CelExpr visitedResult = visit(compre.result()); + visit(compre.result()); env.exitScope(); - if (namespacedDeclarations) { - if (!visitedRange.equals(compre.iterRange())) { - expr = replaceComprehensionRangeSubtree(expr, visitedRange); - } - if (!visitedInit.equals(compre.accuInit())) { - expr = replaceComprehensionAccuInitSubtree(expr, visitedInit); - } - if (!visitedStep.equals(compre.loopStep())) { - expr = replaceComprehensionStepSubtree(expr, visitedStep); - } - if (!visitedResult.equals(compre.result())) { - expr = replaceComprehensionResultSubtree(expr, visitedResult); - } - } - env.setType(expr, inferenceContext.specialize(env.getType(visitedResult))); - return expr; + + env.setType(expr, inferenceContext.specialize(env.getType(compre.result()))); } private CelReference makeReference(String name, CelIdentDecl decl) { @@ -630,8 +581,8 @@ private OverloadResolution resolveOverload( long callExprId, int position, @Nullable CelFunctionDecl function, - @Nullable CelExpr target, - List args) { + @Nullable CelMutableExpr target, + List args) { if (function == null || function.equals(Env.ERROR_FUNCTION_DECL)) { // Error reported, just return error value. return OverloadResolution.of(CelReference.newBuilder().build(), SimpleType.ERROR); @@ -640,7 +591,7 @@ private OverloadResolution resolveOverload( if (target != null) { argTypes.add(env.getType(target)); } - for (CelExpr arg : args) { + for (CelMutableExpr arg : args) { argTypes.add(env.getType(arg)); } CelType resultType = null; // For most common result type. @@ -716,7 +667,7 @@ private OverloadResolution resolveOverload( // Return value from visit is not needed as the subtree is not rewritten here. @SuppressWarnings("CheckReturnValue") private CelType visitSelectField( - CelExpr expr, CelExpr operand, String field, boolean isOptional) { + CelMutableExpr expr, CelMutableExpr operand, String field, boolean isOptional) { CelType operandType = inferenceContext.specialize(env.getType(operand)); CelType resultType = SimpleType.ERROR; @@ -763,25 +714,21 @@ private CelType visitSelectField( return resultType; } - private CelExpr visitOptionalCall(CelExpr expr, CelExpr.CelCall call) { - CelExpr operand = call.args().get(0); - CelExpr field = call.args().get(1); - if (!field.exprKind().getKind().equals(CelExpr.ExprKind.Kind.CONSTANT) - || field.constant().getKind() != CelConstant.Kind.STRING_VALUE) { + private void visitOptionalCall(CelMutableExpr expr, CelMutableCall call) { + CelMutableExpr operand = call.args().get(0); + CelMutableExpr field = call.args().get(1); + if (!field.getKind().equals(CelExpr.ExprKind.Kind.CONSTANT) + || !field.constant().getKind().equals(CelConstant.Kind.STRING_VALUE)) { + env.reportError(expr.id(), getPosition(field), "unsupported optional field selection"); - return expr; + return; } - CelExpr visitedOperand = visit(operand); - if (namespacedDeclarations && !operand.equals(visitedOperand)) { - // Subtree has been rewritten. Replace the operand. - expr = replaceCallArgumentSubtree(expr, visitedOperand, 0); - } + visit(operand); + CelType resultType = visitSelectField(expr, operand, field.constant().stringValue(), true); env.setType(expr, resultType); env.setRef(expr, CelReference.newBuilder().addOverloadIds("select_optional_field").build()); - - return expr; } /** @@ -789,8 +736,8 @@ private CelExpr visitOptionalCall(CelExpr expr, CelExpr.CelCall call) { * expression and returns the name they constitute, or null if the expression cannot be * interpreted like this. */ - private @Nullable String asQualifiedName(CelExpr expr) { - switch (expr.exprKind().getKind()) { + private @Nullable String asQualifiedName(CelMutableExpr expr) { + switch (expr.getKind()) { case IDENT: return expr.ident().name(); case SELECT: @@ -862,16 +809,16 @@ private CelType unwrapOptional(CelType type) { return type.parameters().get(0); } - private void assertType(CelExpr expr, CelType type) { + private void assertType(CelMutableExpr expr, CelType type) { assertIsAssignable(expr.id(), getPosition(expr), env.getType(expr), type); } - private int getPosition(CelExpr expr) { + private int getPosition(CelMutableExpr expr) { Integer pos = positionMap.get(expr.id()); return pos == null ? 0 : pos; } - private int getPosition(CelExpr.CelStruct.Entry entry) { + private int getPosition(CelMutableStruct.Entry entry) { Integer pos = positionMap.get(entry.id()); return pos == null ? 0 : pos; } @@ -894,81 +841,4 @@ public static OverloadResolution of(CelReference reference, CelType type) { /** Helper object to represent a {@link TypeProvider.FieldType} lookup failure. */ private static final TypeProvider.FieldType ERROR = TypeProvider.FieldType.of(Types.ERROR); - - private static CelExpr replaceIdentSubtree(CelExpr expr, String name) { - CelExpr.CelIdent newIdent = CelExpr.CelIdent.newBuilder().setName(name).build(); - return expr.toBuilder().setIdent(newIdent).build(); - } - - private static CelExpr replaceSelectOperandSubtree(CelExpr expr, CelExpr operand) { - CelExpr.CelSelect newSelect = expr.select().toBuilder().setOperand(operand).build(); - return expr.toBuilder().setSelect(newSelect).build(); - } - - private static CelExpr replaceCallArgumentSubtree(CelExpr expr, CelExpr newArg, int index) { - CelExpr.CelCall newCall = expr.call().toBuilder().setArg(index, newArg).build(); - return expr.toBuilder().setCall(newCall).build(); - } - - private static CelExpr replaceCallSubtree(CelExpr expr, String functionName) { - CelExpr.CelCall newCall = - expr.call().toBuilder().setFunction(functionName).clearTarget().build(); - return expr.toBuilder().setCall(newCall).build(); - } - - private static CelExpr replaceCallSubtree(CelExpr expr, CelExpr target) { - CelExpr.CelCall newCall = expr.call().toBuilder().setTarget(target).build(); - return expr.toBuilder().setCall(newCall).build(); - } - - private static CelExpr replaceListElementSubtree(CelExpr expr, CelExpr element, int index) { - CelExpr.CelList newList = expr.list().toBuilder().setElement(index, element).build(); - return expr.toBuilder().setList(newList).build(); - } - - private static CelExpr replaceStructEntryValueSubtree(CelExpr expr, CelExpr newValue, int index) { - CelExpr.CelStruct struct = expr.struct(); - CelExpr.CelStruct.Entry newEntry = - struct.entries().get(index).toBuilder().setValue(newValue).build(); - struct = struct.toBuilder().setEntry(index, newEntry).build(); - return expr.toBuilder().setStruct(struct).build(); - } - - private static CelExpr replaceMapEntryKeySubtree(CelExpr expr, CelExpr newKey, int index) { - CelExpr.CelMap map = expr.map(); - CelExpr.CelMap.Entry newEntry = map.entries().get(index).toBuilder().setKey(newKey).build(); - map = map.toBuilder().setEntry(index, newEntry).build(); - return expr.toBuilder().setMap(map).build(); - } - - private static CelExpr replaceMapEntryValueSubtree(CelExpr expr, CelExpr newValue, int index) { - CelExpr.CelMap map = expr.map(); - CelExpr.CelMap.Entry newEntry = map.entries().get(index).toBuilder().setValue(newValue).build(); - map = map.toBuilder().setEntry(index, newEntry).build(); - return expr.toBuilder().setMap(map).build(); - } - - private static CelExpr replaceComprehensionAccuInitSubtree(CelExpr expr, CelExpr newAccuInit) { - CelExpr.CelComprehension newComprehension = - expr.comprehension().toBuilder().setAccuInit(newAccuInit).build(); - return expr.toBuilder().setComprehension(newComprehension).build(); - } - - private static CelExpr replaceComprehensionRangeSubtree(CelExpr expr, CelExpr newRange) { - CelExpr.CelComprehension newComprehension = - expr.comprehension().toBuilder().setIterRange(newRange).build(); - return expr.toBuilder().setComprehension(newComprehension).build(); - } - - private static CelExpr replaceComprehensionStepSubtree(CelExpr expr, CelExpr newStep) { - CelExpr.CelComprehension newComprehension = - expr.comprehension().toBuilder().setLoopStep(newStep).build(); - return expr.toBuilder().setComprehension(newComprehension).build(); - } - - private static CelExpr replaceComprehensionResultSubtree(CelExpr expr, CelExpr newResult) { - CelExpr.CelComprehension newComprehension = - expr.comprehension().toBuilder().setResult(newResult).build(); - return expr.toBuilder().setComprehension(newComprehension).build(); - } } diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index ba223d213..62ba8db78 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -194,6 +194,7 @@ java_library( deps = [ ":cel_source", "//common/ast:mutable_expr", + "//common/internal", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], diff --git a/common/src/main/java/dev/cel/common/CelMutableAst.java b/common/src/main/java/dev/cel/common/CelMutableAst.java index fd3fe28f8..dd57d8b4e 100644 --- a/common/src/main/java/dev/cel/common/CelMutableAst.java +++ b/common/src/main/java/dev/cel/common/CelMutableAst.java @@ -70,8 +70,19 @@ public Optional getType(long exprId) { /** Converts this mutable AST into a parsed {@link CelAbstractSyntaxTree}. */ public CelAbstractSyntaxTree toParsedAst() { + return toParsedAst(false); + } + + /** + * Converts this mutable AST into a parsed {@link CelAbstractSyntaxTree}. + * + * @param retainSourcePositions If true, the source positions (line offsets, code points) will be + * retained in the resulting AST. If false, they will be scrubbed. + */ + public CelAbstractSyntaxTree toParsedAst(boolean retainSourcePositions) { return CelAbstractSyntaxTree.newParsedAst( - CelMutableExprConverter.fromMutableExpr(mutatedExpr), source.toCelSource()); + CelMutableExprConverter.fromMutableExpr(mutatedExpr), + source.toCelSource(retainSourcePositions)); } /** diff --git a/common/src/main/java/dev/cel/common/CelMutableSource.java b/common/src/main/java/dev/cel/common/CelMutableSource.java index 459042c6d..cf85f5af6 100644 --- a/common/src/main/java/dev/cel/common/CelMutableSource.java +++ b/common/src/main/java/dev/cel/common/CelMutableSource.java @@ -17,10 +17,12 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableMap.toImmutableMap; +import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; import dev.cel.common.CelSource.Extension; import dev.cel.common.ast.CelMutableExpr; import dev.cel.common.ast.CelMutableExprConverter; +import dev.cel.common.internal.CelCodePointArray; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -38,6 +40,9 @@ public final class CelMutableSource { private String description; private final Map macroCalls; private final Set extensions; + private final CelCodePointArray codePoints; + private final ImmutableList lineOffsets; + private final Map positions; @CanIgnoreReturnValue public CelMutableSource addMacroCalls(long exprId, CelMutableExpr expr) { @@ -88,8 +93,13 @@ public Set getExtensions() { return extensions; } - public CelSource toCelSource() { - return CelSource.newBuilder() + public CelSource toCelSource(boolean retainSourcePositions) { + CelSource.Builder builder = + retainSourcePositions + ? CelSource.newBuilder(codePoints, lineOffsets).addPositionsMap(positions) + : CelSource.newBuilder(); + + return builder .setDescription(description) .addAllExtensions(extensions) .addAllMacroCalls( @@ -101,7 +111,13 @@ public CelSource toCelSource() { } public static CelMutableSource newInstance() { - return new CelMutableSource("", new HashMap<>(), new HashSet<>()); + return new CelMutableSource( + "", + new HashMap<>(), + new HashSet<>(), + CelCodePointArray.fromString(""), + ImmutableList.of(), + new HashMap<>()); } public static CelMutableSource fromCelSource(CelSource source) { @@ -117,13 +133,24 @@ public static CelMutableSource fromCelSource(CelSource source) { "Unexpected source collision at ID: " + prev.id()); }, HashMap::new)), - source.getExtensions()); + source.getExtensions(), + source.getContent(), + source.getLineOffsets(), + source.getPositionsMap()); } CelMutableSource( - String description, Map macroCalls, Set extensions) { + String description, + Map macroCalls, + Set extensions, + CelCodePointArray codePoints, + ImmutableList lineOffsets, + Map positions) { this.description = checkNotNull(description); this.macroCalls = checkNotNull(macroCalls); this.extensions = checkNotNull(extensions); + this.codePoints = checkNotNull(codePoints); + this.lineOffsets = checkNotNull(lineOffsets); + this.positions = checkNotNull(positions); } } diff --git a/common/src/main/java/dev/cel/common/CelSource.java b/common/src/main/java/dev/cel/common/CelSource.java index 2678a0a2c..d64049f61 100644 --- a/common/src/main/java/dev/cel/common/CelSource.java +++ b/common/src/main/java/dev/cel/common/CelSource.java @@ -148,8 +148,13 @@ public static Builder newBuilder(String text) { return newBuilder(CelCodePointArray.fromString(text)); } - public static Builder newBuilder(CelCodePointArray codePointArray) { - return new Builder(codePointArray, codePointArray.lineOffsets()); + public static Builder newBuilder(CelCodePointArray codePoints) { + return newBuilder(codePoints, codePoints.lineOffsets()); + } + + public static Builder newBuilder( + CelCodePointArray codePoints, ImmutableList lineOffsets) { + return new Builder(codePoints, lineOffsets); } /** Builder for {@link CelSource}. */ From 72a12078be40e8b1099b463e6f94558832719ccc Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 5 Feb 2026 10:02:35 -0800 Subject: [PATCH 068/100] Prevent non-foldable functions from being folded in comprehensions PiperOrigin-RevId: 865997662 --- .../optimizers/ConstantFoldingOptimizer.java | 2 +- .../optimizers/ConstantFoldingOptimizerTest.java | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) 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 ca1744c88..ada73ce56 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -201,7 +201,7 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) { CelNavigableMutableExpr operand = navigableExpr.children().collect(onlyElement()); return areChildrenArgConstant(operand); case COMPREHENSION: - return !isNestedComprehension(navigableExpr); + return !isNestedComprehension(navigableExpr) && containsFoldableFunctionOnly(navigableExpr); default: return false; } diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java index 1a3fac852..e259a7a35 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -47,19 +47,24 @@ @RunWith(TestParameterInjector.class) public class ConstantFoldingOptimizerTest { private static final CelOptions CEL_OPTIONS = - CelOptions.current() - .enableTimestampEpoch(true) - .build(); + CelOptions.current().populateMacroCalls(true).enableTimestampEpoch(true).build(); private static final Cel CEL = CelFactory.standardCelBuilder() .addVar("x", SimpleType.DYN) .addVar("y", SimpleType.DYN) .addVar("list_var", ListType.create(SimpleType.STRING)) .addVar("map_var", MapType.create(SimpleType.STRING, SimpleType.STRING)) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "get_true", - CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL))) + CelOverloadDecl.newGlobalOverload("get_true_overload", SimpleType.BOOL)), + CelFunctionDecl.newFunctionDeclaration( + "get_list", + CelOverloadDecl.newGlobalOverload( + "get_list_overload", + ListType.create(SimpleType.INT), + ListType.create(SimpleType.INT)))) .addFunctionBindings( CelFunctionBinding.from("get_true_overload", ImmutableList.of(), unused -> true)) .addMessageTypes(TestAllTypes.getDescriptor()) @@ -371,6 +376,8 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E @TestParameters("{source: 'x == 42'}") @TestParameters("{source: 'timestamp(100)'}") @TestParameters("{source: 'duration(\"1h\")'}") + @TestParameters("{source: '[true].exists(x, x == get_true())'}") + @TestParameters("{source: 'get_list([1, 2]).map(x, x * 2)'}") public void constantFold_noOp(String source) throws Exception { CelAbstractSyntaxTree ast = CEL.compile(source).getAst(); From e36c49febc36bcb16ef4f07274fcc8873c1eee59 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 5 Feb 2026 16:18:47 -0800 Subject: [PATCH 069/100] Add json_name protobuf field option support PiperOrigin-RevId: 866159844 --- .../src/test/java/dev/cel/bundle/BUILD.bazel | 2 +- .../test/java/dev/cel/bundle/CelImplTest.java | 85 ++++++++- .../src/main/java/dev/cel/checker/BUILD.bazel | 1 + .../dev/cel/checker/CelCheckerLegacyImpl.java | 9 +- .../java/dev/cel/checker/ExprChecker.java | 58 +++++- .../main/java/dev/cel/common/CelOptions.java | 13 ++ .../cel/common/types/ProtoMessageType.java | 26 ++- .../types/ProtoMessageTypeProvider.java | 178 ++++++++++++++++-- .../java/dev/cel/common/types/BUILD.bazel | 1 + .../types/ProtoMessageTypeProviderTest.java | 21 +++ .../common/types/ProtoMessageTypeTest.java | 3 +- .../java/dev/cel/runtime/CelRuntimeImpl.java | 9 +- .../runtime/DescriptorMessageProvider.java | 9 + .../dev/cel/runtime/planner/EvalHelpers.java | 2 +- .../src/test/java/dev/cel/runtime/BUILD.bazel | 3 + .../cel/runtime/CelLiteInterpreterTest.java | 27 ++- .../cel/runtime/PlannerInterpreterTest.java | 24 ++- .../src/main/java/dev/cel/testing/BUILD.bazel | 1 + .../dev/cel/testing/BaseInterpreterTest.java | 52 +++-- .../dev/cel/testing/CelBaselineTestCase.java | 11 +- .../test/resources/protos/single_file.proto | 1 + 21 files changed, 455 insertions(+), 81 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel index 26cbe392d..cd33dd67d 100644 --- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel @@ -34,7 +34,6 @@ java_library( "//common:proto_ast", "//common:source_location", "//common/ast", - "//common/internal:proto_time_utils", "//common/resources/testdata/proto3:standalone_global_enum_java_proto", "//common/testing", "//common/types", @@ -55,6 +54,7 @@ java_library( "//runtime:evaluation_listener", "//runtime:function_binding", "//runtime:unknown_attributes", + "//testing/protos:single_file_java_proto", "@cel_spec//proto/cel/expr:checked_java_proto", "@cel_spec//proto/cel/expr:syntax_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 13c3392d3..0708b37c3 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -71,6 +71,7 @@ import dev.cel.common.CelIssue; import dev.cel.common.CelOptions; import dev.cel.common.CelProtoAbstractSyntaxTree; +import dev.cel.common.CelSource.Extension; import dev.cel.common.CelSourceLocation; import dev.cel.common.CelValidationException; import dev.cel.common.CelValidationResult; @@ -112,6 +113,7 @@ import dev.cel.runtime.CelUnknownSet; import dev.cel.runtime.CelVariableResolver; import dev.cel.runtime.UnknownContext; +import dev.cel.testing.testdata.SingleFileProto.SingleFile; import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum; import java.time.Instant; import java.util.ArrayList; @@ -743,7 +745,7 @@ public void program_withThrowingFunction() throws Exception { CelFunctionBinding.from( "throws", ImmutableList.of(), - (args) -> { + (unused) -> { throw new CelEvaluationException("this method always throws"); })) .setResultType(SimpleType.BOOL) @@ -771,7 +773,7 @@ public void program_withThrowingFunctionShortcircuited() throws Exception { CelFunctionBinding.from( "throws", ImmutableList.of(), - (args) -> { + (unused) -> { throw CelEvaluationExceptionBuilder.newBuilder("this method always throws") .setCause(new RuntimeException("reason")) .build(); @@ -1143,7 +1145,7 @@ public void program_customVarResolver() throws Exception { program.eval( (name) -> name.equals("variable") ? Optional.of("hello") : Optional.empty())) .isEqualTo(true); - assertThat(program.eval((name) -> Optional.of(""))).isEqualTo(false); + assertThat(program.eval((unused) -> Optional.of(""))).isEqualTo(false); } @Test @@ -2193,6 +2195,83 @@ public void toBuilder_isImmutable() { assertThat(newRuntimeBuilder).isNotEqualTo(celImpl.toRuntimeBuilder()); } + @Test + public void eval_withJsonFieldName() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName())) + .addMessageTypes(SingleFile.getDescriptor()) + .setOptions(CelOptions.current().enableJsonFieldNames(true).build()) + .build(); + CelAbstractSyntaxTree ast = cel.compile("file.camelCased").getAst(); + + Object result = + cel.createProgram(ast) + .eval(ImmutableMap.of("file", SingleFile.newBuilder().setSnakeCased("foo").build())); + + assertThat(result).isEqualTo("foo"); + } + + @Test + public void eval_withJsonFieldName_runtimeOptionDisabled_throws() throws Exception { + CelCompiler celCompiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName())) + .addMessageTypes(SingleFile.getDescriptor()) + .setOptions(CelOptions.current().enableJsonFieldNames(true).build()) + .build(); + CelRuntime celRuntime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addMessageTypes(SingleFile.getDescriptor()) + .setOptions(CelOptions.current().enableJsonFieldNames(false).build()) + .build(); + CelAbstractSyntaxTree ast = celCompiler.compile("file.camelCased").getAst(); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, + () -> + celRuntime + .createProgram(ast) + .eval(ImmutableMap.of("file", SingleFile.getDefaultInstance()))); + assertThat(e) + .hasMessageThat() + .contains( + "field 'camelCased' is not declared in message 'dev.cel.testing.testdata.SingleFile"); + } + + @Test + public void compile_withJsonFieldName_astTagged() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName())) + .addMessageTypes(SingleFile.getDescriptor()) + .setOptions(CelOptions.current().enableJsonFieldNames(true).build()) + .build(); + CelAbstractSyntaxTree ast = cel.compile("file.camelCased").getAst(); + + assertThat(ast.getSource().getExtensions()) + .contains( + Extension.create( + "json_name", Extension.Version.of(1L, 1L), Extension.Component.COMPONENT_RUNTIME)); + } + + @Test + public void compile_withJsonFieldName_protoFieldNameComparison_throws() throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addVar("file", StructTypeReference.create(SingleFile.getDescriptor().getFullName())) + .addMessageTypes(SingleFile.getDescriptor()) + .setOptions(CelOptions.current().enableJsonFieldNames(true).build()) + .build(); + + CelValidationException e = + assertThrows( + CelValidationException.class, + () -> cel.compile("file.camelCased == file.snake_cased").getAst()); + assertThat(e).hasMessageThat().contains("undefined field 'snake_cased'"); + } + private static TypeProvider aliasingProvider(ImmutableMap typeAliases) { return new TypeProvider() { @Override diff --git a/checker/src/main/java/dev/cel/checker/BUILD.bazel b/checker/src/main/java/dev/cel/checker/BUILD.bazel index 8c6a8b89d..99d4da586 100644 --- a/checker/src/main/java/dev/cel/checker/BUILD.bazel +++ b/checker/src/main/java/dev/cel/checker/BUILD.bazel @@ -177,6 +177,7 @@ java_library( ":standard_decl", "//:auto_value", "//common:cel_ast", + "//common:cel_source", "//common:compiler_common", "//common:container", "//common:mutable_ast", diff --git a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java index 41d1ca073..df8a82f43 100644 --- a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java +++ b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java @@ -456,9 +456,12 @@ public CelCheckerLegacyImpl build() { } CelTypeProvider messageTypeProvider = - new ProtoMessageTypeProvider( - CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( - fileTypeSet, celOptions.resolveTypeDependencies())); + ProtoMessageTypeProvider.newBuilder() + .setAllowJsonFieldNames(celOptions.enableJsonFieldNames()) + .setResolveTypeDependencies(celOptions.resolveTypeDependencies()) + .addFileDescriptors(fileTypeSet) + .build(); + if (celTypeProvider != null && fileTypeSet.isEmpty()) { messageTypeProvider = celTypeProvider; } else if (celTypeProvider != null) { diff --git a/checker/src/main/java/dev/cel/checker/ExprChecker.java b/checker/src/main/java/dev/cel/checker/ExprChecker.java index b6885d1aa..e3ce99e67 100644 --- a/checker/src/main/java/dev/cel/checker/ExprChecker.java +++ b/checker/src/main/java/dev/cel/checker/ExprChecker.java @@ -32,6 +32,7 @@ import dev.cel.common.CelMutableAst; import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelProtoAbstractSyntaxTree; +import dev.cel.common.CelSource; import dev.cel.common.Operator; import dev.cel.common.annotations.Internal; import dev.cel.common.ast.CelConstant; @@ -52,12 +53,14 @@ import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; import dev.cel.common.types.OptionalType; +import dev.cel.common.types.ProtoMessageType; import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeType; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.jspecify.annotations.Nullable; /** @@ -70,6 +73,11 @@ @Internal @Deprecated public final class ExprChecker { + private static final CelSource.Extension JSON_NAME_EXTENSION = + CelSource.Extension.create( + "json_name", + CelSource.Extension.Version.of(1, 1), + CelSource.Extension.Component.COMPONENT_RUNTIME); /** * Deprecated type-check API. @@ -152,7 +160,10 @@ public static CelAbstractSyntaxTree typecheck( CelAbstractSyntaxTree parsedAst = mutableAst.toParsedAst(/* retainSourcePositions= */ true); return CelAbstractSyntaxTree.newCheckedAst( - parsedAst.getExpr(), parsedAst.getSource(), env.getRefMap(), typeMap); + parsedAst.getExpr(), + parsedAst.getSource().toBuilder().addAllExtensions(checker.extensions).build(), + env.getRefMap(), + typeMap); } private final Env env; @@ -163,6 +174,7 @@ public static CelAbstractSyntaxTree typecheck( private final boolean compileTimeOverloadResolution; private final boolean homogeneousLiterals; private final boolean namespacedDeclarations; + private final Set extensions; private ExprChecker( Env env, @@ -180,6 +192,7 @@ private ExprChecker( this.compileTimeOverloadResolution = compileTimeOverloadResolution; this.homogeneousLiterals = homogeneousLiterals; this.namespacedDeclarations = namespacedDeclarations; + this.extensions = new HashSet<>(); } /** Visit the {@code expr} value, routing to overloads based on the kind of expression. */ @@ -370,13 +383,13 @@ private void visit(CelMutableExpr expr, CelMutableStruct struct) { env.setRef(expr, CelReference.newBuilder().setName(decl.name()).build()); CelType type = decl.type(); - if (type.kind() != CelKind.ERROR) { - if (type.kind() != CelKind.TYPE) { + if (!type.kind().equals(CelKind.ERROR)) { + if (!type.kind().equals(CelKind.TYPE)) { // expected type of types env.reportError(expr.id(), getPosition(expr), "'%s' is not a type", CelTypes.format(type)); } else { messageType = ((TypeType) type).type(); - if (messageType.kind() != CelKind.STRUCT) { + if (!messageType.kind().equals(CelKind.STRUCT)) { env.reportError( expr.id(), getPosition(expr), @@ -677,14 +690,18 @@ private CelType visitSelectField( } if (!Types.isDynOrError(operandType)) { - if (operandType.kind() == CelKind.STRUCT) { + if (operandType.kind().equals(CelKind.STRUCT)) { TypeProvider.FieldType fieldType = getFieldType(expr.id(), getPosition(expr), operandType, field); + ProtoMessageType protoMessageType = resolveProtoMessageType(operandType); + if (protoMessageType != null && protoMessageType.isJsonName(field)) { + extensions.add(JSON_NAME_EXTENSION); + } // Type of the field resultType = fieldType.celType(); - } else if (operandType.kind() == CelKind.MAP) { + } else if (operandType.kind().equals(CelKind.MAP)) { resultType = ((MapType) operandType).valueType(); - } else if (operandType.kind() == CelKind.TYPE_PARAM) { + } else if (operandType.kind().equals(CelKind.TYPE_PARAM)) { // Mark the operand as type DYN to avoid cases where the free type variable might take on // an incorrect type if used in multiple locations. // @@ -714,6 +731,33 @@ private CelType visitSelectField( return resultType; } + private @Nullable ProtoMessageType resolveProtoMessageType(CelType operandType) { + if (operandType instanceof ProtoMessageType) { + return (ProtoMessageType) operandType; + } + + if (operandType.kind().equals(CelKind.STRUCT)) { + // This is either a StructTypeReference or just a Struct. Attempt to search for + // ProtoMessageType that may exist in in the type provider. + TypeType typeDef = + typeProvider + .lookupCelType(operandType.name()) + .filter(t -> t instanceof TypeType) + .map(TypeType.class::cast) + .orElse(null); + if (typeDef == null || typeDef.parameters().size() != 1) { + return null; + } + + CelType maybeProtoMessageType = typeDef.parameters().get(0); + if (maybeProtoMessageType instanceof ProtoMessageType) { + return (ProtoMessageType) maybeProtoMessageType; + } + } + + return null; + } + private void visitOptionalCall(CelMutableExpr expr, CelMutableCall call) { CelMutableExpr operand = call.args().get(0); CelMutableExpr field = call.args().get(1); diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java index d39d53803..d0b020697 100644 --- a/common/src/main/java/dev/cel/common/CelOptions.java +++ b/common/src/main/java/dev/cel/common/CelOptions.java @@ -83,6 +83,8 @@ public enum ProtoUnsetFieldOptions { public abstract boolean enableNamespacedDeclarations(); + public abstract boolean enableJsonFieldNames(); + // Evaluation related options public abstract boolean disableCelStandardEquality(); @@ -150,6 +152,7 @@ public static Builder newBuilder() { .enableTimestampEpoch(false) .enableHeterogeneousNumericComparisons(false) .enableNamespacedDeclarations(true) + .enableJsonFieldNames(false) // Evaluation options .disableCelStandardEquality(true) .evaluateCanonicalTypesToNativeValues(false) @@ -529,6 +532,16 @@ public abstract static class Builder { */ public abstract Builder maxRegexProgramSize(int value); + /** + * Use the `json_name` field option on a protobuf message as the name of the field. + * + *

    If enabled, the compiler will only accept the `json_name` and no longer recognize the + * original protobuf field name. Use with caution as this may break existing expressions during + * compilation. The runtime continues to support both names for maintaining backwards + * compatibility. + */ + public abstract Builder enableJsonFieldNames(boolean value); + public abstract CelOptions build(); } } diff --git a/common/src/main/java/dev/cel/common/types/ProtoMessageType.java b/common/src/main/java/dev/cel/common/types/ProtoMessageType.java index 7ac3f4fd3..11e48bbe6 100644 --- a/common/src/main/java/dev/cel/common/types/ProtoMessageType.java +++ b/common/src/main/java/dev/cel/common/types/ProtoMessageType.java @@ -29,14 +29,17 @@ public final class ProtoMessageType extends StructType { private final StructType.FieldResolver extensionResolver; + private final JsonNameResolver jsonNameResolver; ProtoMessageType( String name, ImmutableSet fieldNames, StructType.FieldResolver fieldResolver, - StructType.FieldResolver extensionResolver) { + StructType.FieldResolver extensionResolver, + JsonNameResolver jsonNameResolver) { super(name, fieldNames, fieldResolver); this.extensionResolver = extensionResolver; + this.jsonNameResolver = jsonNameResolver; } /** Find an {@code Extension} by its fully-qualified {@code extensionName}. */ @@ -46,20 +49,35 @@ public Optional findExtension(String extensionName) { .map(type -> Extension.of(extensionName, type, this)); } + /** Returns true if the field name is a json name. */ + public boolean isJsonName(String fieldName) { + return jsonNameResolver.isJsonName(fieldName); + } + /** * Create a new instance of the {@code ProtoMessageType} using the {@code visibleFields} set as a * mask of the fields from the backing proto. */ public ProtoMessageType withVisibleFields(ImmutableSet visibleFields) { - return new ProtoMessageType(name, visibleFields, fieldResolver, extensionResolver); + return new ProtoMessageType( + name, visibleFields, fieldResolver, extensionResolver, jsonNameResolver); } public static ProtoMessageType create( String name, ImmutableSet fieldNames, FieldResolver fieldResolver, - FieldResolver extensionResolver) { - return new ProtoMessageType(name, fieldNames, fieldResolver, extensionResolver); + FieldResolver extensionResolver, + JsonNameResolver jsonNameResolver) { + return new ProtoMessageType( + name, fieldNames, fieldResolver, extensionResolver, jsonNameResolver); + } + + /** Functional interface for resolving whether a field name is a json name. */ + @FunctionalInterface + @Immutable + public interface JsonNameResolver { + boolean isJsonName(String fieldName); } /** {@code Extension} contains the name, type, and target message type of the extension. */ diff --git a/common/src/main/java/dev/cel/common/types/ProtoMessageTypeProvider.java b/common/src/main/java/dev/cel/common/types/ProtoMessageTypeProvider.java index 4b97178d0..022f5cd8e 100644 --- a/common/src/main/java/dev/cel/common/types/ProtoMessageTypeProvider.java +++ b/common/src/main/java/dev/cel/common/types/ProtoMessageTypeProvider.java @@ -14,15 +14,14 @@ package dev.cel.common.types; -import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.protobuf.DescriptorProtos.FileDescriptorSet; @@ -34,6 +33,7 @@ import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelDescriptors; import dev.cel.common.internal.FileDescriptorSetConverter; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -68,28 +68,53 @@ public final class ProtoMessageTypeProvider implements CelTypeProvider { .buildOrThrow(); private final ImmutableMap allTypes; + private final boolean allowJsonFieldNames; + /** Returns a new builder for {@link ProtoMessageTypeProvider}. */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * @deprecated Use {@link #newBuilder()} instead. + */ + @Deprecated public ProtoMessageTypeProvider() { - this(CelDescriptors.builder().build()); + this(CelDescriptors.builder().build(), false); } + /** + * @deprecated Use {@link #newBuilder()} instead. + */ + @Deprecated public ProtoMessageTypeProvider(FileDescriptorSet descriptorSet) { this( CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( - FileDescriptorSetConverter.convert(descriptorSet))); + FileDescriptorSetConverter.convert(descriptorSet)), + false); } + /** + * @deprecated Use {@link #newBuilder()} instead. + */ + @Deprecated public ProtoMessageTypeProvider(Iterable descriptors) { this( CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( - ImmutableSet.copyOf(Iterables.transform(descriptors, Descriptor::getFile)))); + ImmutableSet.copyOf(Iterables.transform(descriptors, Descriptor::getFile))), + false); } + /** + * @deprecated Use {@link #newBuilder()} instead. + */ + @Deprecated public ProtoMessageTypeProvider(ImmutableSet fileDescriptors) { - this(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors)); + this(CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors), false); } - public ProtoMessageTypeProvider(CelDescriptors celDescriptors) { + private ProtoMessageTypeProvider(CelDescriptors celDescriptors, boolean allowJsonFieldNames) { + this.allowJsonFieldNames = allowJsonFieldNames; this.allTypes = ImmutableMap.builder() .putAll(createEnumTypes(celDescriptors.enumDescriptors())) @@ -120,8 +145,18 @@ private ImmutableMap createProtoMessageTypes( if (protoMessageTypes.containsKey(descriptor.getFullName())) { continue; } - ImmutableList fieldNames = - descriptor.getFields().stream().map(FieldDescriptor::getName).collect(toImmutableList()); + + ImmutableSet.Builder fieldNamesBuilder = ImmutableSet.builder(); + ImmutableSet.Builder jsonNamesBuilder = ImmutableSet.builder(); + for (FieldDescriptor fd : descriptor.getFields()) { + if (allowJsonFieldNames) { + fieldNamesBuilder.add(fd.getJsonName()); + jsonNamesBuilder.add(fd.getJsonName()); + } else { + fieldNamesBuilder.add(fd.getName()); + } + } + ImmutableSet jsonNames = jsonNamesBuilder.build(); Map extensionFields = new HashMap<>(); for (FieldDescriptor extension : extensionMap.get(descriptor.getFullName())) { @@ -133,9 +168,10 @@ private ImmutableMap createProtoMessageTypes( descriptor.getFullName(), ProtoMessageType.create( descriptor.getFullName(), - ImmutableSet.copyOf(fieldNames), + fieldNamesBuilder.build(), new FieldResolver(this, descriptor)::findField, - new FieldResolver(this, extensions)::findField)); + new FieldResolver(this, extensions)::findField, + jsonNames::contains)); } return ImmutableMap.copyOf(protoMessageTypes); } @@ -158,19 +194,42 @@ private ImmutableMap createEnumTypes( } private static class FieldResolver { - private final CelTypeProvider celTypeProvider; + private final ProtoMessageTypeProvider protoMessageTypeProvider; private final ImmutableMap fields; - private FieldResolver(CelTypeProvider celTypeProvider, Descriptor descriptor) { + private static ImmutableMap collectJsonFieldDescriptorMap( + Descriptor descriptor) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (FieldDescriptor fd : descriptor.getFields()) { + if (!fd.getJsonName().isEmpty()) { + builder.put(fd.getJsonName(), fd); + } else { + builder.put(fd.getName(), fd); + } + } + + return builder.buildOrThrow(); + } + + private static ImmutableMap collectFieldDescriptorMap( + Descriptor descriptor) { + return descriptor.getFields().stream() + .collect(toImmutableMap(FieldDescriptor::getName, Function.identity())); + } + + private FieldResolver( + ProtoMessageTypeProvider protoMessageTypeProvider, Descriptor descriptor) { this( - celTypeProvider, - descriptor.getFields().stream() - .collect(toImmutableMap(FieldDescriptor::getName, Function.identity()))); + protoMessageTypeProvider, + protoMessageTypeProvider.allowJsonFieldNames + ? collectJsonFieldDescriptorMap(descriptor) + : collectFieldDescriptorMap(descriptor)); } private FieldResolver( - CelTypeProvider celTypeProvider, ImmutableMap fields) { - this.celTypeProvider = celTypeProvider; + ProtoMessageTypeProvider protoMessageTypeProvider, + ImmutableMap fields) { + this.protoMessageTypeProvider = protoMessageTypeProvider; this.fields = fields; } @@ -203,11 +262,11 @@ private Optional findFieldInternal(FieldDescriptor fieldDescriptor) { String messageName = descriptor.getFullName(); fieldType = CelTypes.getWellKnownCelType(messageName) - .orElse(celTypeProvider.findType(descriptor.getFullName()).orElse(null)); + .orElse(protoMessageTypeProvider.findType(descriptor.getFullName()).orElse(null)); break; case ENUM: EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType(); - fieldType = celTypeProvider.findType(enumDescriptor.getFullName()).orElse(null); + fieldType = protoMessageTypeProvider.findType(enumDescriptor.getFullName()).orElse(null); break; default: fieldType = PROTO_TYPE_TO_CEL_TYPE.get(fieldDescriptor.getType()); @@ -222,4 +281,83 @@ private Optional findFieldInternal(FieldDescriptor fieldDescriptor) { return Optional.of(fieldType); } } + + /** Builder for {@link ProtoMessageTypeProvider}. */ + public static final class Builder { + private final ImmutableSet.Builder fileDescriptors = ImmutableSet.builder(); + private boolean allowJsonFieldNames; + private boolean resolveTypeDependencies; + private CelDescriptors celDescriptors; + + /** Adds a {@link FileDescriptor} to the provider. */ + @CanIgnoreReturnValue + public Builder addFileDescriptors(FileDescriptor... fileDescriptors) { + return addFileDescriptors(Arrays.asList(fileDescriptors)); + } + + /** Adds a collection of {@link FileDescriptor}s to the provider. */ + @CanIgnoreReturnValue + public Builder addFileDescriptors(Iterable fileDescriptors) { + this.fileDescriptors.addAll(fileDescriptors); + return this; + } + + /** Adds a collection of {@link Descriptor}s. The parent file of each descriptor is added. */ + @CanIgnoreReturnValue + public Builder addDescriptors(Iterable descriptors) { + this.fileDescriptors.addAll(Iterables.transform(descriptors, Descriptor::getFile)); + return this; + } + + /** + * Use the `json_name` field option on a protobuf message as the name of the field. + * + *

    If enabled, the type checker will only accept the `json_name` and will no longer recognize + * the original protobuf field name. This is to avoid ambiguity between the two names. + */ + @CanIgnoreReturnValue + public Builder setAllowJsonFieldNames(boolean allowJsonFieldNames) { + this.allowJsonFieldNames = allowJsonFieldNames; + return this; + } + + /** + * If true, all transitive dependencies of the added {@link FileDescriptor}s will be resolved + * and their types will be made available to the type provider. By default, this is disabled. + */ + @CanIgnoreReturnValue + public Builder setResolveTypeDependencies(boolean resolveTypeDependencies) { + this.resolveTypeDependencies = resolveTypeDependencies; + return this; + } + + /** + * Sets the CEL descriptors. Note this cannot be used in conjunction with other descriptor + * adders such as {@link #addDescriptors}. + */ + @CanIgnoreReturnValue + public Builder setCelDescriptors(CelDescriptors celDescriptors) { + this.celDescriptors = celDescriptors; + return this; + } + + /** Builds the {@link ProtoMessageTypeProvider}. */ + public ProtoMessageTypeProvider build() { + ImmutableSet fds = fileDescriptors.build(); + if (celDescriptors != null && !fds.isEmpty()) { + throw new IllegalArgumentException( + "Both CelDescriptors and FileDescriptors cannot be set at the same time."); + } + + if (celDescriptors == null) { + celDescriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor( + fileDescriptors.build(), resolveTypeDependencies); + } + + return new ProtoMessageTypeProvider(celDescriptors, allowJsonFieldNames); + } + + private Builder() {} + } } diff --git a/common/src/test/java/dev/cel/common/types/BUILD.bazel b/common/src/test/java/dev/cel/common/types/BUILD.bazel index 600a63940..0c8121bbd 100644 --- a/common/src/test/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/types/BUILD.bazel @@ -16,6 +16,7 @@ java_library( "//common/types:cel_types", "//common/types:message_type_provider", "//common/types:type_providers", + "//testing/protos:single_file_java_proto", "@cel_spec//proto/cel/expr:checked_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", diff --git a/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java b/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java index d774903e1..16797b714 100644 --- a/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java +++ b/common/src/test/java/dev/cel/common/types/ProtoMessageTypeProviderTest.java @@ -20,8 +20,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import dev.cel.common.types.CelTypeProvider.CombinedCelTypeProvider; +import dev.cel.common.types.StructType.Field; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.expr.conformance.proto2.TestAllTypesExtensions; +import dev.cel.testing.testdata.SingleFileProto.SingleFile; import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -254,4 +256,23 @@ public void types_combinedDuplicateProviderIsSameAsFirst() { CombinedCelTypeProvider combined = new CombinedCelTypeProvider(proto3Provider, proto3Provider); assertThat(combined.types()).hasSize(proto3Provider.types().size()); } + + @Test + public void findField_withJsonNameOption() { + ProtoMessageTypeProvider typeProvider = + ProtoMessageTypeProvider.newBuilder() + .addFileDescriptors(SingleFile.getDescriptor().getFile()) + .setAllowJsonFieldNames(true) + .build(); + + ProtoMessageType msgType = + (ProtoMessageType) typeProvider.findType(SingleFile.getDescriptor().getFullName()).get(); + + // Note that these are the same fields, with json_name option set + Optional snakeCasedField = msgType.findField("snake_cased"); + Optional jsonNameField = msgType.findField("camelCased"); + + assertThat(snakeCasedField).isEmpty(); + assertThat(jsonNameField).isPresent(); + } } diff --git a/common/src/test/java/dev/cel/common/types/ProtoMessageTypeTest.java b/common/src/test/java/dev/cel/common/types/ProtoMessageTypeTest.java index 3feab06fd..d6e90b1b4 100644 --- a/common/src/test/java/dev/cel/common/types/ProtoMessageTypeTest.java +++ b/common/src/test/java/dev/cel/common/types/ProtoMessageTypeTest.java @@ -46,7 +46,8 @@ public void setUp() { "my.package.TestMessage", FIELD_MAP.keySet(), (field) -> Optional.ofNullable(FIELD_MAP.get(field)), - (extension) -> Optional.ofNullable(EXTENSION_MAP.get(extension))); + (extension) -> Optional.ofNullable(EXTENSION_MAP.get(extension)), + (unused) -> false); } @Test diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index 1417007e0..3f3245c84 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -437,9 +437,16 @@ public CelRuntime build() { } } + CelTypeProvider messageTypeProvider = + ProtoMessageTypeProvider.newBuilder() + .setCelDescriptors(celDescriptors) + .setAllowJsonFieldNames(options().enableJsonFieldNames()) + .setResolveTypeDependencies(options().resolveTypeDependencies()) + .build(); + CelTypeProvider combinedTypeProvider = new CelTypeProvider.CombinedCelTypeProvider( - new ProtoMessageTypeProvider(celDescriptors), DefaultTypeProvider.getInstance()); + messageTypeProvider, DefaultTypeProvider.getInstance()); if (typeProvider() != null) { combinedTypeProvider = new CelTypeProvider.CombinedCelTypeProvider(combinedTypeProvider, typeProvider()); diff --git a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java index 2980aea96..ba0e442ec 100644 --- a/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/DescriptorMessageProvider.java @@ -182,6 +182,15 @@ private FieldDescriptor findField(Descriptor descriptor, String fieldName) { } } + if (fieldDescriptor == null && celOptions.enableJsonFieldNames()) { + for (FieldDescriptor fd : descriptor.getFields()) { + if (fd.getJsonName().equals(fieldName)) { + fieldDescriptor = fd; + break; + } + } + } + if (fieldDescriptor == null) { throw new IllegalArgumentException( String.format( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index ee86c9dd6..7923682ee 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -47,7 +47,7 @@ static Object evalStrictly( } catch (Exception e) { // Wrap generic exceptions with location throw new LocalizedEvaluationException( - e.getCause(), CelErrorCode.INTERNAL_ERROR, interpretable.exprId()); + e, CelErrorCode.INTERNAL_ERROR, interpretable.exprId()); } } diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 30691fa4a..097282f6b 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -131,7 +131,9 @@ java_library( "PlannerInterpreterTest.java", ], deps = [ + "//common:options", "//extensions", + "//runtime", "//runtime:runtime_planner_impl", "//testing:base_interpreter_test", "@maven//:com_google_testparameterinjector_test_parameter_injector", @@ -183,6 +185,7 @@ java_library( "CelLiteInterpreterTest.java", ], deps = [ + "//common:options", "//common/values:proto_message_lite_value_provider", "//extensions:optional_library", "//runtime", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java index 088a2d7b0..b3a1f2efa 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java @@ -15,6 +15,7 @@ package dev.cel.runtime; import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.common.CelOptions; import dev.cel.common.values.ProtoMessageLiteValueProvider; import dev.cel.expr.conformance.proto3.TestAllTypesCelDescriptor; import dev.cel.extensions.CelOptionalLibrary; @@ -27,16 +28,16 @@ */ @RunWith(TestParameterInjector.class) public class CelLiteInterpreterTest extends BaseInterpreterTest { - public CelLiteInterpreterTest() { - super( - CelRuntimeFactory.standardCelRuntimeBuilder() - .setValueProvider( - ProtoMessageLiteValueProvider.newInstance( - dev.cel.expr.conformance.proto2.TestAllTypesCelDescriptor.getDescriptor(), - TestAllTypesCelDescriptor.getDescriptor())) - .addLibraries(CelOptionalLibrary.INSTANCE) - .setOptions(newBaseCelOptions().toBuilder().enableCelValue(true).build()) - .build()); + + @Override + protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) { + return CelRuntimeFactory.standardCelRuntimeBuilder() + .setValueProvider( + ProtoMessageLiteValueProvider.newInstance( + dev.cel.expr.conformance.proto2.TestAllTypesCelDescriptor.getDescriptor(), + TestAllTypesCelDescriptor.getDescriptor())) + .addLibraries(CelOptionalLibrary.INSTANCE) + .setOptions(celOptions.toBuilder().enableCelValue(true).build()); } @Override @@ -97,4 +98,10 @@ public void jsonValueTypes() { public void messages_error() { skipBaselineVerification(); } + + @Override + public void jsonFieldNames() { + // json_name field option is not yet supported in lite runtime + skipBaselineVerification(); + } } diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 4e3a00a34..4b2468538 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -15,6 +15,7 @@ package dev.cel.runtime; import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.common.CelOptions; import dev.cel.extensions.CelExtensions; import dev.cel.testing.BaseInterpreterTest; import org.junit.runner.RunWith; @@ -23,15 +24,14 @@ @RunWith(TestParameterInjector.class) public class PlannerInterpreterTest extends BaseInterpreterTest { - public PlannerInterpreterTest() { - super( - CelRuntimeImpl.newBuilder() - .addLateBoundFunctions("record") - // CEL-Internal-2 - .setOptions(newBaseCelOptions()) - .addLibraries(CelExtensions.optional()) - .addFileTypes(TEST_FILE_DESCRIPTORS) - .build()); + @Override + protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) { + return CelRuntimeImpl.newBuilder() + .addLateBoundFunctions("record") + // CEL-Internal-2 + .setOptions(celOptions) + .addLibraries(CelExtensions.optional()) + .addFileTypes(TEST_FILE_DESCRIPTORS); } @Override @@ -57,4 +57,10 @@ public void optional_errors() { // TODO: Fix error message for function dispatch failures skipBaselineVerification(); } + + @Override + public void jsonFieldNames() throws Exception { + // TODO: Support JSON field names for planner + skipBaselineVerification(); + } } diff --git a/testing/src/main/java/dev/cel/testing/BUILD.bazel b/testing/src/main/java/dev/cel/testing/BUILD.bazel index d9d6a6564..5ecfd37f2 100644 --- a/testing/src/main/java/dev/cel/testing/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/BUILD.bazel @@ -84,6 +84,7 @@ java_library( "//common/internal:proto_time_utils", "//common/resources/testdata/proto3:standalone_global_enum_java_proto", "//common/types", + "//common/types:message_type_provider", "//common/types:type_providers", "//common/values:cel_byte_string", "//extensions:optional_library", diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 425271e1b..74bcca00e 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -65,6 +65,7 @@ import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; import dev.cel.common.types.OpaqueType; +import dev.cel.common.types.ProtoMessageTypeProvider; import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeParamType; @@ -77,6 +78,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelLateFunctionBindings; import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.CelRuntimeFactory; import dev.cel.runtime.CelUnknownSet; import dev.cel.runtime.CelVariableResolver; @@ -114,36 +116,43 @@ public abstract class BaseInterpreterTest extends CelBaselineTestCase { .comprehensionMaxIterations(1_000) .build(); private CelRuntime celRuntime; + protected CelOptions celOptions; protected BaseInterpreterTest() { - this(newRuntime(BASE_CEL_OPTIONS)); - } - - protected BaseInterpreterTest(CelOptions celOptions) { - this(newRuntime(celOptions)); + this.celOptions = BASE_CEL_OPTIONS; + this.celRuntime = newBaseRuntimeBuilder(celOptions).build(); } protected BaseInterpreterTest(CelRuntime celRuntime) { this.celRuntime = celRuntime; + this.celOptions = BASE_CEL_OPTIONS; } - private static CelRuntime newRuntime(CelOptions celOptions) { + protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) { return CelRuntimeFactory.standardCelRuntimeBuilder() .addLibraries(CelOptionalLibrary.INSTANCE) .addFileTypes(TEST_FILE_DESCRIPTORS) - .setOptions(celOptions) - .build(); + .setOptions(celOptions); } - protected static CelOptions newBaseCelOptions() { - return BASE_CEL_OPTIONS; + @Override + protected CelAbstractSyntaxTree prepareTest(List descriptors) { + return prepareTest( + ProtoMessageTypeProvider.newBuilder() + .addFileDescriptors(descriptors) + .setAllowJsonFieldNames(celOptions.enableJsonFieldNames()) + .build()); } @Override protected void prepareCompiler(CelTypeProvider typeProvider) { super.prepareCompiler(typeProvider); this.celCompiler = - celCompiler.toCompilerBuilder().addLibraries(CelOptionalLibrary.INSTANCE).build(); + celCompiler + .toCompilerBuilder() + .addLibraries(CelOptionalLibrary.INSTANCE) + .setOptions(celOptions) + .build(); } private CelAbstractSyntaxTree compileTestCase() { @@ -2068,7 +2077,8 @@ public void wrappers() throws Exception { @Test public void longComprehension() { ImmutableList l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList()); - addFunctionBinding(CelFunctionBinding.from("constantLongList", ImmutableList.of(), unused -> l)); + addFunctionBinding( + CelFunctionBinding.from("constantLongList", ImmutableList.of(), unused -> l)); // Comprehension over compile-time constant long list. declareFunction( @@ -2482,4 +2492,22 @@ private static Descriptor getDeserializedTestAllTypeDescriptor() { throw new RuntimeException("Error loading TestAllTypes descriptor", e); } } + + @Test + public void jsonFieldNames() throws Exception { + this.celOptions = celOptions.toBuilder().enableJsonFieldNames(true).build(); + this.celRuntime = newBaseRuntimeBuilder(celOptions).build(); + + TestAllTypes message = TestAllTypes.newBuilder().setSingleInt32(42).build(); + declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); + + source = "x.singleInt32 == 42"; + assertThat(runTest(ImmutableMap.of("x", message))).isEqualTo(true); + + source = "TestAllTypes{singleInt32: 42}.singleInt32 == 42"; + container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + assertThat(runTest()).isEqualTo(true); + + skipBaselineVerification(); + } } diff --git a/testing/src/main/java/dev/cel/testing/CelBaselineTestCase.java b/testing/src/main/java/dev/cel/testing/CelBaselineTestCase.java index 0bee52af7..79ae88f47 100644 --- a/testing/src/main/java/dev/cel/testing/CelBaselineTestCase.java +++ b/testing/src/main/java/dev/cel/testing/CelBaselineTestCase.java @@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.protobuf.DescriptorProtos.FileDescriptorSet; -import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; @@ -70,15 +69,11 @@ protected CelAbstractSyntaxTree prepareTest(List descriptors) { return prepareTest(new ProtoMessageTypeProvider(ImmutableSet.copyOf(descriptors))); } - protected CelAbstractSyntaxTree prepareTest(Iterable descriptors) { - return prepareTest(new ProtoMessageTypeProvider(descriptors)); - } - protected CelAbstractSyntaxTree prepareTest(FileDescriptorSet descriptorSet) { return prepareTest(new ProtoMessageTypeProvider(descriptorSet)); } - private CelAbstractSyntaxTree prepareTest(CelTypeProvider typeProvider) { + protected CelAbstractSyntaxTree prepareTest(CelTypeProvider typeProvider) { prepareCompiler(typeProvider); CelAbstractSyntaxTree ast; @@ -186,9 +181,7 @@ protected String formatVarDecl(CelVarDecl decl) { * Declares a function with one or more overloads * * @param functionName Function name - * @param overloads Function overloads in protobuf representation. If {@link #declareWithCelTypes} - * is set, the protobuf overloads are internally converted into java native versions {@link - * CelOverloadDecl}. + * @param overloads Function overloads in protobuf representation. */ protected void declareFunction(String functionName, CelOverloadDecl... overloads) { this.functionDecls.add(newFunctionDeclaration(functionName, overloads)); diff --git a/testing/src/test/resources/protos/single_file.proto b/testing/src/test/resources/protos/single_file.proto index 0fcf270a1..b5ce518e0 100644 --- a/testing/src/test/resources/protos/single_file.proto +++ b/testing/src/test/resources/protos/single_file.proto @@ -26,4 +26,5 @@ message SingleFile { string name = 1; Path path = 2; + string snake_cased = 3 [json_name = "camelCased"]; } From b3ee6a6599b20814488a83bb660cfaace158c0d2 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 10 Feb 2026 14:44:21 -0800 Subject: [PATCH 070/100] Fix wrapper types to properly unwrap in lists PiperOrigin-RevId: 868327828 --- .../dev/cel/common/internal/ProtoAdapter.java | 54 +++++++++++++++---- .../cel/runtime/PlannerInterpreterTest.java | 6 +++ runtime/src/test/resources/wrappers.baseline | 41 ++++++++++++++ .../dev/cel/testing/BaseInterpreterTest.java | 36 +++++++++++++ 4 files changed, 128 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java index 3c3382ef2..962a9d2e9 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -192,7 +192,9 @@ public Optional adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec if (bidiConverter == BidiConverter.IDENTITY) { return Optional.of(fieldValue); } - return Optional.of(AdaptingTypes.adaptingList((List) fieldValue, bidiConverter)); + ArrayList convertedList = + new ArrayList<>(AdaptingTypes.adaptingList((List) fieldValue, bidiConverter)); + return Optional.of(convertedList); } return Optional.of( @@ -244,28 +246,48 @@ private BidiConverter fieldToValueConverter(FieldDescriptor fieldDescriptor) { case SFIXED32: case SINT32: case INT32: - return INT_CONVERTER; + return unwrapAndConvert(INT_CONVERTER); case FIXED32: case UINT32: if (celOptions.enableUnsignedLongs()) { - return UNSIGNED_UINT32_CONVERTER; + return unwrapAndConvert(UNSIGNED_UINT32_CONVERTER); } - return SIGNED_UINT32_CONVERTER; + return unwrapAndConvert(SIGNED_UINT32_CONVERTER); case FIXED64: case UINT64: if (celOptions.enableUnsignedLongs()) { - return UNSIGNED_UINT64_CONVERTER; + return unwrapAndConvert(UNSIGNED_UINT64_CONVERTER); } - return BidiConverter.IDENTITY; + return BidiConverter.of( + BidiConverter.IDENTITY.forwardConverter(), + value -> BidiConverter.IDENTITY.backwardConverter().convert(maybeUnwrap(value))); case FLOAT: - return DOUBLE_CONVERTER; + return unwrapAndConvert(DOUBLE_CONVERTER); + case DOUBLE: + case SFIXED64: + case SINT64: + case INT64: + return BidiConverter.of( + BidiConverter.IDENTITY.forwardConverter(), + value -> BidiConverter.IDENTITY.backwardConverter().convert(maybeUnwrap(value))); case BYTES: if (celOptions.evaluateCanonicalTypesToNativeValues()) { return BidiConverter.of( - ProtoAdapter::adaptProtoByteStringToValue, ProtoAdapter::adaptCelByteStringToProto); + ProtoAdapter::adaptProtoByteStringToValue, + value -> adaptCelByteStringToProto(maybeUnwrap(value))); } - return BidiConverter.IDENTITY; + return BidiConverter.of( + BidiConverter.IDENTITY.forwardConverter(), + value -> BidiConverter.IDENTITY.backwardConverter().convert(maybeUnwrap(value))); + case STRING: + return BidiConverter.of( + BidiConverter.IDENTITY.forwardConverter(), + value -> BidiConverter.IDENTITY.backwardConverter().convert(maybeUnwrap(value))); + case BOOL: + return BidiConverter.of( + BidiConverter.IDENTITY.forwardConverter(), + value -> BidiConverter.IDENTITY.backwardConverter().convert(maybeUnwrap(value))); case ENUM: return BidiConverter.of( value -> (long) ((EnumValueDescriptor) value).getNumber(), @@ -371,4 +393,18 @@ private static int unsignedIntCheckedCast(long value) { throw new CelNumericOverflowException(e); } } + + private Object maybeUnwrap(Object value) { + if (value instanceof Message) { + return adaptProtoToValue((MessageOrBuilder) value); + } + return value; + } + + private BidiConverter unwrapAndConvert( + final BidiConverter original) { + return BidiConverter.of( + original.forwardConverter()::convert, + value -> original.backwardConverter().convert((Number) maybeUnwrap(value))); + } } diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 4b2468538..205fb2bed 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -63,4 +63,10 @@ public void jsonFieldNames() throws Exception { // TODO: Support JSON field names for planner skipBaselineVerification(); } + + @Override + public void wrappers() throws Exception { + // TODO: Fix for planner + skipBaselineVerification(); + } } diff --git a/runtime/src/test/resources/wrappers.baseline b/runtime/src/test/resources/wrappers.baseline index a971dcb29..d8212059b 100644 --- a/runtime/src/test/resources/wrappers.baseline +++ b/runtime/src/test/resources/wrappers.baseline @@ -154,6 +154,47 @@ declare dyn_var { bindings: {dyn_var=NULL_VALUE} result: NULL_VALUE +Source: TestAllTypes{repeated_int32: int32_list}.repeated_int32 == [1] && TestAllTypes{repeated_int64: int64_list}.repeated_int64 == [2] && TestAllTypes{repeated_uint32: uint32_list}.repeated_uint32 == [3u] && TestAllTypes{repeated_uint64: uint64_list}.repeated_uint64 == [4u] && TestAllTypes{repeated_float: float_list}.repeated_float == [5.5] && TestAllTypes{repeated_double: double_list}.repeated_double == [6.6] && TestAllTypes{repeated_bool: bool_list}.repeated_bool == [true] && TestAllTypes{repeated_string: string_list}.repeated_string == ['hello'] && TestAllTypes{repeated_bytes: bytes_list}.repeated_bytes == [b'world'] +declare int32_list { + value list(int) +} +declare int64_list { + value list(int) +} +declare uint32_list { + value list(uint) +} +declare uint64_list { + value list(uint) +} +declare float_list { + value list(double) +} +declare double_list { + value list(double) +} +declare bool_list { + value list(bool) +} +declare string_list { + value list(string) +} +declare bytes_list { + value list(bytes) +} +=====> +bindings: {int32_list=[value: 1 +], int64_list=[value: 2 +], uint32_list=[value: 3 +], uint64_list=[value: 4 +], float_list=[value: 5.5 +], double_list=[value: 6.6 +], bool_list=[value: true +], string_list=[value: "hello" +], bytes_list=[value: "world" +]} +result: true + Source: google.protobuf.Timestamp{ seconds: 253402300800 } =====> bindings: {} diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index 74bcca00e..f1ba69c6f 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -2067,6 +2067,42 @@ public void wrappers() throws Exception { source = "dyn_var"; runTest(ImmutableMap.of("dyn_var", NullValue.NULL_VALUE)); + clearAllDeclarations(); + declareVariable("int32_list", ListType.create(SimpleType.INT)); + declareVariable("int64_list", ListType.create(SimpleType.INT)); + declareVariable("uint32_list", ListType.create(SimpleType.UINT)); + declareVariable("uint64_list", ListType.create(SimpleType.UINT)); + declareVariable("float_list", ListType.create(SimpleType.DOUBLE)); + declareVariable("double_list", ListType.create(SimpleType.DOUBLE)); + declareVariable("bool_list", ListType.create(SimpleType.BOOL)); + declareVariable("string_list", ListType.create(SimpleType.STRING)); + declareVariable("bytes_list", ListType.create(SimpleType.BYTES)); + + container = CelContainer.ofName(TestAllTypes.getDescriptor().getFullName()); + source = + "TestAllTypes{repeated_int32: int32_list}.repeated_int32 == [1] && " + + "TestAllTypes{repeated_int64: int64_list}.repeated_int64 == [2] && " + + "TestAllTypes{repeated_uint32: uint32_list}.repeated_uint32 == [3u] && " + + "TestAllTypes{repeated_uint64: uint64_list}.repeated_uint64 == [4u] && " + + "TestAllTypes{repeated_float: float_list}.repeated_float == [5.5] && " + + "TestAllTypes{repeated_double: double_list}.repeated_double == [6.6] && " + + "TestAllTypes{repeated_bool: bool_list}.repeated_bool == [true] && " + + "TestAllTypes{repeated_string: string_list}.repeated_string == ['hello'] && " + + "TestAllTypes{repeated_bytes: bytes_list}.repeated_bytes == [b'world']"; + + runTest( + ImmutableMap.builder() + .put("int32_list", ImmutableList.of(Int32Value.of(1))) + .put("int64_list", ImmutableList.of(Int64Value.of(2))) + .put("uint32_list", ImmutableList.of(UInt32Value.of(3))) + .put("uint64_list", ImmutableList.of(UInt64Value.of(4))) + .put("float_list", ImmutableList.of(FloatValue.of(5.5f))) + .put("double_list", ImmutableList.of(DoubleValue.of(6.6))) + .put("bool_list", ImmutableList.of(BoolValue.of(true))) + .put("string_list", ImmutableList.of(StringValue.of("hello"))) + .put("bytes_list", ImmutableList.of(BytesValue.of(ByteString.copyFromUtf8("world")))) + .buildOrThrow()); + clearAllDeclarations(); // Currently allowed, but will be an error // See https://github.com/google/cel-spec/pull/501 From d4e49849cded652b1b6024433e457fe4e8589281 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 11 Feb 2026 15:00:44 -0800 Subject: [PATCH 071/100] Invert the nesting level sequencing when mangling comprehension identifiers PiperOrigin-RevId: 868859295 --- .../java/dev/cel/optimizer/AstMutator.java | 27 +- .../dev/cel/optimizer/AstMutatorTest.java | 40 +- .../SubexpressionOptimizerBaselineTest.java | 1 + ...old_before_subexpression_unparsed.baseline | 15 + ..._expressions_block_common_subexpr.baseline | 2 +- ...pressions_block_recursion_depth_1.baseline | 2 +- ...pressions_block_recursion_depth_2.baseline | 2 +- ...pressions_block_recursion_depth_3.baseline | 2 +- ...ion_ast_block_common_subexpr_only.baseline | 240 ++++++--- ...ssion_ast_block_recursion_depth_1.baseline | 455 ++++++++++++------ ...ssion_ast_block_recursion_depth_2.baseline | 455 ++++++++++++------ ...ssion_ast_block_recursion_depth_3.baseline | 455 ++++++++++++------ ...ssion_ast_block_recursion_depth_4.baseline | 366 +++++++++----- ...ssion_ast_block_recursion_depth_5.baseline | 326 +++++++++---- ...ssion_ast_block_recursion_depth_6.baseline | 294 +++++++---- ...ssion_ast_block_recursion_depth_7.baseline | 240 ++++++--- ...ssion_ast_block_recursion_depth_8.baseline | 240 ++++++--- ...ssion_ast_block_recursion_depth_9.baseline | 240 ++++++--- .../resources/subexpression_unparsed.baseline | 177 +++---- 19 files changed, 2453 insertions(+), 1126 deletions(-) diff --git a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java index 10ea36e48..59f842e29 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java +++ b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java @@ -404,16 +404,23 @@ private static MangledComprehensionName getMangledComprehensionName( } private static int countComprehensionNestingLevel(CelNavigableMutableExpr comprehensionExpr) { - int nestedLevel = 0; - Optional maybeParent = comprehensionExpr.parent(); - while (maybeParent.isPresent()) { - if (maybeParent.get().getKind().equals(Kind.COMPREHENSION)) { - nestedLevel++; - } - - maybeParent = maybeParent.get().parent(); - } - return nestedLevel; + return comprehensionExpr + .descendants() + .filter(node -> node.getKind().equals(Kind.COMPREHENSION)) + .mapToInt( + node -> { + int nestedLevel = 1; + CelNavigableMutableExpr maybeParent = node.parent().orElse(null); + while (maybeParent != null && maybeParent.id() != comprehensionExpr.id()) { + if (maybeParent.getKind().equals(Kind.COMPREHENSION)) { + nestedLevel++; + } + maybeParent = maybeParent.parent().orElse(null); + } + return nestedLevel; + }) + .max() + .orElse(0); } /** diff --git a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java index e03f95aa4..fa896ebca 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java @@ -807,8 +807,8 @@ public void mangleComprehensionVariable_adjacentMacros_sameIterVarTypes() throws assertThat(CEL_UNPARSER.unparse(mangledAst)) .isEqualTo( - "[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0 + 1)) == " - + "[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0 + 1))"); + "[1, 2, 3].map(@it:1:0, [1, 2, 3].map(@it:0:0, @it:0:0 + 1)) == " + + "[1, 2, 3].map(@it:1:0, [1, 2, 3].map(@it:0:0, @it:0:0 + 1))"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(true); assertConsistentMacroCalls(ast); } @@ -831,9 +831,9 @@ public void mangleComprehensionVariable_withTwoIterVars_adjacentMacros_sameIterV assertThat(CEL_UNPARSER.unparse(mangledAst)) .isEqualTo( - "[1, 2].transformMap(@it:0:0, @it2:0:0, [1, 2].transformMap(@it:1:0, @it2:1:0," - + " @it:1:0)) == [1, 2].transformMap(@it:0:0, @it2:0:0, [1," - + " 2].transformMap(@it:1:0, @it2:1:0, @it:1:0))"); + "[1, 2].transformMap(@it:1:0, @it2:1:0, [1, 2].transformMap(@it:0:0, @it2:0:0," + + " @it:0:0)) == [1, 2].transformMap(@it:1:0, @it2:1:0, [1," + + " 2].transformMap(@it:0:0, @it2:0:0, @it:0:0))"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(true); assertConsistentMacroCalls(ast); } @@ -854,8 +854,8 @@ public void mangleComprehensionVariable_adjacentMacros_differentIterVarTypes() t assertThat(CEL_UNPARSER.unparse(mangledAst)) .isEqualTo( - "[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0)) == " - + "dyn([1u, 2u, 3u].map(@it:0:1, [1u, 2u, 3u].map(@it:1:1, @it:1:1)))"); + "[1, 2, 3].map(@it:1:0, [1, 2, 3].map(@it:0:0, @it:0:0)) == " + + "dyn([1u, 2u, 3u].map(@it:1:1, [1u, 2u, 3u].map(@it:0:1, @it:0:1)))"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(true); assertConsistentMacroCalls(ast); } @@ -892,7 +892,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw assertThat(mangledAst.getExpr().toString()) .isEqualTo( "COMPREHENSION [27] {\n" - + " iter_var: @it:0:0\n" + + " iter_var: @it:1:0\n" + " iter_range: {\n" + " LIST [1] {\n" + " elements: {\n" @@ -902,7 +902,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:0:0\n" + + " accu_var: @ac:1:0\n" + " accu_init: {\n" + " CONSTANT [20] { value: false }\n" + " }\n" @@ -914,7 +914,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: !_\n" + " args: {\n" + " IDENT [21] {\n" - + " name: @ac:0:0\n" + + " name: @ac:1:0\n" + " }\n" + " }\n" + " }\n" @@ -926,20 +926,20 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: _||_\n" + " args: {\n" + " IDENT [24] {\n" - + " name: @ac:0:0\n" + + " name: @ac:1:0\n" + " }\n" + " COMPREHENSION [19] {\n" - + " iter_var: @it:1:0\n" + + " iter_var: @it:0:0\n" + " iter_range: {\n" + " LIST [5] {\n" + " elements: {\n" + " IDENT [6] {\n" - + " name: @it:0:0\n" + + " name: @it:1:0\n" + " }\n" + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:1:0\n" + + " accu_var: @ac:0:0\n" + " accu_init: {\n" + " CONSTANT [12] { value: false }\n" + " }\n" @@ -951,7 +951,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: !_\n" + " args: {\n" + " IDENT [13] {\n" - + " name: @ac:1:0\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + " }\n" @@ -963,13 +963,13 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: _||_\n" + " args: {\n" + " IDENT [16] {\n" - + " name: @ac:1:0\n" + + " name: @ac:0:0\n" + " }\n" + " CALL [10] {\n" + " function: _==_\n" + " args: {\n" + " IDENT [9] {\n" - + " name: @it:1:0\n" + + " name: @it:0:0\n" + " }\n" + " CONSTANT [11] { value: 1 }\n" + " }\n" @@ -979,7 +979,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " result: {\n" + " IDENT [18] {\n" - + " name: @ac:1:0\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + " }\n" @@ -988,12 +988,12 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " result: {\n" + " IDENT [26] {\n" - + " name: @ac:0:0\n" + + " name: @ac:1:0\n" + " }\n" + " }\n" + "}"); assertThat(CEL_UNPARSER.unparse(mangledAst)) - .isEqualTo("[x].exists(@it:0:0, [@it:0:0].exists(@it:1:0, @it:1:0 == 1))"); + .isEqualTo("[x].exists(@it:1:0, [@it:1:0].exists(@it:0:0, @it:0:0 == 1))"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval(ImmutableMap.of("x", 1))) .isEqualTo(true); assertConsistentMacroCalls(ast); diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java index 93f7778ec..07573f428 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java @@ -415,6 +415,7 @@ private enum CseTestCase { + "[1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0)"), NESTED_MACROS("[1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]]"), NESTED_MACROS_2("[1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]]"), + NESTED_MACROS_3("[1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2)"), NESTED_MACROS_COMP_V2_1( "[1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == " + "[[2, 4, 6], [2, 4, 6], [2, 4, 6]]"), diff --git a/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline b/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline index cf6fc3e5f..55da856cd 100644 --- a/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline @@ -358,6 +358,21 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: true [BLOCK_RECURSION_DEPTH_9]: true +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +Result: [2, 4, 6, 4, 8, 12] +[BLOCK_COMMON_SUBEXPR_ONLY]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_1]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_2]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_3]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_4]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_5]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_6]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_7]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_8]: [2, 4, 6, 4, 8, 12] +[BLOCK_RECURSION_DEPTH_9]: [2, 4, 6, 4, 8, 12] + Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> diff --git a/optimizer/src/test/resources/large_expressions_block_common_subexpr.baseline b/optimizer/src/test/resources/large_expressions_block_common_subexpr.baseline index 87623446d..fc5498563 100644 --- a/optimizer/src/test/resources/large_expressions_block_common_subexpr.baseline +++ b/optimizer/src/test/resources/large_expressions_block_common_subexpr.baseline @@ -14,4 +14,4 @@ Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3])))))))) =====> Result: [[[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]]] -Unparsed: cel.@block([[1, 2, 3]], @index0.map(@it:0:0, @index0.map(@it:1:0, @index0.map(@it:2:0, @index0.map(@it:3:0, @index0.map(@it:4:0, @index0.map(@it:5:0, @index0.map(@it:6:0, @index0.map(@it:7:0, @index0))))))))) \ No newline at end of file +Unparsed: cel.@block([[1, 2, 3]], @index0.map(@it:7:0, @index0.map(@it:6:0, @index0.map(@it:5:0, @index0.map(@it:4:0, @index0.map(@it:3:0, @index0.map(@it:2:0, @index0.map(@it:1:0, @index0.map(@it:0:0, @index0))))))))) \ No newline at end of file diff --git a/optimizer/src/test/resources/large_expressions_block_recursion_depth_1.baseline b/optimizer/src/test/resources/large_expressions_block_recursion_depth_1.baseline index 57e109c22..5d7b20381 100644 --- a/optimizer/src/test/resources/large_expressions_block_recursion_depth_1.baseline +++ b/optimizer/src/test/resources/large_expressions_block_recursion_depth_1.baseline @@ -14,4 +14,4 @@ Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3])))))))) =====> Result: [[[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]]] -Unparsed: cel.@block([[1, 2, 3], [@index0]], @index0.map(@it:0:0, @index0.map(@it:1:0, @index0.map(@it:2:0, @index0.map(@it:3:0, @index0.map(@it:4:0, @index0.map(@it:5:0, @index0.map(@it:6:0, @index0.map(@it:7:0, @index0))))))))) \ No newline at end of file +Unparsed: cel.@block([[1, 2, 3], [@index0]], @index0.map(@it:7:0, @index0.map(@it:6:0, @index0.map(@it:5:0, @index0.map(@it:4:0, @index0.map(@it:3:0, @index0.map(@it:2:0, @index0.map(@it:1:0, @index0.map(@it:0:0, @index0))))))))) \ No newline at end of file diff --git a/optimizer/src/test/resources/large_expressions_block_recursion_depth_2.baseline b/optimizer/src/test/resources/large_expressions_block_recursion_depth_2.baseline index 1cbd1d4e7..aba464e22 100644 --- a/optimizer/src/test/resources/large_expressions_block_recursion_depth_2.baseline +++ b/optimizer/src/test/resources/large_expressions_block_recursion_depth_2.baseline @@ -14,4 +14,4 @@ Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3])))))))) =====> Result: [[[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]]] -Unparsed: cel.@block([[1, 2, 3], [@index0], @index0.map(@it:7:0, @index0), [@index2], @index0.map(@it:6:0, @index2), [@index4], @index0.map(@it:5:0, @index4), [@index6], @index0.map(@it:4:0, @index6), [@index8], @index0.map(@it:3:0, @index8), [@index10], @index0.map(@it:2:0, @index10), [@index12], @index0.map(@it:1:0, @index12), [@index14]], @index0.map(@it:0:0, @index14)) \ No newline at end of file +Unparsed: cel.@block([[1, 2, 3], [@index0], @index0.map(@it:0:0, @index0), [@index2], @index0.map(@it:1:0, @index2), [@index4], @index0.map(@it:2:0, @index4), [@index6], @index0.map(@it:3:0, @index6), [@index8], @index0.map(@it:4:0, @index8), [@index10], @index0.map(@it:5:0, @index10), [@index12], @index0.map(@it:6:0, @index12), [@index14]], @index0.map(@it:7:0, @index14)) \ No newline at end of file diff --git a/optimizer/src/test/resources/large_expressions_block_recursion_depth_3.baseline b/optimizer/src/test/resources/large_expressions_block_recursion_depth_3.baseline index 3ce295592..e5a542ac4 100644 --- a/optimizer/src/test/resources/large_expressions_block_recursion_depth_3.baseline +++ b/optimizer/src/test/resources/large_expressions_block_recursion_depth_3.baseline @@ -14,4 +14,4 @@ Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3].map(i, [1, 2, 3])))))))) =====> Result: [[[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]], [[[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]], [[[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]], [[[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]], [[[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]], [[[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]], [[[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3], [1, 2, 3]]]]]]]]] -Unparsed: cel.@block([[1, 2, 3], @index0.map(@it:7:0, @index0), @index0.map(@it:6:0, @index1), @index0.map(@it:5:0, @index2), @index0.map(@it:4:0, @index3), @index0.map(@it:3:0, @index4), @index0.map(@it:2:0, @index5), @index0.map(@it:1:0, @index6)], @index0.map(@it:0:0, @index7)) \ No newline at end of file +Unparsed: cel.@block([[1, 2, 3], @index0.map(@it:0:0, @index0), @index0.map(@it:1:0, @index1), @index0.map(@it:2:0, @index2), @index0.map(@it:3:0, @index3), @index0.map(@it:4:0, @index4), @index0.map(@it:5:0, @index5), @index0.map(@it:6:0, @index6)], @index0.map(@it:7:0, @index7)) \ No newline at end of file diff --git a/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline b/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline index 103fcac54..9782b5dc2 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_common_subexpr_only.baseline @@ -2117,13 +2117,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [12] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [13] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [14] { elements: { @@ -2138,18 +2138,18 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @ac:0:0 + name: @ac:1:0 } LIST [18] { elements: { COMPREHENSION [19] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [20] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [21] { elements: { @@ -2164,7 +2164,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } LIST [25] { elements: { @@ -2172,7 +2172,7 @@ CALL [1] { function: _+_ args: { IDENT [27] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [28] { value: 1 } } @@ -2184,7 +2184,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2195,7 +2195,7 @@ CALL [1] { } result: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2341,6 +2341,122 @@ CALL [31] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _+_ + args: { + IDENT [11] { + name: @ac:0:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _*_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [16] { + name: @ac:0:0 + } + } + } + } + } + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @index0 + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _*_ + args: { + IDENT [27] { + name: @it:1:0 + } + CONSTANT [28] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2369,14 +2485,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [12] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [13] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [14] { elements: { @@ -2391,19 +2507,19 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @ac:0:0 + name: @ac:1:0 } LIST [18] { elements: { COMPREHENSION [19] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [20] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [21] { elements: { @@ -2418,7 +2534,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } LIST [25] { elements: { @@ -2429,10 +2545,10 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @it:1:0 + name: @it:0:0 } IDENT [29] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2446,7 +2562,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2457,7 +2573,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2636,13 +2752,13 @@ CALL [1] { } } COMPREHENSION [7] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [9] { elements: { @@ -2657,18 +2773,18 @@ CALL [1] { function: _+_ args: { IDENT [12] { - name: @ac:0:0 + name: @ac:1:0 } LIST [13] { elements: { COMPREHENSION [14] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [15] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [16] { elements: { @@ -2683,7 +2799,7 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @ac:1:0 + name: @ac:0:0 } LIST [20] { elements: { @@ -2691,7 +2807,7 @@ CALL [1] { function: _+_ args: { IDENT [22] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [23] { value: 1 } } @@ -2703,7 +2819,7 @@ CALL [1] { } result: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2714,7 +2830,7 @@ CALL [1] { } result: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2749,14 +2865,14 @@ CALL [1] { } } COMPREHENSION [7] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [9] { @@ -2770,20 +2886,20 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [12] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [13] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [14] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [15] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [16] { @@ -2797,10 +2913,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [19] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [20] { - name: @it:1:0 + name: @it:0:0 } CALL [21] { function: _+_ @@ -2809,10 +2925,10 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @it:1:0 + name: @it:0:0 } IDENT [24] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2824,7 +2940,7 @@ CALL [1] { } result: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2833,7 +2949,7 @@ CALL [1] { } result: { IDENT [27] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3018,13 +3134,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [13] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [14] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [15] { elements: { @@ -3039,18 +3155,18 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:0:0 + name: @ac:1:0 } LIST [19] { elements: { COMPREHENSION [20] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [21] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [22] { elements: { @@ -3065,7 +3181,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:1:0 + name: @ac:0:0 } LIST [26] { elements: { @@ -3079,7 +3195,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3090,7 +3206,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3144,14 +3260,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [13] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [14] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [15] { elements: { @@ -3166,19 +3282,19 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:0:0 + name: @ac:1:0 } LIST [19] { elements: { COMPREHENSION [20] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [21] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [22] { elements: { @@ -3193,7 +3309,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:1:0 + name: @ac:0:0 } LIST [26] { elements: { @@ -3207,7 +3323,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3218,7 +3334,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline index 134e7effd..1b3146069 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_1.baseline @@ -2905,13 +2905,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2926,18 +2926,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2952,7 +2952,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } LIST [29] { elements: { @@ -2960,7 +2960,7 @@ CALL [1] { function: _+_ args: { IDENT [31] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [32] { value: 1 } } @@ -2972,7 +2972,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2983,7 +2983,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3041,13 +3041,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [18] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [19] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [20] { elements: { @@ -3062,18 +3062,18 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @ac:0:0 + name: @ac:1:0 } LIST [24] { elements: { COMPREHENSION [25] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [27] { elements: { @@ -3091,10 +3091,10 @@ CALL [1] { function: _==_ args: { IDENT [31] { - name: @it:1:0 + name: @it:0:0 } IDENT [32] { - name: @it:0:0 + name: @it:1:0 } } } @@ -3102,26 +3102,26 @@ CALL [1] { function: _+_ args: { IDENT [34] { - name: @ac:1:0 + name: @ac:0:0 } LIST [35] { elements: { IDENT [36] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [37] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [38] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3132,7 +3132,7 @@ CALL [1] { } result: { IDENT [39] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3143,6 +3143,165 @@ CALL [1] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + } + } + CALL [7] { + function: _+_ + args: { + COMPREHENSION [8] { + iter_var: @it:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [10] { + elements: { + } + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: _+_ + args: { + IDENT [13] { + name: @ac:0:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _*_ + args: { + IDENT [16] { + name: @it:0:0 + } + CONSTANT [17] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @ac:0:0 + } + } + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_range: { + IDENT [21] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [22] { + elements: { + } + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @ac:0:0 + } + LIST [26] { + elements: { + CALL [27] { + function: _*_ + args: { + IDENT [28] { + name: @it:0:0 + } + CONSTANT [29] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [31] { + elements: { + } + } + } + loop_condition: { + CONSTANT [32] { value: true } + } + loop_step: { + CALL [33] { + function: _+_ + args: { + IDENT [34] { + name: @ac:1:0 + } + LIST [35] { + elements: { + CALL [36] { + function: _*_ + args: { + IDENT [37] { + name: @it:1:0 + } + CONSTANT [38] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [39] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -3184,14 +3343,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -3206,19 +3365,19 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -3233,7 +3392,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } LIST [29] { elements: { @@ -3244,10 +3403,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:1:0 + name: @it:0:0 } IDENT [33] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3261,7 +3420,7 @@ CALL [1] { } result: { IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3272,7 +3431,7 @@ CALL [1] { } result: { IDENT [36] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3330,14 +3489,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [18] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [19] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [20] { elements: { @@ -3352,18 +3511,18 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @ac:0:0 + name: @ac:1:0 } LIST [24] { elements: { COMPREHENSION [25] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [27] { elements: { @@ -3384,10 +3543,10 @@ CALL [1] { function: _==_ args: { IDENT [32] { - name: @it:1:0 + name: @it:0:0 } IDENT [33] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -3395,10 +3554,10 @@ CALL [1] { function: _<_ args: { IDENT [35] { - name: @it:0:0 + name: @it:1:0 } IDENT [36] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -3408,26 +3567,26 @@ CALL [1] { function: _+_ args: { IDENT [38] { - name: @ac:1:0 + name: @ac:0:0 } LIST [39] { elements: { IDENT [40] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [41] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [42] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3438,7 +3597,7 @@ CALL [1] { } result: { IDENT [43] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3470,13 +3629,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [8] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [9] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [10] { elements: { @@ -3491,18 +3650,18 @@ CALL [1] { function: _+_ args: { IDENT [13] { - name: @ac:0:0 + name: @ac:1:0 } LIST [14] { elements: { COMPREHENSION [15] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [16] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [17] { elements: { @@ -3517,7 +3676,7 @@ CALL [1] { function: _+_ args: { IDENT [20] { - name: @ac:1:0 + name: @ac:0:0 } LIST [21] { elements: { @@ -3525,7 +3684,7 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [24] { value: 1 } } @@ -3537,7 +3696,7 @@ CALL [1] { } result: { IDENT [25] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3548,18 +3707,18 @@ CALL [1] { } result: { IDENT [26] { - name: @ac:0:0 + name: @ac:1:0 } } } COMPREHENSION [27] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [28] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [29] { elements: { @@ -3574,18 +3733,18 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } LIST [33] { elements: { COMPREHENSION [34] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [35] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [36] { elements: { @@ -3600,7 +3759,7 @@ CALL [1] { function: _+_ args: { IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } LIST [40] { elements: { @@ -3608,7 +3767,7 @@ CALL [1] { function: _+_ args: { IDENT [42] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [43] { value: 1 } } @@ -3620,7 +3779,7 @@ CALL [1] { } result: { IDENT [44] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3631,7 +3790,7 @@ CALL [1] { } result: { IDENT [45] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3660,14 +3819,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [8] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [9] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [10] { @@ -3681,20 +3840,20 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [13] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [14] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [15] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [16] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [17] { @@ -3708,10 +3867,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [20] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [21] { - name: @it:1:0 + name: @it:0:0 } CALL [22] { function: _+_ @@ -3720,10 +3879,10 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @it:1:0 + name: @it:0:0 } IDENT [25] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3735,7 +3894,7 @@ CALL [1] { } result: { IDENT [27] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3744,19 +3903,19 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } } } COMPREHENSION [29] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [30] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [31] { @@ -3770,20 +3929,20 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [35] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [36] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [37] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [38] { @@ -3797,10 +3956,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [41] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [42] { - name: @it:1:0 + name: @it:0:0 } CALL [43] { function: _+_ @@ -3809,10 +3968,10 @@ CALL [1] { function: _+_ args: { IDENT [45] { - name: @it:1:0 + name: @it:0:0 } IDENT [46] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3824,7 +3983,7 @@ CALL [1] { } result: { IDENT [48] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3833,7 +3992,7 @@ CALL [1] { } result: { IDENT [49] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -4042,13 +4201,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [18] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [19] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [20] { elements: { @@ -4063,18 +4222,18 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @ac:0:0 + name: @ac:1:0 } LIST [24] { elements: { COMPREHENSION [25] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [26] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [27] { elements: { @@ -4089,7 +4248,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [31] { name: @index3 @@ -4099,7 +4258,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -4110,7 +4269,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -4174,14 +4333,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [18] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [19] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [20] { elements: { @@ -4196,19 +4355,19 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @ac:0:0 + name: @ac:1:0 } LIST [24] { elements: { COMPREHENSION [25] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [26] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [27] { elements: { @@ -4223,7 +4382,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [31] { name: @index3 @@ -4233,7 +4392,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -4244,7 +4403,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -4386,16 +4545,16 @@ CALL [1] { } } COMPREHENSION [6] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { COMPREHENSION [7] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [9] { elements: { @@ -4410,7 +4569,7 @@ CALL [1] { function: _+_ args: { IDENT [12] { - name: @ac:1:0 + name: @ac:0:0 } LIST [13] { elements: { @@ -4420,10 +4579,10 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @it:1:0 + name: @it:0:0 } IDENT [17] { - name: @it:1:0 + name: @it:0:0 } } } @@ -4431,10 +4590,10 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it:1:0 + name: @it:0:0 } IDENT [20] { - name: @it:1:0 + name: @it:0:0 } } } @@ -4447,12 +4606,12 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -4467,7 +4626,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -4477,10 +4636,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4488,10 +4647,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:0:0 + name: @it:1:0 } IDENT [33] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4504,7 +4663,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -4661,18 +4820,18 @@ CALL [1] { } } COMPREHENSION [6] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { COMPREHENSION [7] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [9] { @@ -4686,10 +4845,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [12] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [13] { - name: @it:1:0 + name: @it:0:0 } LIST [14] { elements: { @@ -4697,10 +4856,10 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @it:1:0 + name: @it:0:0 } IDENT [17] { - name: @it:1:0 + name: @it:0:0 } } } @@ -4708,10 +4867,10 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it2:1:0 + name: @it2:0:0 } IDENT [20] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -4722,12 +4881,12 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [22] { @@ -4741,10 +4900,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [26] { - name: @it:0:0 + name: @it:1:0 } LIST [27] { elements: { @@ -4752,10 +4911,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4763,10 +4922,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it2:0:0 + name: @it2:1:0 } IDENT [33] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -4777,7 +4936,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline index e161e091a..317bc87aa 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_2.baseline @@ -2726,13 +2726,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2747,18 +2747,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2773,7 +2773,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } LIST [29] { elements: { @@ -2781,7 +2781,7 @@ CALL [1] { function: _+_ args: { IDENT [31] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [32] { value: 1 } } @@ -2793,7 +2793,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2804,7 +2804,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2856,13 +2856,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2877,18 +2877,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2906,10 +2906,10 @@ CALL [1] { function: _==_ args: { IDENT [29] { - name: @it:1:0 + name: @it:0:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -2917,26 +2917,26 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:1:0 + name: @ac:0:0 } LIST [33] { elements: { IDENT [34] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2947,7 +2947,7 @@ CALL [1] { } result: { IDENT [37] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2958,6 +2958,165 @@ CALL [1] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + } + } + CALL [7] { + function: _+_ + args: { + COMPREHENSION [8] { + iter_var: @it:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [10] { + elements: { + } + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: _+_ + args: { + IDENT [13] { + name: @ac:0:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _*_ + args: { + IDENT [16] { + name: @it:0:0 + } + CONSTANT [17] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @ac:0:0 + } + } + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_range: { + IDENT [21] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [22] { + elements: { + } + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @ac:0:0 + } + LIST [26] { + elements: { + CALL [27] { + function: _*_ + args: { + IDENT [28] { + name: @it:0:0 + } + CONSTANT [29] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [31] { + elements: { + } + } + } + loop_condition: { + CONSTANT [32] { value: true } + } + loop_step: { + CALL [33] { + function: _+_ + args: { + IDENT [34] { + name: @ac:1:0 + } + LIST [35] { + elements: { + CALL [36] { + function: _*_ + args: { + IDENT [37] { + name: @it:1:0 + } + CONSTANT [38] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [39] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2999,14 +3158,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -3021,19 +3180,19 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -3048,7 +3207,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } LIST [29] { elements: { @@ -3059,10 +3218,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:1:0 + name: @it:0:0 } IDENT [33] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3076,7 +3235,7 @@ CALL [1] { } result: { IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3087,7 +3246,7 @@ CALL [1] { } result: { IDENT [36] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3139,14 +3298,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -3161,18 +3320,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -3193,10 +3352,10 @@ CALL [1] { function: _==_ args: { IDENT [30] { - name: @it:1:0 + name: @it:0:0 } IDENT [31] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -3204,10 +3363,10 @@ CALL [1] { function: _<_ args: { IDENT [33] { - name: @it:0:0 + name: @it:1:0 } IDENT [34] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -3217,26 +3376,26 @@ CALL [1] { function: _+_ args: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } LIST [37] { elements: { IDENT [38] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [40] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3247,7 +3406,7 @@ CALL [1] { } result: { IDENT [41] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3279,13 +3438,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [8] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [9] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [10] { elements: { @@ -3300,18 +3459,18 @@ CALL [1] { function: _+_ args: { IDENT [13] { - name: @ac:0:0 + name: @ac:1:0 } LIST [14] { elements: { COMPREHENSION [15] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [16] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [17] { elements: { @@ -3326,7 +3485,7 @@ CALL [1] { function: _+_ args: { IDENT [20] { - name: @ac:1:0 + name: @ac:0:0 } LIST [21] { elements: { @@ -3334,7 +3493,7 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [24] { value: 1 } } @@ -3346,7 +3505,7 @@ CALL [1] { } result: { IDENT [25] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3357,18 +3516,18 @@ CALL [1] { } result: { IDENT [26] { - name: @ac:0:0 + name: @ac:1:0 } } } COMPREHENSION [27] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [28] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [29] { elements: { @@ -3383,18 +3542,18 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } LIST [33] { elements: { COMPREHENSION [34] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [35] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [36] { elements: { @@ -3409,7 +3568,7 @@ CALL [1] { function: _+_ args: { IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } LIST [40] { elements: { @@ -3417,7 +3576,7 @@ CALL [1] { function: _+_ args: { IDENT [42] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [43] { value: 1 } } @@ -3429,7 +3588,7 @@ CALL [1] { } result: { IDENT [44] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3440,7 +3599,7 @@ CALL [1] { } result: { IDENT [45] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3469,14 +3628,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [8] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [9] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [10] { @@ -3490,20 +3649,20 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [13] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [14] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [15] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [16] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [17] { @@ -3517,10 +3676,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [20] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [21] { - name: @it:1:0 + name: @it:0:0 } CALL [22] { function: _+_ @@ -3529,10 +3688,10 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @it:1:0 + name: @it:0:0 } IDENT [25] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3544,7 +3703,7 @@ CALL [1] { } result: { IDENT [27] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3553,19 +3712,19 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } } } COMPREHENSION [29] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [30] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [31] { @@ -3579,20 +3738,20 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [35] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [36] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [37] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [38] { @@ -3606,10 +3765,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [41] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [42] { - name: @it:1:0 + name: @it:0:0 } CALL [43] { function: _+_ @@ -3618,10 +3777,10 @@ CALL [1] { function: _+_ args: { IDENT [45] { - name: @it:1:0 + name: @it:0:0 } IDENT [46] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3633,7 +3792,7 @@ CALL [1] { } result: { IDENT [48] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3642,7 +3801,7 @@ CALL [1] { } result: { IDENT [49] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3831,13 +3990,13 @@ CALL [1] { } } COMPREHENSION [17] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [18] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [19] { elements: { @@ -3852,7 +4011,7 @@ CALL [1] { function: _+_ args: { IDENT [22] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [23] { name: @index2 @@ -3862,7 +4021,7 @@ CALL [1] { } result: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3879,13 +4038,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [28] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [29] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [30] { elements: { @@ -3900,7 +4059,7 @@ CALL [1] { function: _+_ args: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [34] { name: @index4 @@ -3910,7 +4069,7 @@ CALL [1] { } result: { IDENT [35] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3969,14 +4128,14 @@ CALL [1] { } } COMPREHENSION [17] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [18] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [19] { elements: { @@ -3991,7 +4150,7 @@ CALL [1] { function: _+_ args: { IDENT [22] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [23] { name: @index2 @@ -4001,7 +4160,7 @@ CALL [1] { } result: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -4018,14 +4177,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [28] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [29] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [30] { elements: { @@ -4040,7 +4199,7 @@ CALL [1] { function: _+_ args: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [34] { name: @index4 @@ -4050,7 +4209,7 @@ CALL [1] { } result: { IDENT [35] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -4202,16 +4361,16 @@ CALL [1] { } } COMPREHENSION [6] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { COMPREHENSION [7] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [9] { elements: { @@ -4226,7 +4385,7 @@ CALL [1] { function: _+_ args: { IDENT [12] { - name: @ac:1:0 + name: @ac:0:0 } LIST [13] { elements: { @@ -4236,10 +4395,10 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @it:1:0 + name: @it:0:0 } IDENT [17] { - name: @it:1:0 + name: @it:0:0 } } } @@ -4247,10 +4406,10 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it:1:0 + name: @it:0:0 } IDENT [20] { - name: @it:1:0 + name: @it:0:0 } } } @@ -4263,12 +4422,12 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -4283,7 +4442,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -4293,10 +4452,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4304,10 +4463,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:0:0 + name: @it:1:0 } IDENT [33] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4320,7 +4479,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -4471,18 +4630,18 @@ CALL [1] { } } COMPREHENSION [6] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { COMPREHENSION [7] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [9] { @@ -4496,10 +4655,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [12] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [13] { - name: @it:1:0 + name: @it:0:0 } LIST [14] { elements: { @@ -4507,10 +4666,10 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @it:1:0 + name: @it:0:0 } IDENT [17] { - name: @it:1:0 + name: @it:0:0 } } } @@ -4518,10 +4677,10 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it2:1:0 + name: @it2:0:0 } IDENT [20] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -4532,12 +4691,12 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [22] { @@ -4551,10 +4710,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [26] { - name: @it:0:0 + name: @it:1:0 } LIST [27] { elements: { @@ -4562,10 +4721,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4573,10 +4732,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it2:0:0 + name: @it2:1:0 } IDENT [33] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -4587,7 +4746,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline index 6123928e3..89b1069ad 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_3.baseline @@ -2421,13 +2421,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2442,18 +2442,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2468,7 +2468,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } LIST [29] { elements: { @@ -2476,7 +2476,7 @@ CALL [1] { function: _+_ args: { IDENT [31] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [32] { value: 1 } } @@ -2488,7 +2488,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2499,7 +2499,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2551,13 +2551,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2572,18 +2572,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2601,10 +2601,10 @@ CALL [1] { function: _==_ args: { IDENT [29] { - name: @it:1:0 + name: @it:0:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -2612,26 +2612,26 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:1:0 + name: @ac:0:0 } LIST [33] { elements: { IDENT [34] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2642,7 +2642,7 @@ CALL [1] { } result: { IDENT [37] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2653,6 +2653,165 @@ CALL [1] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + } + } + CALL [7] { + function: _+_ + args: { + COMPREHENSION [8] { + iter_var: @it:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [10] { + elements: { + } + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: _+_ + args: { + IDENT [13] { + name: @ac:0:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _*_ + args: { + IDENT [16] { + name: @it:0:0 + } + CONSTANT [17] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @ac:0:0 + } + } + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_range: { + IDENT [21] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [22] { + elements: { + } + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @ac:0:0 + } + LIST [26] { + elements: { + CALL [27] { + function: _*_ + args: { + IDENT [28] { + name: @it:0:0 + } + CONSTANT [29] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [31] { + elements: { + } + } + } + loop_condition: { + CONSTANT [32] { value: true } + } + loop_step: { + CALL [33] { + function: _+_ + args: { + IDENT [34] { + name: @ac:1:0 + } + LIST [35] { + elements: { + CALL [36] { + function: _*_ + args: { + IDENT [37] { + name: @it:1:0 + } + CONSTANT [38] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [39] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2694,14 +2853,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2716,19 +2875,19 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2743,7 +2902,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } LIST [29] { elements: { @@ -2754,10 +2913,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:1:0 + name: @it:0:0 } IDENT [33] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2771,7 +2930,7 @@ CALL [1] { } result: { IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2782,7 +2941,7 @@ CALL [1] { } result: { IDENT [36] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2834,14 +2993,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2856,18 +3015,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2888,10 +3047,10 @@ CALL [1] { function: _==_ args: { IDENT [30] { - name: @it:1:0 + name: @it:0:0 } IDENT [31] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2899,10 +3058,10 @@ CALL [1] { function: _<_ args: { IDENT [33] { - name: @it:0:0 + name: @it:1:0 } IDENT [34] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2912,26 +3071,26 @@ CALL [1] { function: _+_ args: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } LIST [37] { elements: { IDENT [38] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [40] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2942,7 +3101,7 @@ CALL [1] { } result: { IDENT [41] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2974,13 +3133,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [8] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [9] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [10] { elements: { @@ -2995,18 +3154,18 @@ CALL [1] { function: _+_ args: { IDENT [13] { - name: @ac:0:0 + name: @ac:1:0 } LIST [14] { elements: { COMPREHENSION [15] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [16] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [17] { elements: { @@ -3021,7 +3180,7 @@ CALL [1] { function: _+_ args: { IDENT [20] { - name: @ac:1:0 + name: @ac:0:0 } LIST [21] { elements: { @@ -3029,7 +3188,7 @@ CALL [1] { function: _+_ args: { IDENT [23] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [24] { value: 1 } } @@ -3041,7 +3200,7 @@ CALL [1] { } result: { IDENT [25] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3052,18 +3211,18 @@ CALL [1] { } result: { IDENT [26] { - name: @ac:0:0 + name: @ac:1:0 } } } COMPREHENSION [27] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [28] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [29] { elements: { @@ -3078,18 +3237,18 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } LIST [33] { elements: { COMPREHENSION [34] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [35] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [36] { elements: { @@ -3104,7 +3263,7 @@ CALL [1] { function: _+_ args: { IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } LIST [40] { elements: { @@ -3112,7 +3271,7 @@ CALL [1] { function: _+_ args: { IDENT [42] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [43] { value: 1 } } @@ -3124,7 +3283,7 @@ CALL [1] { } result: { IDENT [44] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3135,7 +3294,7 @@ CALL [1] { } result: { IDENT [45] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3164,14 +3323,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [8] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [9] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [10] { @@ -3185,20 +3344,20 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [13] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [14] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [15] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [16] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [17] { @@ -3212,10 +3371,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [20] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [21] { - name: @it:1:0 + name: @it:0:0 } CALL [22] { function: _+_ @@ -3224,10 +3383,10 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @it:1:0 + name: @it:0:0 } IDENT [25] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3239,7 +3398,7 @@ CALL [1] { } result: { IDENT [27] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3248,19 +3407,19 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } } } COMPREHENSION [29] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [30] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [31] { @@ -3274,20 +3433,20 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [35] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [36] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [37] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [38] { @@ -3301,10 +3460,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [41] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [42] { - name: @it:1:0 + name: @it:0:0 } CALL [43] { function: _+_ @@ -3313,10 +3472,10 @@ CALL [1] { function: _+_ args: { IDENT [45] { - name: @it:1:0 + name: @it:0:0 } IDENT [46] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3328,7 +3487,7 @@ CALL [1] { } result: { IDENT [48] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3337,7 +3496,7 @@ CALL [1] { } result: { IDENT [49] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3523,13 +3682,13 @@ CALL [1] { } } COMPREHENSION [17] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [18] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [19] { elements: { @@ -3544,7 +3703,7 @@ CALL [1] { function: _+_ args: { IDENT [22] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [23] { name: @index2 @@ -3554,7 +3713,7 @@ CALL [1] { } result: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3564,13 +3723,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [26] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [27] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [28] { elements: { @@ -3585,7 +3744,7 @@ CALL [1] { function: _+_ args: { IDENT [31] { - name: @ac:0:0 + name: @ac:1:0 } LIST [32] { elements: { @@ -3599,7 +3758,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3658,14 +3817,14 @@ CALL [1] { } } COMPREHENSION [17] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [18] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [19] { elements: { @@ -3680,7 +3839,7 @@ CALL [1] { function: _+_ args: { IDENT [22] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [23] { name: @index2 @@ -3690,7 +3849,7 @@ CALL [1] { } result: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3700,14 +3859,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [26] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [27] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [28] { elements: { @@ -3722,7 +3881,7 @@ CALL [1] { function: _+_ args: { IDENT [31] { - name: @ac:0:0 + name: @ac:1:0 } LIST [32] { elements: { @@ -3736,7 +3895,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3885,16 +4044,16 @@ CALL [1] { } } COMPREHENSION [6] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { COMPREHENSION [7] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [9] { elements: { @@ -3909,7 +4068,7 @@ CALL [1] { function: _+_ args: { IDENT [12] { - name: @ac:1:0 + name: @ac:0:0 } LIST [13] { elements: { @@ -3919,10 +4078,10 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @it:1:0 + name: @it:0:0 } IDENT [17] { - name: @it:1:0 + name: @it:0:0 } } } @@ -3930,10 +4089,10 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it:1:0 + name: @it:0:0 } IDENT [20] { - name: @it:1:0 + name: @it:0:0 } } } @@ -3946,12 +4105,12 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -3966,7 +4125,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -3976,10 +4135,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -3987,10 +4146,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:0:0 + name: @it:1:0 } IDENT [33] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4003,7 +4162,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -4168,18 +4327,18 @@ CALL [1] { } } COMPREHENSION [6] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { COMPREHENSION [7] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [9] { @@ -4193,10 +4352,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [12] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [13] { - name: @it:1:0 + name: @it:0:0 } LIST [14] { elements: { @@ -4204,10 +4363,10 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @it:1:0 + name: @it:0:0 } IDENT [17] { - name: @it:1:0 + name: @it:0:0 } } } @@ -4215,10 +4374,10 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it2:1:0 + name: @it2:0:0 } IDENT [20] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -4229,12 +4388,12 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [22] { @@ -4248,10 +4407,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [26] { - name: @it:0:0 + name: @it:1:0 } LIST [27] { elements: { @@ -4259,10 +4418,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -4270,10 +4429,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it2:0:0 + name: @it2:1:0 } IDENT [33] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -4284,7 +4443,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline index 2159ae348..fe139eea1 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_4.baseline @@ -2165,13 +2165,13 @@ CALL [1] { } } COMPREHENSION [11] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [12] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [13] { elements: { @@ -2186,7 +2186,7 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } LIST [17] { elements: { @@ -2194,7 +2194,7 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [20] { value: 1 } } @@ -2206,7 +2206,7 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2216,13 +2216,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [23] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [25] { elements: { @@ -2237,7 +2237,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } LIST [29] { elements: { @@ -2251,7 +2251,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2313,13 +2313,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2334,18 +2334,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2363,10 +2363,10 @@ CALL [1] { function: _==_ args: { IDENT [29] { - name: @it:1:0 + name: @it:0:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -2374,26 +2374,26 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:1:0 + name: @ac:0:0 } LIST [33] { elements: { IDENT [34] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2404,7 +2404,7 @@ CALL [1] { } result: { IDENT [37] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2415,6 +2415,122 @@ CALL [1] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _+_ + args: { + IDENT [11] { + name: @ac:0:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _*_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [16] { + name: @ac:0:0 + } + } + } + } + } + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @index0 + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _*_ + args: { + IDENT [27] { + name: @it:1:0 + } + CONSTANT [28] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2456,14 +2572,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2478,19 +2594,19 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2505,7 +2621,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } LIST [29] { elements: { @@ -2516,10 +2632,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:1:0 + name: @it:0:0 } IDENT [33] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2533,7 +2649,7 @@ CALL [1] { } result: { IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2544,7 +2660,7 @@ CALL [1] { } result: { IDENT [36] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2596,14 +2712,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2618,18 +2734,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2650,10 +2766,10 @@ CALL [1] { function: _==_ args: { IDENT [30] { - name: @it:1:0 + name: @it:0:0 } IDENT [31] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2661,10 +2777,10 @@ CALL [1] { function: _<_ args: { IDENT [33] { - name: @it:0:0 + name: @it:1:0 } IDENT [34] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2674,26 +2790,26 @@ CALL [1] { function: _+_ args: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } LIST [37] { elements: { IDENT [38] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [40] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2704,7 +2820,7 @@ CALL [1] { } result: { IDENT [41] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2724,7 +2840,7 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { LIST [4] { elements: { @@ -2734,7 +2850,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [8] { elements: { @@ -2749,7 +2865,7 @@ CALL [1] { function: _+_ args: { IDENT [11] { - name: @ac:1:0 + name: @ac:0:0 } LIST [12] { elements: { @@ -2757,7 +2873,7 @@ CALL [1] { function: _+_ args: { IDENT [14] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [15] { value: 1 } } @@ -2769,12 +2885,12 @@ CALL [1] { } result: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } } } COMPREHENSION [17] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { LIST [18] { elements: { @@ -2784,7 +2900,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -2799,7 +2915,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -2813,7 +2929,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2841,8 +2957,8 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { LIST [4] { elements: { @@ -2852,7 +2968,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [8] { @@ -2866,10 +2982,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [11] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [12] { - name: @it:1:0 + name: @it:0:0 } CALL [13] { function: _+_ @@ -2878,10 +2994,10 @@ CALL [1] { function: _+_ args: { IDENT [15] { - name: @it:1:0 + name: @it:0:0 } IDENT [16] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2893,13 +3009,13 @@ CALL [1] { } result: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } } } COMPREHENSION [19] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { LIST [20] { elements: { @@ -2909,7 +3025,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [24] { @@ -2923,10 +3039,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [27] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [28] { - name: @it:0:0 + name: @it:1:0 } IDENT [29] { name: @index0 @@ -2936,7 +3052,7 @@ CALL [1] { } result: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3120,13 +3236,13 @@ CALL [1] { } } COMPREHENSION [13] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [14] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [15] { elements: { @@ -3141,7 +3257,7 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } LIST [19] { elements: { @@ -3158,7 +3274,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3168,13 +3284,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -3189,7 +3305,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -3203,7 +3319,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3252,14 +3368,14 @@ CALL [1] { } } COMPREHENSION [13] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [14] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [15] { elements: { @@ -3274,7 +3390,7 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } LIST [19] { elements: { @@ -3291,7 +3407,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3301,14 +3417,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -3323,7 +3439,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -3337,7 +3453,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3483,16 +3599,16 @@ CALL [1] { } } COMPREHENSION [6] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { COMPREHENSION [7] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [8] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [9] { elements: { @@ -3507,7 +3623,7 @@ CALL [1] { function: _+_ args: { IDENT [12] { - name: @ac:1:0 + name: @ac:0:0 } LIST [13] { elements: { @@ -3517,10 +3633,10 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @it:1:0 + name: @it:0:0 } IDENT [17] { - name: @it:1:0 + name: @it:0:0 } } } @@ -3528,10 +3644,10 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it:1:0 + name: @it:0:0 } IDENT [20] { - name: @it:1:0 + name: @it:0:0 } } } @@ -3544,12 +3660,12 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -3564,7 +3680,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -3574,10 +3690,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -3585,10 +3701,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:0:0 + name: @it:1:0 } IDENT [33] { - name: @it:0:0 + name: @it:1:0 } } } @@ -3601,7 +3717,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3755,8 +3871,8 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { LIST [4] { elements: { @@ -3765,7 +3881,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [7] { @@ -3779,10 +3895,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [10] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [11] { - name: @it:1:0 + name: @it:0:0 } LIST [12] { elements: { @@ -3790,10 +3906,10 @@ CALL [1] { function: _+_ args: { IDENT [14] { - name: @it:1:0 + name: @it:0:0 } IDENT [15] { - name: @it:1:0 + name: @it:0:0 } } } @@ -3801,10 +3917,10 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @it2:1:0 + name: @it2:0:0 } IDENT [18] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -3815,21 +3931,21 @@ CALL [1] { } result: { IDENT [19] { - name: @ac:1:0 + name: @ac:0:0 } } } } } COMPREHENSION [20] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [21] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [22] { @@ -3843,10 +3959,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [26] { - name: @it:0:0 + name: @it:1:0 } LIST [27] { elements: { @@ -3854,10 +3970,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -3865,10 +3981,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it2:0:0 + name: @it2:1:0 } IDENT [33] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -3879,7 +3995,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline index 37a3d60a8..c7ab9e404 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_5.baseline @@ -2147,13 +2147,13 @@ CALL [1] { } } COMPREHENSION [11] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [12] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [13] { elements: { @@ -2168,7 +2168,7 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } LIST [17] { elements: { @@ -2176,7 +2176,7 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [20] { value: 1 } } @@ -2188,7 +2188,7 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2198,13 +2198,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [23] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [25] { elements: { @@ -2219,7 +2219,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } LIST [29] { elements: { @@ -2233,7 +2233,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2295,13 +2295,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2316,18 +2316,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2345,10 +2345,10 @@ CALL [1] { function: _==_ args: { IDENT [29] { - name: @it:1:0 + name: @it:0:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -2356,26 +2356,26 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:1:0 + name: @ac:0:0 } LIST [33] { elements: { IDENT [34] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2386,7 +2386,7 @@ CALL [1] { } result: { IDENT [37] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2397,6 +2397,122 @@ CALL [1] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _+_ + args: { + IDENT [11] { + name: @ac:0:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _*_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [16] { + name: @ac:0:0 + } + } + } + } + } + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @index0 + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _*_ + args: { + IDENT [27] { + name: @it:1:0 + } + CONSTANT [28] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2420,14 +2536,14 @@ CALL [1] { } } COMPREHENSION [11] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [12] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [13] { elements: { @@ -2442,7 +2558,7 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } LIST [17] { elements: { @@ -2453,10 +2569,10 @@ CALL [1] { function: _+_ args: { IDENT [20] { - name: @it:1:0 + name: @it:0:0 } IDENT [21] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2470,7 +2586,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2480,14 +2596,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -2502,7 +2618,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -2516,7 +2632,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2578,14 +2694,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2600,18 +2716,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2632,10 +2748,10 @@ CALL [1] { function: _==_ args: { IDENT [30] { - name: @it:1:0 + name: @it:0:0 } IDENT [31] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2643,10 +2759,10 @@ CALL [1] { function: _<_ args: { IDENT [33] { - name: @it:0:0 + name: @it:1:0 } IDENT [34] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2656,26 +2772,26 @@ CALL [1] { function: _+_ args: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } LIST [37] { elements: { IDENT [38] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [40] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2686,7 +2802,7 @@ CALL [1] { } result: { IDENT [41] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2706,7 +2822,7 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { LIST [4] { elements: { @@ -2716,7 +2832,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [8] { elements: { @@ -2731,7 +2847,7 @@ CALL [1] { function: _+_ args: { IDENT [11] { - name: @ac:1:0 + name: @ac:0:0 } LIST [12] { elements: { @@ -2739,7 +2855,7 @@ CALL [1] { function: _+_ args: { IDENT [14] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [15] { value: 1 } } @@ -2751,12 +2867,12 @@ CALL [1] { } result: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } } } COMPREHENSION [17] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { LIST [18] { elements: { @@ -2766,7 +2882,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -2781,7 +2897,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -2795,7 +2911,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2823,8 +2939,8 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { LIST [4] { elements: { @@ -2834,7 +2950,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [8] { @@ -2848,10 +2964,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [11] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [12] { - name: @it:1:0 + name: @it:0:0 } CALL [13] { function: _+_ @@ -2860,10 +2976,10 @@ CALL [1] { function: _+_ args: { IDENT [15] { - name: @it:1:0 + name: @it:0:0 } IDENT [16] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2875,13 +2991,13 @@ CALL [1] { } result: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } } } COMPREHENSION [19] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { LIST [20] { elements: { @@ -2891,7 +3007,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [24] { @@ -2905,10 +3021,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [27] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [28] { - name: @it:0:0 + name: @it:1:0 } IDENT [29] { name: @index0 @@ -2918,7 +3034,7 @@ CALL [1] { } result: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3102,13 +3218,13 @@ CALL [1] { } } COMPREHENSION [13] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [14] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [15] { elements: { @@ -3123,7 +3239,7 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } LIST [19] { elements: { @@ -3140,7 +3256,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3150,13 +3266,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -3171,7 +3287,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -3185,7 +3301,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3234,14 +3350,14 @@ CALL [1] { } } COMPREHENSION [13] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [14] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [15] { elements: { @@ -3256,7 +3372,7 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } LIST [19] { elements: { @@ -3273,7 +3389,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3283,14 +3399,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -3305,7 +3421,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -3319,7 +3435,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3457,7 +3573,7 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { LIST [4] { elements: { @@ -3466,7 +3582,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [7] { elements: { @@ -3481,7 +3597,7 @@ CALL [1] { function: _+_ args: { IDENT [10] { - name: @ac:1:0 + name: @ac:0:0 } LIST [11] { elements: { @@ -3491,10 +3607,10 @@ CALL [1] { function: _+_ args: { IDENT [14] { - name: @it:1:0 + name: @it:0:0 } IDENT [15] { - name: @it:1:0 + name: @it:0:0 } } } @@ -3502,10 +3618,10 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @it:1:0 + name: @it:0:0 } IDENT [18] { - name: @it:1:0 + name: @it:0:0 } } } @@ -3518,20 +3634,20 @@ CALL [1] { } result: { IDENT [19] { - name: @ac:1:0 + name: @ac:0:0 } } } } } COMPREHENSION [20] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [21] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -3546,7 +3662,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -3556,10 +3672,10 @@ CALL [1] { function: _+_ args: { IDENT [29] { - name: @it:0:0 + name: @it:1:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -3567,10 +3683,10 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @it:0:0 + name: @it:1:0 } IDENT [33] { - name: @it:0:0 + name: @it:1:0 } } } @@ -3583,7 +3699,7 @@ CALL [1] { } result: { IDENT [34] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline index 420c6d01b..f13d51e99 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_6.baseline @@ -2141,13 +2141,13 @@ CALL [1] { } } COMPREHENSION [11] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [12] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [13] { elements: { @@ -2162,7 +2162,7 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } LIST [17] { elements: { @@ -2170,7 +2170,7 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [20] { value: 1 } } @@ -2182,7 +2182,7 @@ CALL [1] { } result: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2192,13 +2192,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [23] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [24] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [25] { elements: { @@ -2213,7 +2213,7 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } LIST [29] { elements: { @@ -2227,7 +2227,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2289,13 +2289,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2310,18 +2310,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2339,10 +2339,10 @@ CALL [1] { function: _==_ args: { IDENT [29] { - name: @it:1:0 + name: @it:0:0 } IDENT [30] { - name: @it:0:0 + name: @it:1:0 } } } @@ -2350,26 +2350,26 @@ CALL [1] { function: _+_ args: { IDENT [32] { - name: @ac:1:0 + name: @ac:0:0 } LIST [33] { elements: { IDENT [34] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [35] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2380,7 +2380,7 @@ CALL [1] { } result: { IDENT [37] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2391,6 +2391,122 @@ CALL [1] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _+_ + args: { + IDENT [11] { + name: @ac:0:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _*_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [16] { + name: @ac:0:0 + } + } + } + } + } + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @index0 + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _*_ + args: { + IDENT [27] { + name: @it:1:0 + } + CONSTANT [28] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2414,14 +2530,14 @@ CALL [1] { } } COMPREHENSION [11] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [12] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [13] { elements: { @@ -2436,7 +2552,7 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } LIST [17] { elements: { @@ -2447,10 +2563,10 @@ CALL [1] { function: _+_ args: { IDENT [20] { - name: @it:1:0 + name: @it:0:0 } IDENT [21] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2464,7 +2580,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2474,14 +2590,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -2496,7 +2612,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -2510,7 +2626,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2572,14 +2688,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [16] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [17] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [18] { elements: { @@ -2594,18 +2710,18 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:0:0 + name: @ac:1:0 } LIST [22] { elements: { COMPREHENSION [23] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [24] { name: @index2 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [25] { elements: { @@ -2626,10 +2742,10 @@ CALL [1] { function: _==_ args: { IDENT [30] { - name: @it:1:0 + name: @it:0:0 } IDENT [31] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2637,10 +2753,10 @@ CALL [1] { function: _<_ args: { IDENT [33] { - name: @it:0:0 + name: @it:1:0 } IDENT [34] { - name: @it2:0:0 + name: @it2:1:0 } } } @@ -2650,26 +2766,26 @@ CALL [1] { function: _+_ args: { IDENT [36] { - name: @ac:1:0 + name: @ac:0:0 } LIST [37] { elements: { IDENT [38] { - name: @it:1:0 + name: @it:0:0 } } } } } IDENT [39] { - name: @ac:1:0 + name: @ac:0:0 } } } } result: { IDENT [40] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2680,7 +2796,7 @@ CALL [1] { } result: { IDENT [41] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2700,7 +2816,7 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { LIST [4] { elements: { @@ -2710,7 +2826,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [8] { elements: { @@ -2725,7 +2841,7 @@ CALL [1] { function: _+_ args: { IDENT [11] { - name: @ac:1:0 + name: @ac:0:0 } LIST [12] { elements: { @@ -2733,7 +2849,7 @@ CALL [1] { function: _+_ args: { IDENT [14] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [15] { value: 1 } } @@ -2745,12 +2861,12 @@ CALL [1] { } result: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } } } COMPREHENSION [17] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { LIST [18] { elements: { @@ -2760,7 +2876,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [22] { elements: { @@ -2775,7 +2891,7 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @ac:0:0 + name: @ac:1:0 } LIST [26] { elements: { @@ -2789,7 +2905,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2817,8 +2933,8 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { LIST [4] { elements: { @@ -2828,7 +2944,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [8] { @@ -2842,14 +2958,14 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [11] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [12] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [13] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { LIST [14] { elements: { @@ -2859,7 +2975,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [18] { @@ -2873,10 +2989,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [22] { - name: @it:1:0 + name: @it:0:0 } CALL [23] { function: _+_ @@ -2885,10 +3001,10 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @it:1:0 + name: @it:0:0 } IDENT [26] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2900,7 +3016,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2909,7 +3025,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3093,13 +3209,13 @@ CALL [1] { } } COMPREHENSION [13] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [14] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [15] { elements: { @@ -3114,7 +3230,7 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } LIST [19] { elements: { @@ -3131,7 +3247,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3141,13 +3257,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -3162,7 +3278,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -3176,7 +3292,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3225,14 +3341,14 @@ CALL [1] { } } COMPREHENSION [13] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [14] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [15] { elements: { @@ -3247,7 +3363,7 @@ CALL [1] { function: _+_ args: { IDENT [18] { - name: @ac:1:0 + name: @ac:0:0 } LIST [19] { elements: { @@ -3264,7 +3380,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3274,14 +3390,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -3296,7 +3412,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -3310,7 +3426,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline index 6d8187a17..020447afd 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_7.baseline @@ -2143,13 +2143,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [12] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [13] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [14] { elements: { @@ -2164,18 +2164,18 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @ac:0:0 + name: @ac:1:0 } LIST [18] { elements: { COMPREHENSION [19] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [20] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [21] { elements: { @@ -2190,7 +2190,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } LIST [25] { elements: { @@ -2198,7 +2198,7 @@ CALL [1] { function: _+_ args: { IDENT [27] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [28] { value: 1 } } @@ -2210,7 +2210,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2221,7 +2221,7 @@ CALL [1] { } result: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2367,6 +2367,122 @@ CALL [31] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _+_ + args: { + IDENT [11] { + name: @ac:0:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _*_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [16] { + name: @ac:0:0 + } + } + } + } + } + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @index0 + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _*_ + args: { + IDENT [27] { + name: @it:1:0 + } + CONSTANT [28] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2390,14 +2506,14 @@ CALL [1] { } } COMPREHENSION [11] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [12] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [13] { elements: { @@ -2412,7 +2528,7 @@ CALL [1] { function: _+_ args: { IDENT [16] { - name: @ac:1:0 + name: @ac:0:0 } LIST [17] { elements: { @@ -2423,10 +2539,10 @@ CALL [1] { function: _+_ args: { IDENT [20] { - name: @it:1:0 + name: @it:0:0 } IDENT [21] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2440,7 +2556,7 @@ CALL [1] { } result: { IDENT [23] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2450,14 +2566,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [25] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [27] { elements: { @@ -2472,7 +2588,7 @@ CALL [1] { function: _+_ args: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } LIST [31] { elements: { @@ -2486,7 +2602,7 @@ CALL [1] { } result: { IDENT [33] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2658,7 +2774,7 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { LIST [4] { elements: { @@ -2668,7 +2784,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [8] { elements: { @@ -2683,12 +2799,12 @@ CALL [1] { function: _+_ args: { IDENT [11] { - name: @ac:0:0 + name: @ac:1:0 } LIST [12] { elements: { COMPREHENSION [13] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { LIST [14] { elements: { @@ -2698,7 +2814,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [18] { elements: { @@ -2713,7 +2829,7 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } LIST [22] { elements: { @@ -2721,7 +2837,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [25] { value: 1 } } @@ -2733,7 +2849,7 @@ CALL [1] { } result: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2744,7 +2860,7 @@ CALL [1] { } result: { IDENT [27] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2772,8 +2888,8 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { LIST [4] { elements: { @@ -2783,7 +2899,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [8] { @@ -2797,14 +2913,14 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [11] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [12] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [13] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { LIST [14] { elements: { @@ -2814,7 +2930,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [18] { @@ -2828,10 +2944,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [22] { - name: @it:1:0 + name: @it:0:0 } CALL [23] { function: _+_ @@ -2840,10 +2956,10 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @it:1:0 + name: @it:0:0 } IDENT [26] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2855,7 +2971,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2864,7 +2980,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3053,13 +3169,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [14] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [15] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [16] { elements: { @@ -3074,18 +3190,18 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @ac:0:0 + name: @ac:1:0 } LIST [20] { elements: { COMPREHENSION [21] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [22] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [23] { elements: { @@ -3100,7 +3216,7 @@ CALL [1] { function: _+_ args: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } LIST [27] { elements: { @@ -3117,7 +3233,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3128,7 +3244,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3182,14 +3298,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [14] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [15] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [16] { elements: { @@ -3204,19 +3320,19 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @ac:0:0 + name: @ac:1:0 } LIST [20] { elements: { COMPREHENSION [21] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [22] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [23] { elements: { @@ -3231,7 +3347,7 @@ CALL [1] { function: _+_ args: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } LIST [27] { elements: { @@ -3248,7 +3364,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3259,7 +3375,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline index 726b6aa91..bd1fa8d45 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_8.baseline @@ -2143,13 +2143,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [12] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [13] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [14] { elements: { @@ -2164,18 +2164,18 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @ac:0:0 + name: @ac:1:0 } LIST [18] { elements: { COMPREHENSION [19] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [20] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [21] { elements: { @@ -2190,7 +2190,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } LIST [25] { elements: { @@ -2198,7 +2198,7 @@ CALL [1] { function: _+_ args: { IDENT [27] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [28] { value: 1 } } @@ -2210,7 +2210,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2221,7 +2221,7 @@ CALL [1] { } result: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2367,6 +2367,122 @@ CALL [31] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _+_ + args: { + IDENT [11] { + name: @ac:0:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _*_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [16] { + name: @ac:0:0 + } + } + } + } + } + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @index0 + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _*_ + args: { + IDENT [27] { + name: @it:1:0 + } + CONSTANT [28] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2395,14 +2511,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [12] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [13] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [14] { elements: { @@ -2417,19 +2533,19 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @ac:0:0 + name: @ac:1:0 } LIST [18] { elements: { COMPREHENSION [19] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [20] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [21] { elements: { @@ -2444,7 +2560,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } LIST [25] { elements: { @@ -2455,10 +2571,10 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @it:1:0 + name: @it:0:0 } IDENT [29] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2472,7 +2588,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2483,7 +2599,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2655,7 +2771,7 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { LIST [4] { elements: { @@ -2665,7 +2781,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [8] { elements: { @@ -2680,12 +2796,12 @@ CALL [1] { function: _+_ args: { IDENT [11] { - name: @ac:0:0 + name: @ac:1:0 } LIST [12] { elements: { COMPREHENSION [13] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { LIST [14] { elements: { @@ -2695,7 +2811,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [18] { elements: { @@ -2710,7 +2826,7 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } LIST [22] { elements: { @@ -2718,7 +2834,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [25] { value: 1 } } @@ -2730,7 +2846,7 @@ CALL [1] { } result: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2741,7 +2857,7 @@ CALL [1] { } result: { IDENT [27] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2769,8 +2885,8 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { LIST [4] { elements: { @@ -2780,7 +2896,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [8] { @@ -2794,14 +2910,14 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [11] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [12] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [13] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { LIST [14] { elements: { @@ -2811,7 +2927,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [18] { @@ -2825,10 +2941,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [22] { - name: @it:1:0 + name: @it:0:0 } CALL [23] { function: _+_ @@ -2837,10 +2953,10 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @it:1:0 + name: @it:0:0 } IDENT [26] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2852,7 +2968,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2861,7 +2977,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3050,13 +3166,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [14] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [15] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [16] { elements: { @@ -3071,18 +3187,18 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @ac:0:0 + name: @ac:1:0 } LIST [20] { elements: { COMPREHENSION [21] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [22] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [23] { elements: { @@ -3097,7 +3213,7 @@ CALL [1] { function: _+_ args: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } LIST [27] { elements: { @@ -3114,7 +3230,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3125,7 +3241,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3179,14 +3295,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [14] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [15] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [16] { elements: { @@ -3201,19 +3317,19 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @ac:0:0 + name: @ac:1:0 } LIST [20] { elements: { COMPREHENSION [21] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [22] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [23] { elements: { @@ -3228,7 +3344,7 @@ CALL [1] { function: _+_ args: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } LIST [27] { elements: { @@ -3245,7 +3361,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3256,7 +3372,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline index ba147d0df..c2d7334af 100644 --- a/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline +++ b/optimizer/src/test/resources/subexpression_ast_block_recursion_depth_9.baseline @@ -2131,13 +2131,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [12] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [13] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [14] { elements: { @@ -2152,18 +2152,18 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @ac:0:0 + name: @ac:1:0 } LIST [18] { elements: { COMPREHENSION [19] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [20] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [21] { elements: { @@ -2178,7 +2178,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } LIST [25] { elements: { @@ -2186,7 +2186,7 @@ CALL [1] { function: _+_ args: { IDENT [27] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [28] { value: 1 } } @@ -2198,7 +2198,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2209,7 +2209,7 @@ CALL [1] { } result: { IDENT [30] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2355,6 +2355,122 @@ CALL [31] { } } } +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: _+_ + args: { + IDENT [11] { + name: @ac:0:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _*_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [16] { + name: @ac:0:0 + } + } + } + } + } + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @index0 + } + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_range: { + IDENT [20] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [21] { + elements: { + } + } + } + loop_condition: { + CONSTANT [22] { value: true } + } + loop_step: { + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @ac:1:0 + } + LIST [25] { + elements: { + CALL [26] { + function: _*_ + args: { + IDENT [27] { + name: @it:1:0 + } + CONSTANT [28] { value: 2 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } +} Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> @@ -2383,14 +2499,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [12] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [13] { name: @index0 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [14] { elements: { @@ -2405,19 +2521,19 @@ CALL [1] { function: _+_ args: { IDENT [17] { - name: @ac:0:0 + name: @ac:1:0 } LIST [18] { elements: { COMPREHENSION [19] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [20] { name: @index0 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [21] { elements: { @@ -2432,7 +2548,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @ac:1:0 + name: @ac:0:0 } LIST [25] { elements: { @@ -2443,10 +2559,10 @@ CALL [1] { function: _+_ args: { IDENT [28] { - name: @it:1:0 + name: @it:0:0 } IDENT [29] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2460,7 +2576,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2471,7 +2587,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2643,7 +2759,7 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { LIST [4] { elements: { @@ -2653,7 +2769,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [8] { elements: { @@ -2668,12 +2784,12 @@ CALL [1] { function: _+_ args: { IDENT [11] { - name: @ac:0:0 + name: @ac:1:0 } LIST [12] { elements: { COMPREHENSION [13] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { LIST [14] { elements: { @@ -2683,7 +2799,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [18] { elements: { @@ -2698,7 +2814,7 @@ CALL [1] { function: _+_ args: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } LIST [22] { elements: { @@ -2706,7 +2822,7 @@ CALL [1] { function: _+_ args: { IDENT [24] { - name: @it:1:0 + name: @it:0:0 } CONSTANT [25] { value: 1 } } @@ -2718,7 +2834,7 @@ CALL [1] { } result: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2729,7 +2845,7 @@ CALL [1] { } result: { IDENT [27] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -2757,8 +2873,8 @@ CALL [1] { LIST [2] { elements: { COMPREHENSION [3] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { LIST [4] { elements: { @@ -2768,7 +2884,7 @@ CALL [1] { } } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { MAP [8] { @@ -2782,14 +2898,14 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [11] { - name: @ac:0:0 + name: @ac:1:0 } IDENT [12] { - name: @it:0:0 + name: @it:1:0 } COMPREHENSION [13] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { LIST [14] { elements: { @@ -2799,7 +2915,7 @@ CALL [1] { } } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { MAP [18] { @@ -2813,10 +2929,10 @@ CALL [1] { function: cel.@mapInsert args: { IDENT [21] { - name: @ac:1:0 + name: @ac:0:0 } IDENT [22] { - name: @it:1:0 + name: @it:0:0 } CALL [23] { function: _+_ @@ -2825,10 +2941,10 @@ CALL [1] { function: _+_ args: { IDENT [25] { - name: @it:1:0 + name: @it:0:0 } IDENT [26] { - name: @it2:1:0 + name: @it2:0:0 } } } @@ -2840,7 +2956,7 @@ CALL [1] { } result: { IDENT [28] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -2849,7 +2965,7 @@ CALL [1] { } result: { IDENT [29] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3038,13 +3154,13 @@ CALL [1] { function: _==_ args: { COMPREHENSION [14] { - iter_var: @it:0:0 + iter_var: @it:1:0 iter_range: { IDENT [15] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [16] { elements: { @@ -3059,18 +3175,18 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @ac:0:0 + name: @ac:1:0 } LIST [20] { elements: { COMPREHENSION [21] { - iter_var: @it:1:0 + iter_var: @it:0:0 iter_range: { IDENT [22] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [23] { elements: { @@ -3085,7 +3201,7 @@ CALL [1] { function: _+_ args: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } LIST [27] { elements: { @@ -3102,7 +3218,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3113,7 +3229,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } @@ -3167,14 +3283,14 @@ CALL [1] { function: _==_ args: { COMPREHENSION [14] { - iter_var: @it:0:0 - iter_var2: @it2:0:0 + iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [15] { name: @index1 } } - accu_var: @ac:0:0 + accu_var: @ac:1:0 accu_init: { LIST [16] { elements: { @@ -3189,19 +3305,19 @@ CALL [1] { function: _+_ args: { IDENT [19] { - name: @ac:0:0 + name: @ac:1:0 } LIST [20] { elements: { COMPREHENSION [21] { - iter_var: @it:1:0 - iter_var2: @it2:1:0 + iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [22] { name: @index1 } } - accu_var: @ac:1:0 + accu_var: @ac:0:0 accu_init: { LIST [23] { elements: { @@ -3216,7 +3332,7 @@ CALL [1] { function: _+_ args: { IDENT [26] { - name: @ac:1:0 + name: @ac:0:0 } LIST [27] { elements: { @@ -3233,7 +3349,7 @@ CALL [1] { } result: { IDENT [31] { - name: @ac:1:0 + name: @ac:0:0 } } } @@ -3244,7 +3360,7 @@ CALL [1] { } result: { IDENT [32] { - name: @ac:0:0 + name: @ac:1:0 } } } diff --git a/optimizer/src/test/resources/subexpression_unparsed.baseline b/optimizer/src/test/resources/subexpression_unparsed.baseline index 892a6fbe5..e0edc8987 100644 --- a/optimizer/src/test/resources/subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/subexpression_unparsed.baseline @@ -332,58 +332,73 @@ Test case: NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [[2, 3, 4], [2, 3, 4], [2, 3, 4]] =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3], [2, 3, 4], [@index1, @index1, @index1]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == @index2) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3], [2, 3, 4], [@index1, @index1, @index1]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == @index2) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3], [2, 3, 4], [@index1, @index1, @index1]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == @index2) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3], [2, 3, 4], @index0.map(@it:1:0, @it:1:0 + 1)], @index0.map(@it:0:0, @index2) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3], [2, 3, 4], @index0.map(@it:1:0, @it:1:0 + 1)], @index0.map(@it:0:0, @index2) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3], [2, 3, 4], @index0.map(@it:1:0, @it:1:0 + 1)], @index0.map(@it:0:0, @index2) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3], [2, 3, 4], [@index1, @index1, @index1]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3], [2, 3, 4], [@index1, @index1, @index1]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3], [2, 3, 4], [@index1, @index1, @index1]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3], [2, 3, 4], @index0.map(@it:0:0, @it:0:0 + 1)], @index0.map(@it:1:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3], [2, 3, 4], @index0.map(@it:0:0, @it:0:0 + 1)], @index0.map(@it:1:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3], [2, 3, 4], @index0.map(@it:0:0, @it:0:0 + 1)], @index0.map(@it:1:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3], [2, 3, 4]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == [@index1, @index1, @index1]) Test case: NESTED_MACROS_2 Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] =====> Result: true [BLOCK_COMMON_SUBEXPR_ONLY]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [1, 2, 3], [1], [2], [@index2, @index3]], @index0.map(@it:0:0, @index1.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index4) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:0:0, @index2.filter(@it:1:0, @it:1:0 == @it:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [1, 2, 3], [1], [2], [@index2, @index3]], @index0.map(@it:1:0, @index1.filter(@it:0:0, @it:0:0 == @it:1:0)) == @index4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:1:0, @index2.filter(@it:0:0, @it:0:0 == @it:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:1:0, @index2.filter(@it:0:0, @it:0:0 == @it:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:1:0, @index2.filter(@it:0:0, @it:0:0 == @it:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:1:0, @index2.filter(@it:0:0, @it:0:0 == @it:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.map(@it:1:0, @index2.filter(@it:0:0, @it:0:0 == @it:1:0)) == @index0) [BLOCK_RECURSION_DEPTH_7]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_8]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_9]: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_3 +Source: [1,2,3].map(i, i * 2) + [1,2,3].map(i, i * 2).map(i, i * 2) +=====> +Result: [2, 4, 6, 4, 8, 12] +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], @index0 + @index0.map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3]], @index0.map(@it:0:0, @it:0:0 * 2) + @index0.map(@it:0:0, @it:0:0 * 2).map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3]], @index0.map(@it:0:0, @it:0:0 * 2) + @index0.map(@it:0:0, @it:0:0 * 2).map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3]], @index0.map(@it:0:0, @it:0:0 * 2) + @index0.map(@it:0:0, @it:0:0 * 2).map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], @index0 + @index0.map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], @index0 + @index0.map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], @index0 + @index0.map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], @index0 + @index0.map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], @index0 + @index0.map(@it:1:0, @it:1:0 * 2)) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], @index0 + @index0.map(@it:1:0, @it:1:0 * 2)) + Test case: NESTED_MACROS_COMP_V2_1 Source: [1,2,3].transformList(i, v, [1, 2, 3].transformList(i, v, i + v + 1)) == [[2, 4, 6], [2, 4, 6], [2, 4, 6]] =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index2) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)], @index0.transformList(@it:0:0, @it2:0:0, @index2) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)], @index0.transformList(@it:0:0, @it2:0:0, @index2) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)], @index0.transformList(@it:0:0, @it2:0:0, @index2) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == [@index1, @index1, @index1]) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3], [2, 4, 6], [@index1, @index1, @index1]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == @index2) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)], @index0.transformList(@it:1:0, @it2:1:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)], @index0.transformList(@it:1:0, @it2:1:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3], [2, 4, 6], @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)], @index0.transformList(@it:1:0, @it2:1:0, @index2) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == [@index1, @index1, @index1]) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3], [2, 4, 6]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == [@index1, @index1, @index1]) Test case: NESTED_MACROS_COMP_V2_2 Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> Result: true [BLOCK_COMMON_SUBEXPR_ONLY]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [1, 2, 3], [1], [2], [@index2, @index3]], @index0.transformList(@it:0:0, @it2:0:0, @index1.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index4) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:0:0, @it2:0:0, @index2.filter(@it:1:0, @it:1:0 == @it2:0:0 && @it:0:0 < @it2:0:0)) == @index0) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [1, 2, 3], [1], [2], [@index2, @index3]], @index0.transformList(@it:1:0, @it2:1:0, @index1.filter(@it:0:0, @it:0:0 == @it2:1:0 && @it:1:0 < @it2:1:0)) == @index4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:1:0, @it2:1:0, @index2.filter(@it:0:0, @it:0:0 == @it2:1:0 && @it:1:0 < @it2:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:1:0, @it2:1:0, @index2.filter(@it:0:0, @it:0:0 == @it2:1:0 && @it:1:0 < @it2:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:1:0, @it2:1:0, @index2.filter(@it:0:0, @it:0:0 == @it2:1:0 && @it:1:0 < @it2:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:1:0, @it2:1:0, @index2.filter(@it:0:0, @it:0:0 == @it2:1:0 && @it:1:0 < @it2:1:0)) == @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[1], [2]], [1, 2], [1, 2, 3]], @index1.transformList(@it:1:0, @it2:1:0, @index2.filter(@it:0:0, @it:0:0 == @it2:1:0 && @it:1:0 < @it2:1:0)) == @index0) [BLOCK_RECURSION_DEPTH_7]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_8]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] [BLOCK_RECURSION_DEPTH_9]: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] @@ -392,31 +407,31 @@ Test case: ADJACENT_NESTED_MACROS Source: [1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [1,2,3].map(j, [1, 2, 3].map(j, j + 1)) =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1))], @index1 == @index1) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1))) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1))) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3]], @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1)) == @index0.map(@it:0:0, @index0.map(@it:1:0, @it:1:0 + 1))) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].map(@it:1:0, @it:1:0 + 1), [1, 2, 3].map(@it:0:0, @index0)], @index1 == @index1) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].map(@it:1:0, @it:1:0 + 1), [1, 2, 3].map(@it:0:0, @index0)], @index1 == @index1) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].map(@it:1:0, @it:1:0 + 1), [1, 2, 3].map(@it:0:0, @index0)], @index1 == @index1) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0 + 1))], @index0 == @index0) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0 + 1))], @index0 == @index0) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3].map(@it:0:0, [1, 2, 3].map(@it:1:0, @it:1:0 + 1))], @index0 == @index0) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1))], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1))) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1))) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3]], @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1)) == @index0.map(@it:1:0, @index0.map(@it:0:0, @it:0:0 + 1))) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 + 1), [1, 2, 3].map(@it:1:0, @index0)], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 + 1), [1, 2, 3].map(@it:1:0, @index0)], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 + 1), [1, 2, 3].map(@it:1:0, @index0)], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3].map(@it:1:0, [1, 2, 3].map(@it:0:0, @it:0:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3].map(@it:1:0, [1, 2, 3].map(@it:0:0, @it:0:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3].map(@it:1:0, [1, 2, 3].map(@it:0:0, @it:0:0 + 1))], @index0 == @index0) Test case: ADJACENT_NESTED_MACROS_COMP_V2 Source: [1,2,3].transformMap(i, x, [1, 2, 3].transformMap(i, x, i + x + 1)) == [1,2,3].transformMap(j, y, [1, 2, 3].transformMap(j, y, j + y + 1)) =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index1 == @index1) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1)) == @index0.transformMap(@it:0:0, @it2:0:0, @index0.transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1), [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @index0)], @index1 == @index1) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1), [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @index0)], @index1 == @index1) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @it:1:0 + @it2:1:0 + 1))], @index0 == @index0) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2, 3], @index0.transformMap(@it:1:0, @it2:1:0, @index0.transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:1:0, @it2:1:0, @index0.transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == @index0.transformMap(@it:1:0, @it2:1:0, @index0.transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:1:0, @it2:1:0, @index0.transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == @index0.transformMap(@it:1:0, @it2:1:0, @index0.transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1, 2, 3]], @index0.transformMap(@it:1:0, @it2:1:0, @index0.transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1)) == @index0.transformMap(@it:1:0, @it2:1:0, @index0.transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1), [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @index0)], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1, 2, 3].transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1), [1, 2, 3].transformMap(@it:1:0, @it2:1:0, @index0)], @index1 == @index1) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))], @index0 == @index0) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1, 2, 3].transformMap(@it:1:0, @it2:1:0, [1, 2, 3].transformMap(@it:0:0, @it2:0:0, @it:0:0 + @it2:0:0 + 1))], @index0 == @index0) Test case: INCLUSION_LIST Source: 1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3] @@ -452,31 +467,31 @@ Test case: MACRO_ITER_VAR_NOT_REFERENCED Source: [1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2], [3, 4], [@index1, @index1]], @index0.map(@it:0:0, @index0.map(@it:1:0, @index1)) == [@index2, @index2]) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [3, 4], [@index1, @index1], [@index1], [@index2, @index2]], @index0.map(@it:0:0, @index0.map(@it:1:0, @index1)) == @index4) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.map(@it:1:0, [3, 4]), [@index3]], @index1.map(@it:0:0, @index3) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.map(@it:1:0, [3, 4])], @index1.map(@it:0:0, @index3) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.map(@it:1:0, [3, 4])], @index1.map(@it:0:0, @index2) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.map(@it:1:0, [3, 4])], @index1.map(@it:0:0, @index2) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.map(@it:1:0, [3, 4])], @index1.map(@it:0:0, @index2) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:0:0, @index1.map(@it:1:0, [3, 4])) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:0:0, @index1.map(@it:1:0, [3, 4])) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:0:0, @index1.map(@it:1:0, [3, 4])) == [@index0, @index0]) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2], [3, 4], [@index1, @index1]], @index0.map(@it:1:0, @index0.map(@it:0:0, @index1)) == [@index2, @index2]) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [3, 4], [@index1, @index1], [@index1], [@index2, @index2]], @index0.map(@it:1:0, @index0.map(@it:0:0, @index1)) == @index4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.map(@it:0:0, [3, 4]), [@index3]], @index1.map(@it:1:0, @index3) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.map(@it:0:0, [3, 4])], @index1.map(@it:1:0, @index3) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.map(@it:0:0, [3, 4])], @index1.map(@it:1:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.map(@it:0:0, [3, 4])], @index1.map(@it:1:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.map(@it:0:0, [3, 4])], @index1.map(@it:1:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:1:0, @index1.map(@it:0:0, [3, 4])) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:1:0, @index1.map(@it:0:0, [3, 4])) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.map(@it:1:0, @index1.map(@it:0:0, [3, 4])) == [@index0, @index0]) Test case: MACRO_ITER_VAR2_NOT_REFERENCED Source: [1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]] =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2], [3, 4], [@index1, @index1]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @index1)) == [@index2, @index2]) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [3, 4], [@index1, @index1], [@index1], [@index2, @index2]], @index0.transformList(@it:0:0, @it2:0:0, @index0.transformList(@it:1:0, @it2:1:0, @index1)) == @index4) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.transformList(@it:1:0, @it2:1:0, [3, 4]), [@index3]], @index1.transformList(@it:0:0, @it2:0:0, @index3) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index3) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index2) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index2) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:1:0, @it2:1:0, [3, 4])], @index1.transformList(@it:0:0, @it2:0:0, @index2) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:0:0, @it2:0:0, @index1.transformList(@it:1:0, @it2:1:0, [3, 4])) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:0:0, @it2:0:0, @index1.transformList(@it:1:0, @it2:1:0, [3, 4])) == [@index0, @index0]) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:0:0, @it2:0:0, @index1.transformList(@it:1:0, @it2:1:0, [3, 4])) == [@index0, @index0]) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1, 2], [3, 4], [@index1, @index1]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @index1)) == [@index2, @index2]) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1, 2], [3, 4], [@index1, @index1], [@index1], [@index2, @index2]], @index0.transformList(@it:1:0, @it2:1:0, @index0.transformList(@it:0:0, @it2:0:0, @index1)) == @index4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.transformList(@it:0:0, @it2:0:0, [3, 4]), [@index3]], @index1.transformList(@it:1:0, @it2:1:0, @index3) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[[3, 4], [3, 4]], [1, 2], [[3, 4]], @index1.transformList(@it:0:0, @it2:0:0, [3, 4])], @index1.transformList(@it:1:0, @it2:1:0, @index3) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:0:0, @it2:0:0, [3, 4])], @index1.transformList(@it:1:0, @it2:1:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:0:0, @it2:0:0, [3, 4])], @index1.transformList(@it:1:0, @it2:1:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[[3, 4], [3, 4]], [1, 2], @index1.transformList(@it:0:0, @it2:0:0, [3, 4])], @index1.transformList(@it:1:0, @it2:1:0, @index2) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:1:0, @it2:1:0, @index1.transformList(@it:0:0, @it2:0:0, [3, 4])) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:1:0, @it2:1:0, @index1.transformList(@it:0:0, @it2:0:0, [3, 4])) == [@index0, @index0]) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[[3, 4], [3, 4]], [1, 2]], @index1.transformList(@it:1:0, @it2:1:0, @index1.transformList(@it:0:0, @it2:0:0, [3, 4])) == [@index0, @index0]) Test case: MACRO_SHADOWED_VARIABLE Source: [x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3 @@ -498,11 +513,11 @@ Source: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) =====> Result: [[[foofoo, foofoo, foofoo, foofoo], [foofoo, foofoo, foofoo, foofoo]], [[barbar, barbar, barbar, barbar], [barbar, barbar, barbar, barbar]]] [BLOCK_COMMON_SUBEXPR_ONLY]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([["foo", "bar"]], @index0.map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0]).map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0])) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([["foo", "bar"]], @index0.map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0]).map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0])) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([["foo", "bar"]], @index0.map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0]).map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0])) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([["foo", "bar"]], @index0.map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0]).map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0])) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([["foo", "bar"].map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0])], @index0.map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0])) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([["foo", "bar"]], @index0.map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0]).map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0])) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([["foo", "bar"]], @index0.map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0]).map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0])) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([["foo", "bar"]], @index0.map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0]).map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0])) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([["foo", "bar"]], @index0.map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0]).map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0])) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([["foo", "bar"].map(@it:0:0, [@it:0:0 + @it:0:0, @it:0:0 + @it:0:0])], @index0.map(@it:1:0, [@it:1:0 + @it:1:0, @it:1:0 + @it:1:0])) [BLOCK_RECURSION_DEPTH_6]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) [BLOCK_RECURSION_DEPTH_7]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) [BLOCK_RECURSION_DEPTH_8]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) @@ -528,10 +543,10 @@ Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x =====> Result: {0=[0, [0, foofoo, 0, foofoo]], 1=[2, [2, barbar, 2, barbar]]} [BLOCK_COMMON_SUBEXPR_ONLY]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0]).transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0]).transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0]).transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([["foo", "bar"].transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0])], @index0.transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0]).transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0])) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0]).transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0])) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([["foo", "bar"]], @index0.transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0]).transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0])) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([["foo", "bar"].transformMap(@it:0:0, @it2:0:0, [@it:0:0 + @it:0:0, @it2:0:0 + @it2:0:0])], @index0.transformMap(@it:1:0, @it2:1:0, [@it:1:0 + @it:1:0, @it2:1:0 + @it2:1:0])) [BLOCK_RECURSION_DEPTH_5]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) [BLOCK_RECURSION_DEPTH_6]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) [BLOCK_RECURSION_DEPTH_7]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) From d0b4848a2227a65873edd2015f6c4f1ef62d221c Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 12 Feb 2026 09:03:35 -0800 Subject: [PATCH 072/100] Add a shorthand for declaring policy variables PiperOrigin-RevId: 869243638 --- .../cel/policy/CelPolicyParserBuilder.java | 24 +++++++ .../dev/cel/policy/CelPolicyYamlParser.java | 65 +++++++++++++++++-- .../cel/policy/CelPolicyCompilerImplTest.java | 23 +++++++ .../cel/policy/CelPolicyYamlParserTest.java | 24 +++++++ 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/policy/src/main/java/dev/cel/policy/CelPolicyParserBuilder.java b/policy/src/main/java/dev/cel/policy/CelPolicyParserBuilder.java index ba34a1a60..266264780 100644 --- a/policy/src/main/java/dev/cel/policy/CelPolicyParserBuilder.java +++ b/policy/src/main/java/dev/cel/policy/CelPolicyParserBuilder.java @@ -29,6 +29,30 @@ public interface CelPolicyParserBuilder { @CanIgnoreReturnValue CelPolicyParserBuilder addTagVisitor(TagVisitor tagVisitor); + /** + * Configures the parser to allow for key-value pairs to declare a variable name and expression. + * + *

    For example: + * + *

    {@code
    +   * variables:
    +   * - foo: bar
    +   * - baz: qux
    +   * }
    + * + *

    This is in contrast to the default behavior, which requires the following syntax: + * + *

    {@code
    +   * variables:
    +   * - name: foo
    +   *   expression: bar
    +   * - name: baz
    +   *   expression: qux
    +   * }
    + */ + @CanIgnoreReturnValue + CelPolicyParserBuilder enableSimpleVariables(boolean enable); + /** Builds a new instance of {@link CelPolicyParser}. */ @CheckReturnValue CelPolicyParser build(); diff --git a/policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java b/policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java index b7255c93b..43595c4ab 100644 --- a/policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java +++ b/policy/src/main/java/dev/cel/policy/CelPolicyYamlParser.java @@ -49,6 +49,7 @@ final class CelPolicyYamlParser implements CelPolicyParser { Variable.newBuilder().setExpression(ERROR_VALUE).setName(ERROR_VALUE).build(); private final TagVisitor tagVisitor; + private final boolean enableSimpleVariables; @Override public CelPolicy parse(String policySource) throws CelPolicyValidationException { @@ -58,13 +59,15 @@ public CelPolicy parse(String policySource) throws CelPolicyValidationException @Override public CelPolicy parse(String policySource, String description) throws CelPolicyValidationException { - ParserImpl parser = new ParserImpl(tagVisitor, policySource, description); + ParserImpl parser = + new ParserImpl(tagVisitor, enableSimpleVariables, policySource, description); return parser.parseYaml(); } private static class ParserImpl implements PolicyParserContext { private final TagVisitor tagVisitor; + private final boolean enableSimpleVariables; private final CelPolicySource policySource; private final ParserContext ctx; @@ -336,9 +339,45 @@ public CelPolicy.Variable parseVariable( if (!assertYamlType(ctx, id, node, YamlNodeType.MAP)) { return ERROR_VARIABLE; } + MappingNode variableMap = (MappingNode) node; Variable.Builder builder = Variable.newBuilder(); + if (enableSimpleVariables) { + return parseVariableInline(ctx, id, variableMap, builder); + } + return parseVariableObject(ctx, policyBuilder, id, variableMap, builder); + } + + private Variable parseVariableInline( + PolicyParserContext ctx, long id, MappingNode variableMap, Variable.Builder builder) { + int iterations = 0; + for (NodeTuple nodeTuple : variableMap.getValue()) { + Node keyNode = nodeTuple.getKeyNode(); + long keyId = ctx.collectMetadata(keyNode); + builder + .setName(ctx.newValueString(keyNode)) + .setExpression(ctx.newValueString(nodeTuple.getValueNode())); + iterations++; + + if (iterations > 1) { + ctx.reportError(keyId, "Only one variable may be defined inline"); + } + } + + if (!assertRequiredFields(ctx, id, builder.getMissingRequiredFieldNames())) { + return ERROR_VARIABLE; + } + + return builder.build(); + } + + private Variable parseVariableObject( + PolicyParserContext ctx, + CelPolicy.Builder policyBuilder, + long id, + MappingNode variableMap, + Variable.Builder builder) { for (NodeTuple nodeTuple : variableMap.getValue()) { Node keyNode = nodeTuple.getKeyNode(); long keyId = ctx.collectMetadata(keyNode); @@ -370,8 +409,13 @@ public CelPolicy.Variable parseVariable( return builder.build(); } - private ParserImpl(TagVisitor tagVisitor, String source, String description) { + private ParserImpl( + TagVisitor tagVisitor, + boolean enableSimpleVariables, + String source, + String description) { this.tagVisitor = tagVisitor; + this.enableSimpleVariables = enableSimpleVariables; this.policySource = CelPolicySource.newBuilder(CelCodePointArray.fromString(source)) .setDescription(description) @@ -413,9 +457,11 @@ public ValueString newValueString(Node node) { static final class Builder implements CelPolicyParserBuilder { private TagVisitor tagVisitor; + private boolean enableSimpleVariables; private Builder() { this.tagVisitor = new TagVisitor() {}; + this.enableSimpleVariables = false; } @Override @@ -424,17 +470,24 @@ public CelPolicyParserBuilder addTagVisitor(TagVisitor tagVisitor) { return this; } + @Override + public CelPolicyParserBuilder enableSimpleVariables(boolean enable) { + this.enableSimpleVariables = enable; + return this; + } + @Override public CelPolicyParser build() { - return new CelPolicyYamlParser(tagVisitor); + return new CelPolicyYamlParser(tagVisitor, enableSimpleVariables); } } - static Builder newBuilder() { - return new Builder(); + static CelPolicyParserBuilder newBuilder() { + return new Builder().enableSimpleVariables(false); } - private CelPolicyYamlParser(TagVisitor tagVisitor) { + private CelPolicyYamlParser(TagVisitor tagVisitor, boolean enableSimpleVariables) { this.tagVisitor = checkNotNull(tagVisitor); + this.enableSimpleVariables = enableSimpleVariables; } } diff --git a/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java b/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java index fa0da8a9a..336a392ff 100644 --- a/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java +++ b/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java @@ -302,6 +302,29 @@ public void evaluateYamlPolicy_lateBoundFunction() throws Exception { assertThat(evalResult).isEqualTo("foo" + exampleValue); } + @Test + public void evaluateYamlPolicy_withSimpleVariable() throws Exception { + Cel cel = newCel(); + String policySource = + "name: shorthand_variables_policy\n" + + "rule:\n" + + " variables:\n" + + " - first: 'true'\n" + + " - second: 'false'\n" + + " match:\n" + + " - output: 'variables.first && variables.second'"; + CelPolicyParser parser = + CelPolicyParserFactory.newYamlParserBuilder().enableSimpleVariables(true).build(); + CelPolicy policy = parser.parse(policySource); + + CelAbstractSyntaxTree compiledPolicyAst = + CelPolicyCompilerFactory.newPolicyCompiler(cel).build().compile(policy); + + boolean evalResult = (boolean) cel.createProgram(compiledPolicyAst).eval(); + + assertThat(evalResult).isFalse(); + } + private static final class EvaluablePolicyTestData { private final TestYamlPolicy yamlPolicy; private final PolicyTestCase testCase; diff --git a/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java b/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java index 803d99202..f8327c255 100644 --- a/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java +++ b/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java @@ -148,6 +148,30 @@ public void parseYamlPolicy_withImports() throws Exception { .inOrder(); } + @Test + public void parseYamlPolicy_withSimpleVariable_multipleInlinedVariables() { + String policySource = + "name: shorthand_variables_policy\n" + + "rule:\n" + + " variables:\n" + + " - first: 'true'\n" + + " second: 'false'\n" + + " match:\n" + + " - condition: 'variables.my_var'\n" + + " output: 'true'\n"; + CelPolicyParser parser = + CelPolicyParserFactory.newYamlParserBuilder().enableSimpleVariables(true).build(); + + CelPolicyValidationException e = + assertThrows(CelPolicyValidationException.class, () -> parser.parse(policySource)); + assertThat(e) + .hasMessageThat() + .contains( + "ERROR: :5:7: Only one variable may be defined inline\n" + + " | second: 'false'\n" + + " | ......^"); + } + @Test public void parseYamlPolicy_errors(@TestParameter PolicyParseErrorTestCase testCase) { CelPolicyValidationException e = From 21fbb54b4b5ae2849c3791abaef8ffdbf21a39df Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 12 Feb 2026 17:10:39 -0800 Subject: [PATCH 073/100] Add InliningOptimizer PiperOrigin-RevId: 869451703 --- optimizer/optimizers/BUILD.bazel | 5 + .../dev/cel/optimizer/optimizers/BUILD.bazel | 27 ++ .../optimizers/InliningOptimizer.java | 233 ++++++++++++++++++ .../optimizers/SubexpressionOptimizer.java | 2 +- .../dev/cel/optimizer/optimizers/BUILD.bazel | 1 + .../optimizers/InliningOptimizerTest.java | 224 +++++++++++++++++ 6 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java create mode 100644 optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java diff --git a/optimizer/optimizers/BUILD.bazel b/optimizer/optimizers/BUILD.bazel index 21241241f..26d98c574 100644 --- a/optimizer/optimizers/BUILD.bazel +++ b/optimizer/optimizers/BUILD.bazel @@ -14,3 +14,8 @@ java_library( name = "common_subexpression_elimination", exports = ["//optimizer/src/main/java/dev/cel/optimizer/optimizers:common_subexpression_elimination"], ) + +java_library( + name = "inlining", + exports = ["//optimizer/src/main/java/dev/cel/optimizer/optimizers:inlining"], +) 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 d68842315..7984cf3ba 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -71,6 +71,33 @@ java_library( ], ) +java_library( + name = "inlining", + srcs = [ + "InliningOptimizer.java", + ], + tags = [ + ], + deps = [ + "//:auto_value", + "//bundle:cel", + "//common:cel_ast", + "//common:mutable_ast", + "//common:operator", + "//common/ast", + "//common/ast:mutable_expr", + "//common/navigation:mutable_navigation", + "//common/types", + "//common/types:type_providers", + "//common/values", + "//optimizer:ast_optimizer", + "//optimizer:mutable_ast", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "default_optimizer_constants", srcs = [ diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java new file mode 100644 index 000000000..ca8f0801f --- /dev/null +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java @@ -0,0 +1,233 @@ +// 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.optimizer.optimizers; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import dev.cel.bundle.Cel; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelMutableAst; +import dev.cel.common.Operator; +import dev.cel.common.ast.CelConstant; +import dev.cel.common.ast.CelExpr.ExprKind.Kind; +import dev.cel.common.ast.CelMutableExpr; +import dev.cel.common.ast.CelMutableExpr.CelMutableCall; +import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension; +import dev.cel.common.navigation.CelNavigableMutableAst; +import dev.cel.common.navigation.CelNavigableMutableExpr; +import dev.cel.common.types.CelKind; +import dev.cel.common.types.CelType; +import dev.cel.common.types.SimpleType; +import dev.cel.common.values.NullValue; +import dev.cel.optimizer.AstMutator; +import dev.cel.optimizer.CelAstOptimizer; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * Performs optimization for inlining variables within function calls and select statements with + * their associated AST. + */ +public final class InliningOptimizer implements CelAstOptimizer { + + private final ImmutableList inlineVariables; + private final AstMutator astMutator; + + public static InliningOptimizer newInstance(InlineVariable... inlineVariables) { + return newInstance(InliningOptions.newBuilder().build(), ImmutableList.copyOf(inlineVariables)); + } + + public static InliningOptimizer newInstance( + InliningOptions options, InlineVariable... inlineVariables) { + return newInstance(options, ImmutableList.copyOf(inlineVariables)); + } + + public static InliningOptimizer newInstance( + InliningOptions options, Iterable inlineVariables) { + return new InliningOptimizer(options, ImmutableList.copyOf(inlineVariables)); + } + + @Override + public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) { + CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast); + for (InlineVariable inlineVariable : inlineVariables) { + ImmutableList inlinableExprs = + CelNavigableMutableAst.fromAst(mutableAst) + .getRoot() + .allNodes() + .filter(node -> canInline(node, inlineVariable.name())) + .collect(toImmutableList()); + + for (CelNavigableMutableExpr inlinableExpr : inlinableExprs) { + CelMutableAst inlineVariableAst = CelMutableAst.fromCelAst(inlineVariable.ast()); + CelMutableExpr replacementExpr = inlineVariableAst.expr(); + + if (inlinableExpr.getKind().equals(Kind.SELECT) + && inlinableExpr.expr().select().testOnly()) { + replacementExpr = rewritePresenceExpr(inlineVariable, replacementExpr); + } + + mutableAst = + astMutator.replaceSubtree( + mutableAst, + CelMutableAst.of(replacementExpr, inlineVariableAst.source()), + inlinableExpr.id()); + } + } + + return OptimizationResult.create(astMutator.renumberIdsConsecutively(mutableAst).toParsedAst()); + } + + private static CelMutableExpr rewritePresenceExpr( + InlineVariable inlineVariable, CelMutableExpr replacementExpr) { + if (replacementExpr.getKind().equals(Kind.SELECT)) { + // Preserve testOnly property for Select replacements (has(A) -> has(B)) + replacementExpr.select().setTestOnly(true); + return replacementExpr; + } + + CelType replacementType = + inlineVariable + .ast() + .getType(replacementExpr.id()) + .orElseThrow(() -> new NoSuchElementException("Type is not present.")); + if (isSizerType(replacementType)) { + // has(X) -> X.size() != 0 + return CelMutableExpr.ofCall( + CelMutableCall.create( + Operator.NOT_EQUALS.getFunction(), + CelMutableExpr.ofCall(CelMutableCall.create(replacementExpr, "size")), + CelMutableExpr.ofConstant(CelConstant.ofValue(0)))); + } else if (replacementType.isAssignableFrom(SimpleType.NULL_TYPE)) { + // has(X) -> X != null + // This covers well-known wrapper types + return CelMutableExpr.ofCall( + CelMutableCall.create( + Operator.NOT_EQUALS.getFunction(), + replacementExpr, + CelMutableExpr.ofConstant(CelConstant.ofValue(NullValue.NULL_VALUE)))); + } + + throw new IllegalArgumentException( + String.format( + "Unable to inline expression type %s into presence test", replacementType.name())); + } + + private static boolean isSizerType(CelType type) { + return type.kind().equals(CelKind.LIST) + || type.kind().equals(CelKind.MAP) + || type.equals(SimpleType.STRING) + || type.equals(SimpleType.BYTES); + } + + private static boolean canInline(CelNavigableMutableExpr node, String identifier) { + boolean matches = maybeToQualifiedName(node).map(name -> name.equals(identifier)).orElse(false); + + if (!matches) { + return false; + } + + for (CelNavigableMutableExpr p = node.parent().orElse(null); + p != null; + p = p.parent().orElse(null)) { + if (p.getKind() != Kind.COMPREHENSION) { + continue; + } + + CelMutableComprehension comp = p.expr().comprehension(); + boolean shadows = + Stream.of(comp.iterVar(), comp.iterVar2(), comp.accuVar()).anyMatch(identifier::equals); + + if (shadows) { + return false; + } + } + + return true; + } + + private static Optional maybeToQualifiedName(CelNavigableMutableExpr node) { + if (node.getKind().equals(Kind.IDENT)) { + return Optional.of(node.expr().ident().name()); + } + + if (node.getKind().equals(Kind.SELECT)) { + return node.children() + .findFirst() + .flatMap(InliningOptimizer::maybeToQualifiedName) + .map(operandName -> operandName + "." + node.expr().select().field()); + } + + return Optional.empty(); + } + + /** Represents a variable to be inlined. */ + @AutoValue + public abstract static class InlineVariable { + public abstract String name(); + + public abstract CelAbstractSyntaxTree ast(); + + /** + * Creates a new {@link InlineVariable} with the given name and AST. + * + *

    The name must be a simple identifier or a qualified name (e.g. "a.b.c") and cannot be an + * internal variable (starting with @). + */ + public static InlineVariable of(String name, CelAbstractSyntaxTree ast) { + if (name.startsWith("@")) { + throw new IllegalArgumentException("Internal variables cannot be inlined: " + name); + } + return new AutoValue_InliningOptimizer_InlineVariable(name, ast); + } + } + + /** Options to configure how Inlining behaves. */ + @AutoValue + public abstract static class InliningOptions { + public abstract int maxIterationLimit(); + + /** Builder for configuring the {@link InliningOptimizer.InliningOptions}. */ + @AutoValue.Builder + public abstract static class Builder { + + /** + * Limit the number of iteration while inlining variables. An exception is thrown if the + * iteration count exceeds the set value. + */ + public abstract InliningOptions.Builder maxIterationLimit(int value); + + public abstract InliningOptimizer.InliningOptions build(); + + Builder() {} + } + + /** Returns a new options builder with recommended defaults pre-configured. */ + public static InliningOptimizer.InliningOptions.Builder newBuilder() { + return new AutoValue_InliningOptimizer_InliningOptions.Builder().maxIterationLimit(400); + } + + InliningOptions() {} + } + + private InliningOptimizer( + InliningOptions options, ImmutableList inlineVariables) { + this.inlineVariables = inlineVariables; + this.astMutator = AstMutator.newInstance(options.maxIterationLimit()); + } +} diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java index 46a063a8d..ce9a5dc77 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java @@ -91,7 +91,7 @@ * } * */ -public class SubexpressionOptimizer implements CelAstOptimizer { +public final class SubexpressionOptimizer implements CelAstOptimizer { private static final SubexpressionOptimizer INSTANCE = new SubexpressionOptimizer(SubexpressionOptimizerOptions.newBuilder().build()); diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel index acc8a4c3f..d91e48f54 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -27,6 +27,7 @@ java_library( "//optimizer:optimizer_builder", "//optimizer/optimizers:common_subexpression_elimination", "//optimizer/optimizers:constant_folding", + "//optimizer/optimizers:inlining", "//parser:macro", "//parser:unparser", "//runtime", diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java new file mode 100644 index 000000000..ea1cbc1d0 --- /dev/null +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java @@ -0,0 +1,224 @@ +// 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.optimizer.optimizers; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.bundle.Cel; +import dev.cel.bundle.CelFactory; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; +import dev.cel.common.CelOptions; +import dev.cel.common.CelSource; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.extensions.CelExtensions; +import dev.cel.optimizer.CelOptimizationException; +import dev.cel.optimizer.CelOptimizer; +import dev.cel.optimizer.CelOptimizerFactory; +import dev.cel.optimizer.optimizers.InliningOptimizer.InlineVariable; +import dev.cel.optimizer.optimizers.InliningOptimizer.InliningOptions; +import dev.cel.optimizer.optimizers.SubexpressionOptimizer.SubexpressionOptimizerOptions; +import dev.cel.parser.CelStandardMacro; +import dev.cel.parser.CelUnparserFactory; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class InliningOptimizerTest { + + private static final Cel CEL = + CelFactory.standardCelBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .setContainer(CelContainer.ofName("google.expr.proto3.test")) + .addFileTypes(TestAllTypes.getDescriptor().getFile()) + .addCompilerLibraries(CelExtensions.bindings()) + .addVar("int_var", SimpleType.INT) + .addVar("dyn_var", SimpleType.DYN) + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .addVar("wrapper_var", StructTypeReference.create("google.protobuf.Int64Value")) + .addVar( + "child", + StructTypeReference.create(TestAllTypes.NestedMessage.getDescriptor().getFullName())) + .addVar("shadowed_ident", SimpleType.INT) + .setOptions(CelOptions.current().populateMacroCalls(true).build()) + .build(); + + @Test + public void inlining_success(@TestParameter SuccessTestCase testCase) throws Exception { + CelAbstractSyntaxTree astToInline = CEL.compile(testCase.source).getAst(); + CelAbstractSyntaxTree replacementAst = CEL.compile(testCase.replacement).getAst(); + + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(CEL) + .addAstOptimizers( + InliningOptimizer.newInstance( + InlineVariable.of(testCase.inlineVarName, replacementAst))) + .build(); + + CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline); + + String unparsed = CelUnparserFactory.newUnparser().unparse(optimized); + assertThat(unparsed).isEqualTo(testCase.expected); + } + + @Test + public void inlining_noop(@TestParameter NoOpTestCase testCase) throws Exception { + CelAbstractSyntaxTree astToInline = CEL.compile(testCase.source).getAst(); + CelAbstractSyntaxTree replacementAst = CEL.compile(testCase.replacement).getAst(); + + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(CEL) + .addAstOptimizers( + InliningOptimizer.newInstance(InlineVariable.of(testCase.varName, replacementAst))) + .build(); + + CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline); + + String unparsed = CelUnparserFactory.newUnparser().unparse(optimized); + assertThat(unparsed).isEqualTo(testCase.source); + } + + private enum SuccessTestCase { + CONSTANT( + /* source= */ "int_var + 2 + int_var", + /* inlineVarName= */ "int_var", + /* replacementExpr= */ "1", + /* expected= */ "1 + 2 + 1"), + REPEATED( + /* source= */ "dyn_var + [dyn_var]", + /* inlineVarName= */ "dyn_var", + /* replacementExpr= */ "dyn([1, 2])", + /* expected= */ "dyn([1, 2]) + [dyn([1, 2])]"), + SELECT_WITH_MACRO( + /* source= */ "has(msg.single_any.processing_purpose) ?" + + " msg.single_any.processing_purpose.map(i, i * 2)[0] : 42", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "[1,2,3].map(i, i * 2)", + /* expected= */ "([1, 2, 3].map(i, i * 2).size() != 0) ? ([1, 2, 3].map(i, i * 2).map(i, i" + + " * 2)[0]) : 42"), + PRESENCE_WITH_SELECT_EXPR( + /* source= */ "has(msg.single_any)", + /* inlineVarName= */ "msg.single_any", + /* replacementExpr= */ "msg.single_int64_wrapper", + /* expected= */ "has(msg.single_int64_wrapper)"), + PRESENCE_WITH_IDENT_NOT_NULL_REWRITE( + /* source= */ "has(msg.single_int64_wrapper)", + /* inlineVarName= */ "msg.single_int64_wrapper", + /* replacementExpr= */ "wrapper_var", + /* expected= */ "wrapper_var != null"), + PRESENCE_WITH_LIST_SIZE_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "[1, 2, 3]", + /* expected= */ "[1, 2, 3].size() != 0"), + NESTED_SELECT( + /* source= */ "msg.standalone_message.bb", + /* inlineVarName= */ "msg.standalone_message", + /* replacementExpr= */ "child", + /* expected= */ "child.bb"), + ; + + private final String source; + private final String inlineVarName; + private final String replacement; + private final String expected; + + SuccessTestCase(String source, String inlineVarName, String replacementExpr, String expected) { + this.source = source; + this.inlineVarName = inlineVarName; + this.replacement = replacementExpr; + this.expected = expected; + } + } + + private enum NoOpTestCase { + NO_INLINE_ITER_VAR("[0].exists(shadowed_ident, shadowed_ident == 0)", "shadowed_ident", "1"), + NO_INLINE_BIND_VAR("cel.bind(shadowed_ident, 2, shadowed_ident + 1)", "shadowed_ident", "1"), + ; + + private final String source; + private final String varName; + private final String replacement; + + NoOpTestCase(String source, String varName, String replacement) { + this.source = source; + this.varName = varName; + this.replacement = replacement; + } + } + + @Test + public void inline_exceededIterationLimit_throws() throws Exception { + String expression = "int_var + int_var + int_var"; + CelAbstractSyntaxTree astToInline = CEL.compile(expression).getAst(); + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(CEL) + .addAstOptimizers( + InliningOptimizer.newInstance( + InliningOptions.newBuilder().maxIterationLimit(2).build(), + InlineVariable.of("int_var", CEL.compile("1").getAst()))) + .build(); + + CelOptimizationException e = + assertThrows(CelOptimizationException.class, () -> optimizer.optimize(astToInline)); + assertThat(e).hasMessageThat().contains("Max iteration count reached."); + } + + @Test + public void inlineVariableDecl_internalVar_throws() throws Exception { + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> + InlineVariable.of( + "@internal_var", + CelAbstractSyntaxTree.newParsedAst( + CelExpr.ofNotSet(0L), CelSource.newBuilder().build()))); + assertThat(e).hasMessageThat().contains("Internal variables cannot be inlined: @internal_var"); + } + + @Test + public void inline_then_cse() throws Exception { + String source = + "has(msg.single_any.processing_purpose) ? " + + "msg.single_any.processing_purpose.map(i, i * 2)[0] : 42"; + CelAbstractSyntaxTree astToInline = CEL.compile(source).getAst(); + + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(CEL) + .addAstOptimizers( + InliningOptimizer.newInstance( + InlineVariable.of( + "msg.single_any.processing_purpose", + CEL.compile("[1,2,3].map(i, i * 2)").getAst())), + SubexpressionOptimizer.newInstance( + SubexpressionOptimizerOptions.newBuilder().populateMacroCalls(true).build())) + .build(); + + CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline); + + String unparsed = CelUnparserFactory.newUnparser().unparse(optimized); + assertThat(unparsed) + .isEqualTo( + "cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], " + + "(@index0.size() != 0) ? (@index0.map(@it:1:0, @it:1:0 * 2)[0]) : 42)"); + } +} From f10f91cef8bafd0084c3e18d505ae4d508a6cbd9 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 12 Feb 2026 17:50:41 -0800 Subject: [PATCH 074/100] Inline presence tests for literals with zero value tests PiperOrigin-RevId: 869463359 --- .../optimizers/InliningOptimizer.java | 71 +++++++++++++++---- .../optimizers/InliningOptimizerTest.java | 49 ++++++++++++- 2 files changed, 105 insertions(+), 15 deletions(-) diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java index ca8f0801f..65e90c139 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java @@ -18,6 +18,7 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; +import com.google.common.primitives.UnsignedLong; import dev.cel.bundle.Cel; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelMutableAst; @@ -27,6 +28,7 @@ import dev.cel.common.ast.CelMutableExpr; import dev.cel.common.ast.CelMutableExpr.CelMutableCall; import dev.cel.common.ast.CelMutableExpr.CelMutableComprehension; +import dev.cel.common.ast.CelMutableExpr.CelMutableStruct; import dev.cel.common.navigation.CelNavigableMutableAst; import dev.cel.common.navigation.CelNavigableMutableExpr; import dev.cel.common.types.CelKind; @@ -35,6 +37,7 @@ import dev.cel.common.values.NullValue; import dev.cel.optimizer.AstMutator; import dev.cel.optimizer.CelAstOptimizer; +import java.util.ArrayList; import java.util.NoSuchElementException; import java.util.Optional; import java.util.stream.Stream; @@ -106,26 +109,66 @@ private static CelMutableExpr rewritePresenceExpr( .ast() .getType(replacementExpr.id()) .orElseThrow(() -> new NoSuchElementException("Type is not present.")); + if (isSizerType(replacementType)) { // has(X) -> X.size() != 0 - return CelMutableExpr.ofCall( - CelMutableCall.create( - Operator.NOT_EQUALS.getFunction(), - CelMutableExpr.ofCall(CelMutableCall.create(replacementExpr, "size")), - CelMutableExpr.ofConstant(CelConstant.ofValue(0)))); - } else if (replacementType.isAssignableFrom(SimpleType.NULL_TYPE)) { + return createNotEquals( + CelMutableExpr.ofCall(CelMutableCall.create(replacementExpr, "size")), + CelMutableExpr.ofConstant(CelConstant.ofValue(0))); + } + + if (replacementType.isAssignableFrom(SimpleType.NULL_TYPE)) { // has(X) -> X != null // This covers well-known wrapper types - return CelMutableExpr.ofCall( - CelMutableCall.create( - Operator.NOT_EQUALS.getFunction(), - replacementExpr, - CelMutableExpr.ofConstant(CelConstant.ofValue(NullValue.NULL_VALUE)))); + + return createNotEquals( + replacementExpr, CelMutableExpr.ofConstant(CelConstant.ofValue(NullValue.NULL_VALUE))); } - throw new IllegalArgumentException( - String.format( - "Unable to inline expression type %s into presence test", replacementType.name())); + return getZeroValueExpr(replacementType, replacementExpr) + .map(zeroValue -> createNotEquals(replacementExpr, zeroValue)) + .orElseThrow( + () -> + new IllegalArgumentException( + String.format( + "Unable to inline expression type %s into presence test", + replacementType.name()))); + } + + private static Optional getZeroValueExpr( + CelType type, CelMutableExpr replacementExpr) { + switch (type.kind()) { + case BOOL: + return Optional.of(CelMutableExpr.ofConstant(CelConstant.ofValue(false))); + case DOUBLE: + return Optional.of(CelMutableExpr.ofConstant(CelConstant.ofValue(0.0d))); + case INT: + return Optional.of(CelMutableExpr.ofConstant(CelConstant.ofValue(0))); + case UINT: + return Optional.of(CelMutableExpr.ofConstant(CelConstant.ofValue(UnsignedLong.ZERO))); + case TIMESTAMP: + return Optional.of( + CelMutableExpr.ofCall( + CelMutableCall.create( + "timestamp", CelMutableExpr.ofConstant(CelConstant.ofValue(0))))); + case DURATION: + return Optional.of( + CelMutableExpr.ofCall( + CelMutableCall.create( + "duration", CelMutableExpr.ofConstant(CelConstant.ofValue("0"))))); + case STRUCT: + return Optional.of( + CelMutableExpr.ofStruct( + CelMutableStruct.create( + replacementExpr.struct().messageName(), new ArrayList<>()))); + default: + return Optional.empty(); + } + } + + private static CelMutableExpr createNotEquals(CelMutableExpr left, CelMutableExpr right) { + return CelMutableExpr.ofCall( + CelMutableCall.create(Operator.NOT_EQUALS.getFunction(), left, right)); } private static boolean isSizerType(CelType type) { diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java index ea1cbc1d0..210770448 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java @@ -58,7 +58,8 @@ public class InliningOptimizerTest { "child", StructTypeReference.create(TestAllTypes.NestedMessage.getDescriptor().getFullName())) .addVar("shadowed_ident", SimpleType.INT) - .setOptions(CelOptions.current().populateMacroCalls(true).build()) + .setOptions( + CelOptions.current().populateMacroCalls(true).enableTimestampEpoch(true).build()) .build(); @Test @@ -129,6 +130,52 @@ private enum SuccessTestCase { /* inlineVarName= */ "msg.single_any.processing_purpose", /* replacementExpr= */ "[1, 2, 3]", /* expected= */ "[1, 2, 3].size() != 0"), + PRESENCE_WITH_INT_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "1", + /* expected= */ "1 != 0"), + PRESENCE_WITH_UINT_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "1u", + /* expected= */ "1u != 0u"), + PRESENCE_WITH_DOUBLE_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "1.5", + /* expected= */ "1.5 != 0.0"), + PRESENCE_WITH_BOOL_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "true", + /* expected= */ "true != false"), + PRESENCE_WITH_STRING_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "'foo'", + /* expected= */ "\"foo\".size() != 0"), + PRESENCE_WITH_BYTES_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "b'abc'", + /* expected= */ "b\"\\141\\142\\143\".size() != 0"), + PRESENCE_WITH_TIMESTAMP_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "timestamp(1)", + /* expected= */ "timestamp(1) != timestamp(0)"), + PRESENCE_WITH_DURATION_LITERAL_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "duration('1h')", + /* expected= */ "duration(\"1h\") != duration(\"0\")"), + PRESENCE_WITH_PROTOBUF_MESSAGE_REWRITE( + /* source= */ "has(msg.single_any.processing_purpose)", + /* inlineVarName= */ "msg.single_any.processing_purpose", + /* replacementExpr= */ "cel.expr.conformance.proto3.TestAllTypes{single_int64: 1}", + /* expected= */ "cel.expr.conformance.proto3.TestAllTypes{single_int64: 1} !=" + + " cel.expr.conformance.proto3.TestAllTypes{}"), NESTED_SELECT( /* source= */ "msg.standalone_message.bb", /* inlineVarName= */ "msg.standalone_message", From f748e2d0d0ce210956e20b19b7cb2fd105c9df4f Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 12 Feb 2026 20:39:56 -0800 Subject: [PATCH 075/100] Add another newInstance overload accepting list of inlineVariables PiperOrigin-RevId: 869514285 --- .../java/dev/cel/optimizer/optimizers/InliningOptimizer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java index 65e90c139..0911a1674 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java @@ -52,6 +52,10 @@ public final class InliningOptimizer implements CelAstOptimizer { private final AstMutator astMutator; public static InliningOptimizer newInstance(InlineVariable... inlineVariables) { + return newInstance(ImmutableList.copyOf(inlineVariables)); + } + + public static InliningOptimizer newInstance(Iterable inlineVariables) { return newInstance(InliningOptions.newBuilder().build(), ImmutableList.copyOf(inlineVariables)); } From 38fe14123bd8e6df94de3478b6fb078b3c903f4b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 13 Feb 2026 15:44:44 -0800 Subject: [PATCH 076/100] Document variable dependency behavior in InliningOptimizer PiperOrigin-RevId: 869919231 --- .../optimizers/InliningOptimizer.java | 37 +++++++++++++++++- .../optimizers/InliningOptimizerTest.java | 39 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java index 0911a1674..e4051f82f 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/InliningOptimizer.java @@ -38,6 +38,7 @@ import dev.cel.optimizer.AstMutator; import dev.cel.optimizer.CelAstOptimizer; import java.util.ArrayList; +import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; import java.util.stream.Stream; @@ -51,21 +52,53 @@ public final class InliningOptimizer implements CelAstOptimizer { private final ImmutableList inlineVariables; private final AstMutator astMutator; + /** + * Creates a new {@code InliningOptimizer} with one or more {@link InlineVariable}s. + * + *

    Note that the variables to be inlined can be a dependency to one other based on the supplied + * ordering. This allows for recursive inlining where a replacement value might itself contain + * variables that need to be inlined. + * + *

    For example, given a source expression {@code "a + b"} and inline variables in the following + * order: + * + *

      + *
    • {@code {a: b, b: 2}}, result: {@code 2 + 2}. + *
    • {@code {b: 2, a: b}}, result: {@code b + 2}. + *
    + */ public static InliningOptimizer newInstance(InlineVariable... inlineVariables) { return newInstance(ImmutableList.copyOf(inlineVariables)); } - public static InliningOptimizer newInstance(Iterable inlineVariables) { + /** + * Creates a new {@code InliningOptimizer}. + * + * @see #newInstance(InlineVariable...) + */ + public static InliningOptimizer newInstance(List inlineVariables) { return newInstance(InliningOptions.newBuilder().build(), ImmutableList.copyOf(inlineVariables)); } + /** + * Creates a new {@code InliningOptimizer}. + * + * @see #newInstance(InlineVariable...) + * @param options {@link InliningOptions} to customize the inlining behavior with. + */ public static InliningOptimizer newInstance( InliningOptions options, InlineVariable... inlineVariables) { return newInstance(options, ImmutableList.copyOf(inlineVariables)); } + /** + * Creates a new {@code InliningOptimizer}. + * + * @see #newInstance(InlineVariable...) + * @param options {@link InliningOptions} to customize the inlining behavior with. + */ public static InliningOptimizer newInstance( - InliningOptions options, Iterable inlineVariables) { + InliningOptions options, List inlineVariables) { return new InliningOptimizer(options, ImmutableList.copyOf(inlineVariables)); } diff --git a/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java b/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java index 210770448..7930a03d8 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/InliningOptimizerTest.java @@ -268,4 +268,43 @@ public void inline_then_cse() throws Exception { "cel.@block([[1, 2, 3].map(@it:0:0, @it:0:0 * 2)], " + "(@index0.size() != 0) ? (@index0.map(@it:1:0, @it:1:0 * 2)[0]) : 42)"); } + + @Test + public void allowInliningDependentVariables_inOrder_dependentVariablesInlined() throws Exception { + String source = "int_var + dyn_var"; + CelAbstractSyntaxTree astToInline = CEL.compile(source).getAst(); + + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(CEL) + .addAstOptimizers( + InliningOptimizer.newInstance( + InlineVariable.of("int_var", CEL.compile("dyn_var").getAst()), + InlineVariable.of("dyn_var", CEL.compile("2").getAst()))) + .build(); + + CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline); + + String unparsed = CelUnparserFactory.newUnparser().unparse(optimized); + assertThat(unparsed).isEqualTo("2 + 2"); + } + + @Test + public void allowInliningDependentVariables_reverseOrder_inliningIsIndependent() + throws Exception { + String source = "int_var + dyn_var"; + CelAbstractSyntaxTree astToInline = CEL.compile(source).getAst(); + + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(CEL) + .addAstOptimizers( + InliningOptimizer.newInstance( + InlineVariable.of("dyn_var", CEL.compile("2").getAst()), + InlineVariable.of("int_var", CEL.compile("dyn_var").getAst()))) + .build(); + + CelAbstractSyntaxTree optimized = optimizer.optimize(astToInline); + + String unparsed = CelUnparserFactory.newUnparser().unparse(optimized); + assertThat(unparsed).isEqualTo("dyn_var + 2"); + } } From 1916266a430620637877ed232b83fb61af14770e Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 13 Feb 2026 16:44:19 -0800 Subject: [PATCH 077/100] Fix function dispatch failure error messages to be properly formatted PiperOrigin-RevId: 869938871 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 9 ++++--- .../dev/cel/runtime/planner/EvalHelpers.java | 26 +++++++++++++++++++ .../runtime/planner/EvalLateBoundCall.java | 8 +----- .../dev/cel/runtime/planner/EvalUnary.java | 8 +----- .../cel/runtime/planner/EvalVarArgsCall.java | 8 +----- .../cel/runtime/planner/EvalZeroArity.java | 8 +----- .../cel/runtime/planner/PlannedProgram.java | 6 ++++- .../cel/runtime/PlannerInterpreterTest.java | 6 ----- .../runtime/planner/ProgramPlannerTest.java | 6 +++-- 9 files changed, 44 insertions(+), 41 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 59f39fc91..99273f2fd 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -199,10 +199,10 @@ java_library( name = "eval_zero_arity", srcs = ["EvalZeroArity.java"], deps = [ + ":eval_helpers", ":execution_frame", ":planned_interpretable", "//common/values", - "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", @@ -217,7 +217,6 @@ java_library( ":execution_frame", ":planned_interpretable", "//common/values", - "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", @@ -232,7 +231,6 @@ java_library( ":execution_frame", ":planned_interpretable", "//common/values", - "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", @@ -248,7 +246,6 @@ java_library( ":planned_interpretable", "//common/exceptions:overload_not_found", "//common/values", - "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", @@ -377,7 +374,11 @@ java_library( "//common:error_codes", "//common/exceptions:runtime_exception", "//common/values", + "//common/values:cel_value", + "//runtime:evaluation_exception", "//runtime:interpretable", + "//runtime:resolved_overload", + "@maven//:com_google_guava_guava", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index 7923682ee..08f4fa8a8 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -14,9 +14,14 @@ package dev.cel.runtime.planner; +import com.google.common.base.Joiner; import dev.cel.common.CelErrorCode; import dev.cel.common.exceptions.CelRuntimeException; +import dev.cel.common.values.CelValue; +import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.ErrorValue; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelResolvedOverload; import dev.cel.runtime.GlobalResolver; final class EvalHelpers { @@ -51,5 +56,26 @@ static Object evalStrictly( } } + static Object dispatch(CelResolvedOverload overload, CelValueConverter valueConverter, Object[] args) throws CelEvaluationException { + try { + Object result = overload.getDefinition().apply(args); + Object runtimeValue = valueConverter.toRuntimeValue(result); + if (runtimeValue instanceof CelValue) { + return valueConverter.unwrap((CelValue) runtimeValue); + } + + return runtimeValue; + } catch (CelRuntimeException e) { + // Function dispatch failure that's already been handled -- just propagate. + throw e; + } catch (RuntimeException e) { + // Unexpected function dispatch failure. + throw new IllegalArgumentException(String.format( + "Function '%s' failed with arg(s) '%s'", + overload.getOverloadId(), Joiner.on(", ").join(args)), + e); + } + } + private EvalHelpers() {} } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java index dedef58ca..a22ba8e94 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalLateBoundCall.java @@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableList; import dev.cel.common.exceptions.CelOverloadNotFoundException; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; @@ -48,12 +47,7 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval .findOverload(functionName, overloadIds, argVals) .orElseThrow(() -> new CelOverloadNotFoundException(functionName, overloadIds)); - Object result = resolvedOverload.getDefinition().apply(argVals); - Object runtimeValue = celValueConverter.toRuntimeValue(result); - if (runtimeValue instanceof CelValue) { - return celValueConverter.unwrap((CelValue) runtimeValue); - } - return runtimeValue; + return EvalHelpers.dispatch(resolvedOverload, celValueConverter, argVals); } static EvalLateBoundCall create( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java index e936c511b..c715ff032 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalUnary.java @@ -17,7 +17,6 @@ import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; @@ -37,12 +36,7 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval : evalNonstrictly(arg, resolver, frame); Object[] arguments = new Object[] {argVal}; - Object result = resolvedOverload.getDefinition().apply(arguments); - Object runtimeValue = celValueConverter.toRuntimeValue(result); - if (runtimeValue instanceof CelValue) { - return celValueConverter.unwrap((CelValue) runtimeValue); - } - return runtimeValue; + return EvalHelpers.dispatch(resolvedOverload, celValueConverter, arguments); } static EvalUnary create( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java index a7f2b643d..9f14f8bf9 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalVarArgsCall.java @@ -17,7 +17,6 @@ import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly; import static dev.cel.runtime.planner.EvalHelpers.evalStrictly; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; @@ -43,12 +42,7 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval : evalNonstrictly(arg, resolver, frame); } - Object result = resolvedOverload.getDefinition().apply(argVals); - Object runtimeValue = celValueConverter.toRuntimeValue(result); - if (runtimeValue instanceof CelValue) { - return celValueConverter.unwrap((CelValue) runtimeValue); - } - return runtimeValue; + return EvalHelpers.dispatch(resolvedOverload, celValueConverter, argVals); } static EvalVarArgsCall create( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java index 1a4218860..5b3138207 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalZeroArity.java @@ -14,7 +14,6 @@ package dev.cel.runtime.planner; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelResolvedOverload; @@ -28,12 +27,7 @@ final class EvalZeroArity extends PlannedInterpretable { @Override public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { - Object result = resolvedOverload.getDefinition().apply(EMPTY_ARRAY); - Object runtimeValue = celValueConverter.toRuntimeValue(result); - if (runtimeValue instanceof CelValue) { - return celValueConverter.unwrap((CelValue) runtimeValue); - } - return runtimeValue; + return EvalHelpers.dispatch(resolvedOverload, celValueConverter, EMPTY_ARRAY); } static EvalZeroArity create( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index 22aff43c8..9fcd05447 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -115,7 +115,11 @@ private CelEvaluationException newCelEvaluationException(long exprId, Exception } else if (e instanceof CelRuntimeException) { builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e); } else { - builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e); + // Unhandled function dispatch failures wraps the original exception with a descriptive message + // (e.g: "Function foo failed with...") + // We need to unwrap the cause here to preserve the original exception message and its cause. + Throwable cause = e.getCause() != null ? e.getCause() : e; + builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(cause); } return builder.setMetadata(metadata(), exprId).build(); diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 205fb2bed..00e72a884 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -52,12 +52,6 @@ public void typeComparisons() { skipBaselineVerification(); } - @Override - public void optional_errors() { - // TODO: Fix error message for function dispatch failures - skipBaselineVerification(); - } - @Override public void jsonFieldNames() throws Exception { // TODO: Support JSON field names for planner diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index ca862ed27..9154b5d8b 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -397,10 +397,12 @@ public void plan_call_zeroArgs() throws Exception { public void plan_call_throws() throws Exception { CelAbstractSyntaxTree ast = compile("error()"); Program program = PLANNER.plan(ast); + String expectedOverloadId = isParseOnly ? "error" : "error_overload"; CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - assertThat(e).hasMessageThat().contains("evaluation error at :5: Intentional error"); - assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(e).hasMessageThat().contains("evaluation error at :5: Function '" + expectedOverloadId + "' failed with arg(s) ''"); + assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class); + assertThat(e.getCause().getMessage()).contains("Intentional error"); } @Test From 8e5040d27457bfc4530c35f32a8ea9539420d863 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 13 Feb 2026 17:22:40 -0800 Subject: [PATCH 078/100] Implement type() standard function, integrate in planner PiperOrigin-RevId: 869950575 --- .../java/dev/cel/common/types/BUILD.bazel | 16 ++ .../cel/common/types/DefaultTypeProvider.java | 3 + common/types/BUILD.bazel | 5 + runtime/BUILD.bazel | 78 +++++++++- .../src/main/java/dev/cel/runtime/BUILD.bazel | 142 ++++++++++++------ .../java/dev/cel/runtime/CelRuntimeImpl.java | 11 +- .../cel/runtime/DescriptorTypeResolver.java | 46 +++++- .../java/dev/cel/runtime/TypeResolver.java | 11 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 2 +- .../runtime/planner/PlannedInterpretable.java | 2 - .../cel/runtime/planner/ProgramPlanner.java | 10 +- .../java/dev/cel/runtime/standard/BUILD.bazel | 34 +++++ .../cel/runtime/standard/TypeFunction.java | 71 +++++++++ .../cel/runtime/PlannerInterpreterTest.java | 6 - .../java/dev/cel/runtime/planner/BUILD.bazel | 2 + .../runtime/planner/ProgramPlannerTest.java | 45 +++++- runtime/standard/BUILD.bazel | 10 ++ 17 files changed, 418 insertions(+), 76 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/standard/TypeFunction.java diff --git a/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel index a35a897b8..f758b8e86 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -193,10 +193,26 @@ java_library( deps = [ ":type_providers", ":types", + "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], ) +cel_android_library( + name = "default_type_provider_android", + srcs = [ + "DefaultTypeProvider.java", + ], + tags = [ + ], + deps = [ + ":type_providers_android", + ":types_android", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven_android//:com_google_guava_guava", + ], +) + cel_android_library( name = "cel_types_android", srcs = ["CelTypes.java"], diff --git a/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java index 84e6c9ede..372a50e73 100644 --- a/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java +++ b/common/src/main/java/dev/cel/common/types/DefaultTypeProvider.java @@ -16,9 +16,11 @@ import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; import java.util.Optional; /** {@code DefaultTypeProvider} is a registry of common CEL types. */ +@Immutable public class DefaultTypeProvider implements CelTypeProvider { private static final DefaultTypeProvider INSTANCE = new DefaultTypeProvider(); @@ -43,6 +45,7 @@ private DefaultTypeProvider() { typeMapBuilder.putAll(SimpleType.TYPE_MAP); typeMapBuilder.put("list", ListType.create(SimpleType.DYN)); typeMapBuilder.put("map", MapType.create(SimpleType.DYN, SimpleType.DYN)); + typeMapBuilder.put("type", TypeType.create(SimpleType.DYN)); typeMapBuilder.put( "optional_type", // TODO: Move to CelOptionalLibrary and register it on demand diff --git a/common/types/BUILD.bazel b/common/types/BUILD.bazel index 41d3d59b2..df249ddbc 100644 --- a/common/types/BUILD.bazel +++ b/common/types/BUILD.bazel @@ -81,3 +81,8 @@ cel_android_library( name = "cel_proto_types_android", exports = ["//common/src/main/java/dev/cel/common/types:cel_proto_types_android"], ) + +cel_android_library( + name = "default_type_provider_android", + exports = ["//common/src/main/java/dev/cel/common/types:default_type_provider_android"], +) diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index a42ee7fb4..074ef2059 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -9,14 +9,31 @@ package( java_library( name = "runtime", exports = [ + ":descriptor_message_provider", ":evaluation_exception", + ":function_overload", ":late_function_binding", ":metadata", + ":runtime_factory", + ":runtime_legacy_impl", + ":variable_resolver", "//runtime/src/main/java/dev/cel/runtime", - "//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider", - "//runtime/src/main/java/dev/cel/runtime:function_overload", "//runtime/src/main/java/dev/cel/runtime:runtime_type_provider", - "//runtime/src/main/java/dev/cel/runtime:variable_resolver", + ], +) + +java_library( + name = "runtime_factory", + exports = [ + "//runtime/src/main/java/dev/cel/runtime:runtime_factory", + ], +) + +java_library( + name = "runtime_legacy_impl", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:runtime_legacy_impl", ], ) @@ -28,6 +45,14 @@ java_library( ], ) +cel_android_library( + name = "dispatcher_android", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:dispatcher_android", + ], +) + java_library( name = "standard_functions", exports = [ @@ -43,6 +68,14 @@ java_library( ], ) +cel_android_library( + name = "activation_android", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:activation_android", + ], +) + java_library( name = "proto_message_activation_factory", visibility = ["//:internal"], @@ -79,6 +112,7 @@ cel_android_library( java_library( name = "evaluation_exception_builder", + # used_by_android exports = ["//runtime/src/main/java/dev/cel/runtime:evaluation_exception_builder"], ) @@ -112,6 +146,12 @@ java_library( exports = ["//runtime/src/main/java/dev/cel/runtime:interpretable"], ) +cel_android_library( + name = "interpretable_android", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:interpretable_android"], +) + java_library( name = "runtime_helpers", visibility = ["//:internal"], @@ -154,6 +194,18 @@ java_library( exports = ["//runtime/src/main/java/dev/cel/runtime:type_resolver"], ) +cel_android_library( + name = "type_resolver_android", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:type_resolver_android"], +) + +java_library( + name = "descriptor_type_resolver", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:descriptor_type_resolver"], +) + java_library( name = "unknown_attributes", exports = ["//runtime/src/main/java/dev/cel/runtime:unknown_attributes"], @@ -253,12 +305,20 @@ cel_android_library( java_library( name = "metadata", + # used_by_android visibility = ["//:internal"], exports = ["//runtime/src/main/java/dev/cel/runtime:metadata"], ) +java_library( + name = "variable_resolver", + # used_by_android + exports = ["//runtime/src/main/java/dev/cel/runtime:variable_resolver"], +) + java_library( name = "concatenated_list_view", + # used_by_android visibility = ["//:internal"], exports = [ "//runtime/src/main/java/dev/cel/runtime:concatenated_list_view", @@ -266,10 +326,14 @@ java_library( ) java_library( - name = "variable_resolver", - exports = [ - "//runtime/src/main/java/dev/cel/runtime:variable_resolver", - ], + name = "function_overload", + exports = ["//runtime/src/main/java/dev/cel/runtime:function_overload"], +) + +java_library( + name = "descriptor_message_provider", + visibility = ["//:internal"], + exports = ["//runtime/src/main/java/dev/cel/runtime:descriptor_message_provider"], ) java_library( diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index f77c48d96..0746a5b83 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -139,7 +139,8 @@ java_library( cel_android_library( name = "dispatcher_android", srcs = DISPATCHER_SOURCES, - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":evaluation_exception", ":evaluation_exception_builder", @@ -175,7 +176,8 @@ java_library( cel_android_library( name = "activation_android", srcs = ["Activation.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":interpretable_android", ":runtime_helpers_android", @@ -207,8 +209,10 @@ java_library( tags = [ ], deps = [ + "//common/annotations", "//common/types", "//common/types:type_providers", + "//common/values", "//common/values:cel_byte_string", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -219,11 +223,14 @@ java_library( cel_android_library( name = "type_resolver_android", srcs = ["TypeResolver.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ + "//common/annotations", "//common/types:type_providers_android", "//common/types:types_android", "//common/values:cel_byte_string", + "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven_android//:com_google_guava_guava", @@ -234,14 +241,17 @@ cel_android_library( java_library( name = "descriptor_type_resolver", srcs = ["DescriptorTypeResolver.java"], - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":type_resolver", + "//common/annotations", "//common/types", "//common/types:type_providers", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", ], ) @@ -476,8 +486,6 @@ RUNTIME_SOURCES = [ "CelInternalRuntimeLibrary.java", "CelRuntime.java", "CelRuntimeBuilder.java", - "CelRuntimeFactory.java", - "CelRuntimeLegacyImpl.java", "CelRuntimeLibrary.java", "ProgramImpl.java", "UnknownContext.java", @@ -570,6 +578,8 @@ java_library( name = "metadata", srcs = ["Metadata.java"], # used_by_android + tags = [ + ], deps = [ "//common/annotations", "@maven//:com_google_errorprone_error_prone_annotations", @@ -579,6 +589,8 @@ java_library( java_library( name = "interpretable", srcs = INTERPRABLE_SOURCES, + tags = [ + ], deps = [ ":evaluation_exception", ":evaluation_listener", @@ -592,7 +604,8 @@ java_library( cel_android_library( name = "interpretable_android", srcs = INTERPRABLE_SOURCES, - visibility = ["//visibility:private"], + tags = [ + ], deps = [ ":evaluation_exception", ":evaluation_listener_android", @@ -782,7 +795,7 @@ java_library( ], deps = [ ":evaluation_exception", - "//runtime:unknown_attributes", + ":unknown_attributes", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", ], @@ -795,7 +808,7 @@ cel_android_library( ], deps = [ ":evaluation_exception", - "//runtime:unknown_attributes_android", + ":unknown_attributes_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven_android//:com_google_guava_guava", ], @@ -803,11 +816,22 @@ cel_android_library( java_library( name = "runtime_planner_impl", - testonly = 1, srcs = ["CelRuntimeImpl.java"], tags = [ ], deps = [ + ":descriptor_type_resolver", + ":dispatcher", + ":evaluation_exception", + ":evaluation_listener", + ":function_binding", + ":function_resolver", + ":program", + ":proto_message_runtime_helpers", + ":runtime", + ":runtime_equality", + ":standard_functions", + ":variable_resolver", "//:auto_value", "//common:cel_ast", "//common:cel_descriptor_util", @@ -825,17 +849,8 @@ java_library( "//common/values:cel_value_provider", "//common/values:combined_cel_value_provider", "//common/values:proto_message_value_provider", - "//runtime", - "//runtime:dispatcher", - "//runtime:evaluation_listener", - "//runtime:function_binding", - "//runtime:function_resolver", - "//runtime:program", - "//runtime:proto_message_runtime_helpers", - "//runtime:runtime_equality", - "//runtime:standard_functions", - "//runtime:variable_resolver", "//runtime/planner:program_planner", + "//runtime/standard:type", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -844,30 +859,22 @@ java_library( ) java_library( - name = "runtime", - srcs = RUNTIME_SOURCES, + name = "runtime_legacy_impl", + srcs = ["CelRuntimeLegacyImpl.java"], tags = [ ], deps = [ - ":activation", ":cel_value_runtime_type_provider", ":descriptor_message_provider", ":descriptor_type_resolver", ":dispatcher", - ":evaluation_exception", - ":evaluation_listener", ":function_binding", - ":function_resolver", - ":interpretable", ":interpreter", - ":program", - ":proto_message_activation_factory", ":proto_message_runtime_equality", + ":runtime", ":runtime_equality", ":runtime_type_provider", ":standard_functions", - ":unknown_attributes", - "//:auto_value", "//common:cel_ast", "//common:cel_descriptor_util", "//common:cel_descriptors", @@ -882,7 +889,6 @@ java_library( "//common/types:type_providers", "//common/values:cel_value_provider", "//common/values:proto_message_value_provider", - "//runtime:variable_resolver", "//runtime/standard:add", "//runtime/standard:int", "//runtime/standard:timestamp", @@ -894,6 +900,52 @@ java_library( ], ) +java_library( + name = "runtime_factory", + srcs = ["CelRuntimeFactory.java"], + tags = [ + ], + deps = [ + ":runtime", + ":runtime_legacy_impl", + "//common:options", + ], +) + +java_library( + name = "runtime", + srcs = RUNTIME_SOURCES, + tags = [ + ], + deps = [ + ":activation", + ":evaluation_exception", + ":evaluation_listener", + ":function_binding", + ":function_resolver", + ":interpretable", + ":interpreter", + ":program", + ":proto_message_activation_factory", + ":runtime_equality", + ":standard_functions", + ":unknown_attributes", + ":variable_resolver", + "//:auto_value", + "//common:cel_ast", + "//common:container", + "//common:options", + "//common/annotations", + "//common/types:type_providers", + "//common/values:cel_value_provider", + "@maven//:com_google_code_findbugs_annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", + ], +) + java_library( name = "lite_runtime", srcs = LITE_RUNTIME_SOURCES, @@ -950,8 +1002,8 @@ java_library( ":function_resolver", ":interpretable", ":program", + ":variable_resolver", "//:auto_value", - "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) @@ -1253,19 +1305,6 @@ cel_android_library( ], ) -java_library( - name = "variable_resolver", - srcs = [ - "CelVariableResolver.java", - "HierarchicalVariableResolver.java", - ], - # used_by_android - tags = [ - ], - deps = [ - ], -) - java_library( name = "program", srcs = ["Program.java"], @@ -1318,3 +1357,14 @@ cel_android_library( "@maven_android//:com_google_guava_guava", ], ) + +java_library( + name = "variable_resolver", + srcs = [ + "CelVariableResolver.java", + "HierarchicalVariableResolver.java", + ], + # used_by_android + tags = [ + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index 3f3245c84..27bd1b3fc 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -46,6 +46,7 @@ import dev.cel.common.values.CombinedCelValueProvider; import dev.cel.common.values.ProtoMessageValueProvider; import dev.cel.runtime.planner.ProgramPlanner; +import dev.cel.runtime.standard.TypeFunction; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -446,12 +447,20 @@ public CelRuntime build() { CelTypeProvider combinedTypeProvider = new CelTypeProvider.CombinedCelTypeProvider( - messageTypeProvider, DefaultTypeProvider.getInstance()); + DefaultTypeProvider.getInstance(), messageTypeProvider); if (typeProvider() != null) { combinedTypeProvider = new CelTypeProvider.CombinedCelTypeProvider(combinedTypeProvider, typeProvider()); } + DescriptorTypeResolver descriptorTypeResolver = + DescriptorTypeResolver.create(combinedTypeProvider); + TypeFunction typeFunction = TypeFunction.create(descriptorTypeResolver); + for (CelFunctionBinding binding : + typeFunction.newFunctionBindings(options(), runtimeEquality)) { + mutableFunctionBindings.put(binding.getOverloadId(), binding); + } + DefaultDispatcher dispatcher = newDispatcher( standardFunctions(), mutableFunctionBindings.values(), runtimeEquality, options()); diff --git a/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java b/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java index 46ad0d231..63fcb87b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/DescriptorTypeResolver.java @@ -18,24 +18,46 @@ import com.google.errorprone.annotations.Immutable; import com.google.protobuf.MessageOrBuilder; +import dev.cel.common.annotations.Internal; import dev.cel.common.types.CelType; +import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; +import java.util.NoSuchElementException; import java.util.Optional; +import org.jspecify.annotations.Nullable; /** * {@code DescriptorTypeResolver} extends {@link TypeResolver} and additionally resolves incoming * protobuf message types using descriptors. + * + *

    CEL Library Internals. Do Not Use. */ @Immutable -final class DescriptorTypeResolver extends TypeResolver { +@Internal +public final class DescriptorTypeResolver extends TypeResolver { - static DescriptorTypeResolver create() { + private final @Nullable CelTypeProvider typeProvider; + + /** + * Creates a {@code DescriptorTypeResolver}. All protobuf messages are resolved as a type of + * {@link StructTypeReference}. + */ + public static DescriptorTypeResolver create() { return new DescriptorTypeResolver(); } + /** + * Creates a {@code DescriptorTypeResolver}. If the protobuf message to be resolved can be found + * in the provided {@link CelTypeProvider}, the message is resolved as a concrete {@code + * ProtoMessageType} instead of a {@link StructTypeReference}. + */ + public static DescriptorTypeResolver create(CelTypeProvider typeProvider) { + return new DescriptorTypeResolver(typeProvider); + } + @Override - TypeType resolveObjectType(Object obj, CelType typeCheckedType) { + public TypeType resolveObjectType(Object obj, CelType typeCheckedType) { checkNotNull(obj); Optional wellKnownTypeType = resolveWellKnownObjectType(obj); @@ -45,11 +67,25 @@ TypeType resolveObjectType(Object obj, CelType typeCheckedType) { if (obj instanceof MessageOrBuilder) { MessageOrBuilder msg = (MessageOrBuilder) obj; - return TypeType.create(StructTypeReference.create(msg.getDescriptorForType().getFullName())); + String typeName = msg.getDescriptorForType().getFullName(); + if (typeProvider != null) { + return typeProvider + .findType(typeName) + .map(TypeType::create) + .orElseThrow(() -> new NoSuchElementException("Could not find type: " + typeName)); + } else { + return TypeType.create(StructTypeReference.create(typeName)); + } } return super.resolveObjectType(obj, typeCheckedType); } - private DescriptorTypeResolver() {} + private DescriptorTypeResolver() { + this(null); + } + + private DescriptorTypeResolver(@Nullable CelTypeProvider typeProvider) { + this.typeProvider = typeProvider; + } } diff --git a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java index 905120988..c2ebf521c 100644 --- a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java @@ -26,6 +26,7 @@ import com.google.protobuf.MessageLiteOrBuilder; import com.google.protobuf.NullValue; import com.google.protobuf.Timestamp; +import dev.cel.common.annotations.Internal; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -45,9 +46,12 @@ /** * {@code TypeResolver} resolves incoming {@link CelType} into {@link TypeType}., either as part of * a type call (type('foo'), type(1), etc.) or as a type literal (type, int, string, etc.) + * + *

    CEL Library Internals. Do Not Use. */ @Immutable -class TypeResolver { +@Internal +public class TypeResolver { static TypeResolver create() { return new TypeResolver(); @@ -65,6 +69,7 @@ static TypeResolver create() { .put(UnsignedLong.class, TypeType.create(SimpleType.UINT)) .put(String.class, TypeType.create(SimpleType.STRING)) .put(NullValue.class, TypeType.create(SimpleType.NULL_TYPE)) + .put(dev.cel.common.values.NullValue.class, TypeType.create(SimpleType.NULL_TYPE)) .put(java.time.Duration.class, TypeType.create(SimpleType.DURATION)) .put(Instant.class, TypeType.create(SimpleType.TIMESTAMP)) .put( @@ -135,7 +140,7 @@ Optional resolveWellKnownObjectType(Object obj) { } /** Resolve the CEL type of the {@code obj}. */ - TypeType resolveObjectType(Object obj, CelType typeCheckedType) { + public TypeType resolveObjectType(Object obj, CelType typeCheckedType) { checkNotNull(obj); Optional wellKnownTypeType = resolveWellKnownObjectType(obj); if (wellKnownTypeType.isPresent()) { @@ -188,5 +193,5 @@ private static CelType adaptStructType(StructType typeOfType) { return newTypeOfType; } - TypeResolver() {} + protected TypeResolver() {} } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 99273f2fd..c7e4103af 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -69,7 +69,6 @@ java_library( "//common:options", "//common/exceptions:runtime_exception", "//common/values", - "//runtime", "//runtime:activation", "//runtime:evaluation_exception", "//runtime:evaluation_exception_builder", @@ -77,6 +76,7 @@ java_library( "//runtime:interpretable", "//runtime:program", "//runtime:resolved_overload", + "//runtime:variable_resolver", "@maven//:com_google_errorprone_error_prone_annotations", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java index 5ce3208f8..6f3a9d7ff 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedInterpretable.java @@ -25,8 +25,6 @@ abstract class PlannedInterpretable { /** Runs interpretation with the given activation which supplies name/value bindings. */ abstract Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException; - // TODO: Implement support for late-bound functions and evaluation listener - long exprId() { return exprId; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index b5d43728b..7f8b8a0e0 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -40,6 +40,7 @@ import dev.cel.common.types.CelKind; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; +import dev.cel.common.types.SimpleType; import dev.cel.common.types.StructType; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelValueConverter; @@ -60,7 +61,6 @@ @Immutable @Internal public final class ProgramPlanner { - private final CelTypeProvider typeProvider; private final CelValueProvider valueProvider; private final DefaultDispatcher dispatcher; @@ -182,7 +182,13 @@ private PlannedInterpretable planCheckedIdent( TypeType identType = typeProvider .findType(identRef.name()) - .map(TypeType::create) + .map( + t -> + (t instanceof TypeType) + // Coalesce all type(foo) "type" into a sentinel runtime type to allow for + // erasure based type comparisons + ? TypeType.create(SimpleType.DYN) + : TypeType.create(t)) .orElseThrow( () -> new NoSuchElementException( diff --git a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel index 5853220ae..0a76b6135 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -1389,6 +1389,40 @@ cel_android_library( ], ) +java_library( + name = "type", + srcs = ["TypeFunction.java"], + tags = [ + ], + deps = [ + ":standard_function", + ":standard_overload", + "//common:options", + "//common/types", + "//runtime:function_binding", + "//runtime:runtime_equality", + "//runtime:type_resolver", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "type_android", + srcs = ["TypeFunction.java"], + tags = [ + ], + deps = [ + ":standard_function_android", + ":standard_overload_android", + "//common:options", + "//common/types:types_android", + "//runtime:function_binding_android", + "//runtime:runtime_equality_android", + "//runtime:type_resolver_android", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "uint", srcs = ["UintFunction.java"], diff --git a/runtime/src/main/java/dev/cel/runtime/standard/TypeFunction.java b/runtime/src/main/java/dev/cel/runtime/standard/TypeFunction.java new file mode 100644 index 000000000..b325c0e9c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/standard/TypeFunction.java @@ -0,0 +1,71 @@ +// 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.runtime.standard; + +import com.google.common.collect.ImmutableSet; +import dev.cel.common.CelOptions; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.TypeType; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.RuntimeEquality; +import dev.cel.runtime.TypeResolver; + +/** + * Standard function for the {@code type} function. + * + *

    The {@code type} function returns the CEL type of its argument. It accepts a + * {@link TypeResolver} so that different runtimes can supply the appropriate resolver (e.g. a + * descriptor-based resolver for full proto, or a base resolver for lite proto). + */ +public final class TypeFunction extends CelStandardFunction { + + private final TypeResolver typeResolver; + + public static TypeFunction create(TypeResolver typeResolver) { + return new TypeFunction(typeResolver); + } + + /** Overloads for the standard {@code type} function. */ + public enum TypeOverload implements CelStandardOverload { + TYPE; + + @Override + public CelFunctionBinding newFunctionBinding( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + // This overload is not used directly. The binding is created in TypeFunction via the + // TypeResolver instance. + // TODO: Instantiate from CelStandardFunctions. + throw new UnsupportedOperationException( + "TypeOverload bindings must be created through TypeFunction.create(TypeResolver)"); + } + } + + @Override + public ImmutableSet newFunctionBindings( + CelOptions celOptions, RuntimeEquality runtimeEquality) { + CelFunctionBinding binding = + CelFunctionBinding.from( + "type", + Object.class, + arg -> typeResolver.resolveObjectType(arg, TypeType.create(SimpleType.DYN))); + + return CelFunctionBinding.fromOverloads("type", ImmutableSet.of(binding)); + } + + private TypeFunction(TypeResolver typeResolver) { + super("type", ImmutableSet.copyOf(TypeOverload.values())); + this.typeResolver = typeResolver; + } +} \ No newline at end of file diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 00e72a884..ea8e52d0c 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -46,12 +46,6 @@ public void unknownResultSet() { skipBaselineVerification(); } - @Override - public void typeComparisons() { - // TODO: type() standard function needs to be implemented first. - skipBaselineVerification(); - } - @Override public void jsonFieldNames() throws Exception { // TODO: Support JSON field names for planner diff --git a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel index 6749be24f..fb05b0b31 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel @@ -40,6 +40,7 @@ java_library( "//extensions", "//parser:macro", "//runtime", + "//runtime:descriptor_type_resolver", "//runtime:dispatcher", "//runtime:function_binding", "//runtime:program", @@ -47,6 +48,7 @@ java_library( "//runtime:runtime_helpers", "//runtime:standard_functions", "//runtime/planner:program_planner", + "//runtime/standard:type", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_testparameterinjector_test_parameter_injector", diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 9154b5d8b..e8f5c6662 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -71,9 +71,11 @@ import dev.cel.runtime.CelStandardFunctions; import dev.cel.runtime.CelStandardFunctions.StandardFunction; import dev.cel.runtime.DefaultDispatcher; +import dev.cel.runtime.DescriptorTypeResolver; import dev.cel.runtime.Program; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import dev.cel.runtime.standard.TypeFunction; import org.junit.Test; import org.junit.runner.RunWith; @@ -173,6 +175,10 @@ private static DefaultDispatcher newDispatcher() { addBindingsToDispatcher( builder, stdFunctions.newFunctionBindings(RUNTIME_EQUALITY, CEL_OPTIONS)); + TypeFunction typeFunction = TypeFunction.create(DescriptorTypeResolver.create(TYPE_PROVIDER)); + addBindingsToDispatcher( + builder, typeFunction.newFunctionBindings(CEL_OPTIONS, RUNTIME_EQUALITY)); + // Custom functions addBindingsToDispatcher( builder, @@ -400,9 +406,14 @@ public void plan_call_throws() throws Exception { String expectedOverloadId = isParseOnly ? "error" : "error_overload"; CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); - assertThat(e).hasMessageThat().contains("evaluation error at :5: Function '" + expectedOverloadId + "' failed with arg(s) ''"); - assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class); - assertThat(e.getCause().getMessage()).contains("Intentional error"); + assertThat(e) + .hasMessageThat() + .contains( + "evaluation error at :5: Function '" + + expectedOverloadId + + "' failed with arg(s) ''"); + assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(e.getCause()).hasMessageThat().contains("Intentional error"); } @Test @@ -603,6 +614,17 @@ public void plan_call_lateBoundFunction() throws Exception { assertThat(result).isEqualTo("test_resolved"); } + @Test + public void plan_call_typeResolution(@TestParameter TypeObjectTestCase testCase) + throws Exception { + CelAbstractSyntaxTree ast = compile(testCase.expression); + Program program = PLANNER.plan(ast); + + TypeType result = (TypeType) program.eval(); + + assertThat(result).isEqualTo(testCase.type); + } + @Test public void plan_select_protoMessageField() throws Exception { CelAbstractSyntaxTree ast = compile("msg.single_string"); @@ -1007,6 +1029,23 @@ private enum TypeLiteralTestCase { } } + private enum TypeObjectTestCase { + BOOL("type(true)", SimpleType.BOOL), + INT("type(1)", SimpleType.INT), + DOUBLE("type(1.5)", SimpleType.DOUBLE), + PROTO_MESSAGE_TYPE( + "type(cel.expr.conformance.proto3.TestAllTypes{})", + TYPE_PROVIDER.findType("cel.expr.conformance.proto3.TestAllTypes").get()); + + private final String expression; + private final TypeType type; + + TypeObjectTestCase(String expression, CelType type) { + this.expression = expression; + this.type = TypeType.create(type); + } + } + @SuppressWarnings("Immutable") // Test only private enum PresenceTestCase { PROTO_FIELD_PRESENT( diff --git a/runtime/standard/BUILD.bazel b/runtime/standard/BUILD.bazel index bc6ab9ed3..4ca87e5e0 100644 --- a/runtime/standard/BUILD.bazel +++ b/runtime/standard/BUILD.bazel @@ -406,6 +406,16 @@ cel_android_library( exports = ["//runtime/src/main/java/dev/cel/runtime/standard:uint_android"], ) +java_library( + name = "type", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:type"], +) + +cel_android_library( + name = "type_android", + exports = ["//runtime/src/main/java/dev/cel/runtime/standard:type_android"], +) + java_library( name = "not_strictly_false", exports = ["//runtime/src/main/java/dev/cel/runtime/standard:not_strictly_false"], From daf99698387aa801ff614a110940eb967916463b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 18 Feb 2026 13:11:50 -0800 Subject: [PATCH 079/100] Properly establish cross dependenies across maven artifacts Also removes antlr dependency from `dev.cel:runtime` artifact Fixes: https://github.com/google/cel-java/issues/566 See also https://github.com/grpc/grpc-java/pull/12640 PiperOrigin-RevId: 872010923 --- .../cross_artifact_dependencies_check.sh | 85 ++++++++++ .github/workflows/unwanted_deps.sh | 6 +- .github/workflows/workflow.yml | 53 +++++- common/BUILD.bazel | 9 +- common/internal/BUILD.bazel | 5 + .../src/main/java/dev/cel/common/BUILD.bazel | 29 +++- .../java/dev/cel/common/formats/BUILD.bazel | 1 + .../java/dev/cel/common/internal/BUILD.bazel | 17 +- .../java/dev/cel/common/types/BUILD.bazel | 1 + .../java/dev/cel/common/values/BUILD.bazel | 1 + .../src/test/java/dev/cel/common/BUILD.bazel | 1 + .../src/main/java/dev/cel/parser/BUILD.bazel | 1 + .../src/main/java/dev/cel/policy/BUILD.bazel | 1 + publish/BUILD.bazel | 155 ++++++++++++++---- publish/pom_template.xml | 4 +- publish/publish.sh | 2 +- 16 files changed, 317 insertions(+), 54 deletions(-) create mode 100755 .github/workflows/cross_artifact_dependencies_check.sh diff --git a/.github/workflows/cross_artifact_dependencies_check.sh b/.github/workflows/cross_artifact_dependencies_check.sh new file mode 100755 index 000000000..0802a299a --- /dev/null +++ b/.github/workflows/cross_artifact_dependencies_check.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# 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. + +set -o pipefail + +TARGETS=( + "//publish:cel" + "//publish:cel_common" + "//publish:cel_compiler" + "//publish:cel_runtime" + "//publish:cel_protobuf" + "//publish:cel_v1alpha1" +) + +echo "------------------------------------------------" +echo "Checking for duplicates..." +echo "------------------------------------------------" + +JDK8_FLAGS="--java_language_version=8 --java_runtime_version=8" +bazel build $JDK8_FLAGS "${TARGETS[@]}" || { echo "Bazel build failed"; exit 1; } + +( + for target in "${TARGETS[@]}"; do + # Locate the jar + jar_path=$(bazel cquery "$target" --output=files 2>/dev/null | grep '\-project.jar$') + + if [[ -z "$jar_path" ]]; then + echo "Error: Could not find -project.jar for target $target" >&2 + exit 1 + fi + + # Fix relative paths if running from a subdir. + if [[ ! -f "$jar_path" ]]; then + if [[ -f "../../$jar_path" ]]; then + jar_path="../../$jar_path" + else + echo "Error: File not found at $jar_path" >&2 + exit 1 + fi + fi + + echo "Inspecting: $target" >&2 + + # Extract classes and append the target name to the end of the line + # Format: dev/cel/expr/Expr.class //publish:cel_compiler + jar tf "$jar_path" | grep "\.class$" | awk -v tgt="$target" '{print $0, tgt}' + done +) | awk ' + # $1 is the Class Name, $2 is the Target Name + seen[$1] { + print "❌ DUPLICATE FOUND: " $1 + print " Present in: " seen[$1] + print " And in: " $2 + dupe=1 + next + } + { seen[$1] = $2 } + + END { if (dupe) exit 2 } +' + +EXIT_CODE=$? + +if [ $EXIT_CODE -eq 0 ]; then + echo "✅ Success: No duplicate classes found." +elif [ $EXIT_CODE -eq 2 ]; then + echo "⛔ Failure: Duplicate classes detected." +else + echo "💥 Error: An unexpected error occurred (e.g., missing jar files). Exit Code: $EXIT_CODE" +fi + +exit $EXIT_CODE + diff --git a/.github/workflows/unwanted_deps.sh b/.github/workflows/unwanted_deps.sh index abef91a5f..c483f9730 100755 --- a/.github/workflows/unwanted_deps.sh +++ b/.github/workflows/unwanted_deps.sh @@ -36,10 +36,14 @@ checkUnwantedDeps '//publish:cel_runtime' '@cel_spec' checkUnwantedDeps '//publish:cel_runtime' 'protobuf_java_util' checkUnwantedDeps '//publish:cel' 'protobuf_java_util' +# cel_runtime shouldn't depend on antlr +checkUnwantedDeps '//publish:cel_runtime' '@maven//:org_antlr_antlr4_runtime' + # cel_runtime shouldn't depend on the protobuf_lite runtime checkUnwantedDeps '//publish:cel_runtime' '@maven_android//:com_google_protobuf_protobuf_javalite' checkUnwantedDeps '//publish:cel' '@maven_android//:com_google_protobuf_protobuf_javalite' -# cel_runtime_android shouldn't depend on the full protobuf runtime +# cel_runtime_android shouldn't depend on the full protobuf runtime or antlr checkUnwantedDeps '//publish:cel_runtime_android' '@maven//:com_google_protobuf_protobuf_java' +checkUnwantedDeps '//publish:cel_runtime_android' '@maven//:org_antlr_antlr4_runtime' exit 0 diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 56048491a..19732cd9e 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -15,6 +15,29 @@ concurrency: cancel-in-progress: true jobs: + Static-Checks: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 Job is running on a ${{ runner.os }} server!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v6 + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.18.0 + with: + # Avoid downloading Bazel every time. + bazelisk-cache: true + # Store build cache per workflow. + disk-cache: ${{ github.workflow }} + # Share repository cache between workflows. + repository-cache: true + - name: Unwanted Dependencies + run: .github/workflows/unwanted_deps.sh + - name: Cross-artifact Duplicate Classes Check + run: .github/workflows/cross_artifact_dependencies_check.sh + - run: echo "🍏 This job's status is ${{ job.status }}." Bazel-Tests: runs-on: ubuntu-latest timeout-minutes: 30 @@ -25,7 +48,7 @@ jobs: - name: Check out repository code uses: actions/checkout@v6 - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.14.0 + uses: bazel-contrib/setup-bazel@0.18.0 with: # Avoid downloading Bazel every time. bazelisk-cache: true @@ -41,13 +64,33 @@ jobs: # Exclude codelab exercises as they are intentionally made to fail # Exclude maven conformance tests. They are only executed when there's version change. run: bazelisk test ... --deleted_packages=//codelab/src/test/codelab --test_output=errors --test_tag_filters=-conformance_maven --build_tag_filters=-conformance_maven + - run: echo "🍏 This job's status is ${{ job.status }}." - # -- Start of Maven Conformance Tests (Ran only when there's version changes) -- - - name: Get changed file + # -- Start of Maven Conformance Tests (Ran only when there's version changes) -- + Maven-Conformance: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 Job is running on a ${{ runner.os }} server!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v6 + - name: Get changed files id: changed_file uses: tj-actions/changed-files@v46 with: files: publish/cel_version.bzl + - name: Setup Bazel + if: steps.changed_file.outputs.any_changed == 'true' + uses: bazel-contrib/setup-bazel@0.18.0 + with: + # Avoid downloading Bazel every time. + bazelisk-cache: true + # Store build cache per workflow. + disk-cache: ${{ github.workflow }} + # Share repository cache between workflows. + repository-cache: true - name: Verify Version Consistency if: steps.changed_file.outputs.any_changed == 'true' run: | @@ -72,8 +115,4 @@ jobs: - name: Run Conformance Maven Test on Version Change if: steps.changed_file.outputs.any_changed == 'true' run: bazelisk test //conformance/src/test/java/dev/cel/conformance:conformance_maven --test_output=errors - # -- End of Maven Conformance Tests -- - - - name: Unwanted Dependencies - run: .github/workflows/unwanted_deps.sh - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 21a124565..4e0d7485c 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -79,6 +79,12 @@ cel_android_library( exports = ["//common/src/main/java/dev/cel/common:cel_source_android"], ) +java_library( + name = "cel_source_helper", + visibility = ["//:internal"], + exports = ["//common/src/main/java/dev/cel/common:cel_source_helper"], +) + java_library( name = "cel_ast", exports = ["//common/src/main/java/dev/cel/common:cel_ast"], @@ -108,9 +114,6 @@ java_library( visibility = [ "//:internal", # TODO: Remove references to the following clients - "//java/com/google/abuse/admin/notebook/compiler/checkedtypes:__pkg__", - "//java/com/google/paymentfraud/v2/util/featurereplay/common/risklogrecordio:__pkg__", - "//java/com/google/payments/consumer/growth/treatmentconfig/management/backend/service/config/utils:__pkg__", ], exports = ["//common/src/main/java/dev/cel/common:cel_descriptor_util"], ) diff --git a/common/internal/BUILD.bazel b/common/internal/BUILD.bazel index c69f26b3e..0a07e0d63 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -11,6 +11,11 @@ java_library( exports = ["//common/src/main/java/dev/cel/common/internal"], ) +java_library( + name = "code_point_stream", + exports = ["//common/src/main/java/dev/cel/common/internal:code_point_stream"], +) + java_library( name = "comparison_functions", exports = ["//common/src/main/java/dev/cel/common/internal:comparison_functions"], diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 62ba8db78..38548744c 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -21,7 +21,6 @@ COMPILER_COMMON_SOURCES = [ # keep sorted SOURCE_SOURCES = [ - "CelSourceHelper.java", "Source.java", ] @@ -234,6 +233,7 @@ java_library( tags = [ ], deps = [ + ":cel_source_helper", ":source", ":source_location", "//:auto_value", @@ -250,6 +250,7 @@ cel_android_library( tags = [ ], deps = [ + ":cel_source_helper_android", ":source_android", ":source_location_android", "//:auto_value", @@ -260,6 +261,30 @@ cel_android_library( ], ) +java_library( + name = "cel_source_helper", + srcs = ["CelSourceHelper.java"], + tags = [ + ], + deps = [ + ":source_location", + "//common/annotations", + "//common/internal", + "@maven//:com_google_guava_guava", + ], +) + +cel_android_library( + name = "cel_source_helper_android", + srcs = ["CelSourceHelper.java"], + deps = [ + ":source_location_android", + "//common/annotations", + "//common/internal:internal_android", + "@maven_android//:com_google_guava_guava", + ], +) + java_library( name = "cel_ast", srcs = ["CelAbstractSyntaxTree.java"], @@ -300,7 +325,6 @@ java_library( tags = [ ], deps = [ - ":source_location", "//common/annotations", "//common/internal", "@maven//:com_google_guava_guava", @@ -312,7 +336,6 @@ cel_android_library( srcs = SOURCE_SOURCES, visibility = ["//visibility:private"], deps = [ - ":source_location_android", "//common/annotations", "//common/internal:internal_android", "@maven_android//:com_google_guava_guava", diff --git a/common/src/main/java/dev/cel/common/formats/BUILD.bazel b/common/src/main/java/dev/cel/common/formats/BUILD.bazel index 16918cb3a..e906c8ba2 100644 --- a/common/src/main/java/dev/cel/common/formats/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/formats/BUILD.bazel @@ -71,6 +71,7 @@ java_library( ], deps = [ "//:auto_value", + "//common:cel_source_helper", "//common:source", "//common:source_location", "//common/internal", diff --git a/common/src/main/java/dev/cel/common/internal/BUILD.bazel b/common/src/main/java/dev/cel/common/internal/BUILD.bazel index a0e564788..912b4de4b 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -7,6 +7,7 @@ package( ], default_visibility = [ "//common/internal:__pkg__", + "//publish:__pkg__", ], ) @@ -14,7 +15,6 @@ package( INTERNAL_SOURCES = [ "BasicCodePointArray.java", "CelCodePointArray.java", - "CodePointStream.java", "Constants.java", "EmptyCodePointArray.java", "Latin1CodePointArray.java", @@ -34,6 +34,19 @@ CEL_DESCRIPTOR_POOL_SOURCES = [ "DefaultDescriptorPool.java", ] +java_library( + name = "code_point_stream", + srcs = ["CodePointStream.java"], + tags = [ + ], + deps = [ + ":internal", + "//common/annotations", + "@maven//:com_google_guava_guava", + "@maven//:org_antlr_antlr4_runtime", + ], +) + java_library( name = "internal", srcs = INTERNAL_SOURCES, @@ -48,7 +61,6 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", - "@maven//:org_antlr_antlr4_runtime", ], ) @@ -65,7 +77,6 @@ cel_android_library( "//common/values:values_android", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", - "@maven//:org_antlr_antlr4_runtime", "@maven_android//:com_google_guava_guava", "@maven_android//:com_google_protobuf_protobuf_javalite", ], diff --git a/common/src/main/java/dev/cel/common/types/BUILD.bazel b/common/src/main/java/dev/cel/common/types/BUILD.bazel index f758b8e86..de65d0b1f 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -7,6 +7,7 @@ package( ], default_visibility = [ "//common/types:__pkg__", + "//publish:__pkg__", ], ) diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index 3a78091bf..bacd9d1fc 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -7,6 +7,7 @@ package( ], default_visibility = [ "//common/values:__pkg__", + "//publish:__pkg__", ], ) diff --git a/common/src/test/java/dev/cel/common/BUILD.bazel b/common/src/test/java/dev/cel/common/BUILD.bazel index 8dcb60b92..94060f89a 100644 --- a/common/src/test/java/dev/cel/common/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/BUILD.bazel @@ -25,6 +25,7 @@ java_library( "//common:source_location", "//common/ast", "//common/internal", + "//common/internal:code_point_stream", "//common/types", "//common/types:cel_proto_types", "//common/types:cel_v1alpha1_types", diff --git a/parser/src/main/java/dev/cel/parser/BUILD.bazel b/parser/src/main/java/dev/cel/parser/BUILD.bazel index 84b9e5af6..e32c50ee8 100644 --- a/parser/src/main/java/dev/cel/parser/BUILD.bazel +++ b/parser/src/main/java/dev/cel/parser/BUILD.bazel @@ -66,6 +66,7 @@ java_library( "//common/annotations", "//common/ast", "//common/internal", + "//common/internal:code_point_stream", "//common/internal:env_visitor", "//parser:cel_g4_visitors", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/policy/src/main/java/dev/cel/policy/BUILD.bazel b/policy/src/main/java/dev/cel/policy/BUILD.bazel index 4fa1baa78..52b0b1ba7 100644 --- a/policy/src/main/java/dev/cel/policy/BUILD.bazel +++ b/policy/src/main/java/dev/cel/policy/BUILD.bazel @@ -34,6 +34,7 @@ java_library( ], deps = [ "//:auto_value", + "//common:cel_source_helper", "//common:source", "//common:source_location", "//common/internal", diff --git a/publish/BUILD.bazel b/publish/BUILD.bazel index 9ca6eb770..cb13a70b5 100644 --- a/publish/BUILD.bazel +++ b/publish/BUILD.bazel @@ -5,93 +5,129 @@ load("//publish:cel_version.bzl", "CEL_VERSION") # Note: These targets must reference the build targets in `src` directly in # order to properly generate the maven dependencies in pom.xml. +# keep sorted +COMMON_TARGETS = [ + "//common/src/main/java/dev/cel/common:cel_ast", + "//common/src/main/java/dev/cel/common:cel_descriptor_util", + "//common/src/main/java/dev/cel/common:cel_exception", + "//common/src/main/java/dev/cel/common:cel_source", + "//common/src/main/java/dev/cel/common:container", + "//common/src/main/java/dev/cel/common:error_codes", + "//common/src/main/java/dev/cel/common:operator", + "//common/src/main/java/dev/cel/common:options", + "//common/src/main/java/dev/cel/common:source", + "//common/src/main/java/dev/cel/common/internal", + "//common/src/main/java/dev/cel/common/internal:cel_descriptor_pools", + "//common/src/main/java/dev/cel/common/internal:file_descriptor_converter", + "//common/src/main/java/dev/cel/common/internal:safe_string_formatter", + "//common/src/main/java/dev/cel/common/types:cel_types", + "//common/src/main/java/dev/cel/common/values", + "//common/src/main/java/dev/cel/common/values:cel_value", +] + +# keep sorted RUNTIME_TARGETS = [ "//runtime/src/main/java/dev/cel/runtime", "//runtime/src/main/java/dev/cel/runtime:base", "//runtime/src/main/java/dev/cel/runtime:interpreter", + "//runtime/src/main/java/dev/cel/runtime:late_function_binding", + "//runtime/src/main/java/dev/cel/runtime:runtime_factory", "//runtime/src/main/java/dev/cel/runtime:runtime_helpers", + "//runtime/src/main/java/dev/cel/runtime:runtime_legacy_impl", + "//runtime/src/main/java/dev/cel/runtime:standard_functions", "//runtime/src/main/java/dev/cel/runtime:unknown_attributes", ] +# keep sorted LITE_RUNTIME_TARGETS = [ - "//runtime/src/main/java/dev/cel/runtime:lite_runtime_android", - "//runtime/src/main/java/dev/cel/runtime:lite_runtime_factory_android", - "//runtime/src/main/java/dev/cel/runtime:lite_runtime_library_android", "//common/src/main/java/dev/cel/common:proto_ast_android", # Note: included due to generated protos requiring protolite dependency "//common/src/main/java/dev/cel/common/values:proto_message_lite_value_provider_android", "//protobuf/src/main/java/dev/cel/protobuf:cel_lite_descriptor", + "//runtime/src/main/java/dev/cel/runtime:lite_runtime_android", + "//runtime/src/main/java/dev/cel/runtime:lite_runtime_factory_android", + "//runtime/src/main/java/dev/cel/runtime:lite_runtime_library_android", + "//runtime/src/main/java/dev/cel/runtime:standard_functions_android", ] +# keep sorted COMPILER_TARGETS = [ - "//parser/src/main/java/dev/cel/parser", - "//parser/src/main/java/dev/cel/parser:parser_factory", - "//parser/src/main/java/dev/cel/parser:parser_builder", - "//parser/src/main/java/dev/cel/parser:unparser", "//checker/src/main/java/dev/cel/checker:checker", "//checker/src/main/java/dev/cel/checker:checker_builder", - "//checker/src/main/java/dev/cel/checker:proto_type_mask", "//checker/src/main/java/dev/cel/checker:proto_expr_visitor", + "//checker/src/main/java/dev/cel/checker:proto_type_mask", "//compiler/src/main/java/dev/cel/compiler", "//compiler/src/main/java/dev/cel/compiler:compiler_builder", + "//parser/src/main/java/dev/cel/parser", + "//parser/src/main/java/dev/cel/parser:parser_builder", + "//parser/src/main/java/dev/cel/parser:parser_factory", + "//parser/src/main/java/dev/cel/parser:unparser", ] +# keep sorted VALIDATOR_TARGETS = [ "//validator/src/main/java/dev/cel/validator", - "//validator/src/main/java/dev/cel/validator:validator_builder", "//validator/src/main/java/dev/cel/validator:ast_validator", + "//validator/src/main/java/dev/cel/validator:validator_builder", "//validator/src/main/java/dev/cel/validator:validator_impl", - "//validator/src/main/java/dev/cel/validator/validators:timestamp", "//validator/src/main/java/dev/cel/validator/validators:duration", - "//validator/src/main/java/dev/cel/validator/validators:regex", "//validator/src/main/java/dev/cel/validator/validators:homogeneous_literal", + "//validator/src/main/java/dev/cel/validator/validators:regex", + "//validator/src/main/java/dev/cel/validator/validators:timestamp", ] +# keep sorted OPTIMIZER_TARGETS = [ "//optimizer/src/main/java/dev/cel/optimizer", - "//optimizer/src/main/java/dev/cel/optimizer:optimizer_builder", "//optimizer/src/main/java/dev/cel/optimizer:ast_optimizer", - "//optimizer/src/main/java/dev/cel/optimizer:optimization_exception", "//optimizer/src/main/java/dev/cel/optimizer:mutable_ast", + "//optimizer/src/main/java/dev/cel/optimizer:optimization_exception", + "//optimizer/src/main/java/dev/cel/optimizer:optimizer_builder", "//optimizer/src/main/java/dev/cel/optimizer:optimizer_impl", - "//optimizer/src/main/java/dev/cel/optimizer/optimizers:constant_folding", "//optimizer/src/main/java/dev/cel/optimizer/optimizers:common_subexpression_elimination", + "//optimizer/src/main/java/dev/cel/optimizer/optimizers:constant_folding", + "//optimizer/src/main/java/dev/cel/optimizer/optimizers:inlining", ] +# keep sorted POLICY_COMPILER_TARGETS = [ - "//policy/src/main/java/dev/cel/policy:policy", - "//policy/src/main/java/dev/cel/policy:source", - "//policy/src/main/java/dev/cel/policy:validation_exception", - "//policy/src/main/java/dev/cel/policy:parser_factory", - "//policy/src/main/java/dev/cel/policy:yaml_parser", - "//policy/src/main/java/dev/cel/policy:parser", - "//policy/src/main/java/dev/cel/policy:parser_builder", + "//policy/src/main/java/dev/cel/policy:compiled_rule", "//policy/src/main/java/dev/cel/policy:compiler", "//policy/src/main/java/dev/cel/policy:compiler_builder", "//policy/src/main/java/dev/cel/policy:compiler_factory", + "//policy/src/main/java/dev/cel/policy:parser", + "//policy/src/main/java/dev/cel/policy:parser_builder", + "//policy/src/main/java/dev/cel/policy:parser_factory", + "//policy/src/main/java/dev/cel/policy:policy", "//policy/src/main/java/dev/cel/policy:policy_parser_context", - "//policy/src/main/java/dev/cel/policy:compiled_rule", + "//policy/src/main/java/dev/cel/policy:source", + "//policy/src/main/java/dev/cel/policy:validation_exception", + "//policy/src/main/java/dev/cel/policy:yaml_parser", ] +# keep sorted V1ALPHA1_AST_TARGETS = [ "//common/src/main/java/dev/cel/common:proto_v1alpha1_ast", ] +# keep sorted CANONICAL_AST_TARGETS = [ "//common/src/main/java/dev/cel/common:proto_ast", ] +# keep sorted EXTENSION_TARGETS = [ "//extensions/src/main/java/dev/cel/extensions", "//extensions/src/main/java/dev/cel/extensions:optional_library", ] +# keep sorted BUNDLE_TARGETS = [ "//bundle/src/main/java/dev/cel/bundle:cel", "//bundle/src/main/java/dev/cel/bundle:environment", "//bundle/src/main/java/dev/cel/bundle:environment_yaml_parser", ] -ALL_TARGETS = BUNDLE_TARGETS + RUNTIME_TARGETS + COMPILER_TARGETS + EXTENSION_TARGETS + V1ALPHA1_AST_TARGETS + CANONICAL_AST_TARGETS + OPTIMIZER_TARGETS + VALIDATOR_TARGETS + POLICY_COMPILER_TARGETS +CEL_MISC_TARGETS = BUNDLE_TARGETS + EXTENSION_TARGETS + OPTIMIZER_TARGETS + VALIDATOR_TARGETS + POLICY_COMPILER_TARGETS # Excluded from the JAR as their source of truth is elsewhere EXCLUDED_TARGETS = [ @@ -103,6 +139,27 @@ JAVA_DOC_OPTIONS = [ "--ignore-source-errors", ] +pom_file( + name = "cel_common_pom", + substitutions = { + "CEL_VERSION": CEL_VERSION, + "CEL_ARTIFACT_ID": "common", + "PACKAGE_NAME": "CEL Java Common", + "PACKAGE_DESC": "Common dependencies for Common Expression Language for Java.", + }, + targets = COMMON_TARGETS, + template_file = "pom_template.xml", +) + +java_export( + name = "cel_common", + deploy_env = EXCLUDED_TARGETS, + javadocopts = JAVA_DOC_OPTIONS, + maven_coordinates = "dev.cel:common:%s" % CEL_VERSION, + pom_template = ":cel_common_pom", + exports = COMMON_TARGETS, +) + pom_file( name = "cel_pom", substitutions = { @@ -111,7 +168,13 @@ pom_file( "PACKAGE_NAME": "CEL Java", "PACKAGE_DESC": "Common Expression Language for Java. This include both the compilation and runtime packages.", }, - targets = ALL_TARGETS, + targets = [ + ":cel_common", + ":cel_protobuf", + ":cel_compiler", + ":cel_runtime", + ":cel_v1alpha1", + ] + CEL_MISC_TARGETS, template_file = "pom_template.xml", ) @@ -121,7 +184,13 @@ java_export( javadocopts = JAVA_DOC_OPTIONS, maven_coordinates = "dev.cel:cel:%s" % CEL_VERSION, pom_template = ":cel_pom", - runtime_deps = ALL_TARGETS, + exports = [ + ":cel_common", + ":cel_compiler", + ":cel_protobuf", + ":cel_runtime", + ":cel_v1alpha1", + ] + CEL_MISC_TARGETS, ) pom_file( @@ -132,7 +201,10 @@ pom_file( "PACKAGE_NAME": "CEL Java Compiler", "PACKAGE_DESC": "Common Expression Language Compiler for Java", }, - targets = COMPILER_TARGETS, + targets = [ + ":cel_common", + ":cel_protobuf", + ] + COMPILER_TARGETS, template_file = "pom_template.xml", ) @@ -142,7 +214,10 @@ java_export( javadocopts = JAVA_DOC_OPTIONS, maven_coordinates = "dev.cel:compiler:%s" % CEL_VERSION, pom_template = ":cel_compiler_pom", - runtime_deps = COMPILER_TARGETS, + exports = [ + ":cel_common", + ":cel_protobuf", + ] + COMPILER_TARGETS, ) pom_file( @@ -153,7 +228,9 @@ pom_file( "PACKAGE_NAME": "CEL Java Runtime", "PACKAGE_DESC": "Common Expression Language Runtime for Java", }, - targets = RUNTIME_TARGETS, + targets = [ + ":cel_common", + ] + RUNTIME_TARGETS, template_file = "pom_template.xml", ) @@ -163,7 +240,9 @@ java_export( javadocopts = JAVA_DOC_OPTIONS, maven_coordinates = "dev.cel:runtime:%s" % CEL_VERSION, pom_template = ":cel_runtime_pom", - runtime_deps = RUNTIME_TARGETS, + exports = [ + ":cel_common", + ] + RUNTIME_TARGETS, ) pom_file( @@ -174,7 +253,9 @@ pom_file( "PACKAGE_NAME": "CEL Java v1alpha1 Utility", "PACKAGE_DESC": "Common Expression Language Utility for supporting v1alpha1 protobuf definitions", }, - targets = V1ALPHA1_AST_TARGETS, + targets = [ + ":cel_common", + ] + V1ALPHA1_AST_TARGETS, template_file = "pom_template.xml", ) @@ -184,7 +265,9 @@ java_export( javadocopts = JAVA_DOC_OPTIONS, maven_coordinates = "dev.cel:v1alpha1:%s" % CEL_VERSION, pom_template = ":cel_v1alpha1_pom", - runtime_deps = V1ALPHA1_AST_TARGETS, + exports = [ + ":cel_common", + ] + V1ALPHA1_AST_TARGETS, ) pom_file( @@ -195,7 +278,9 @@ pom_file( "PACKAGE_NAME": "CEL Java Protobuf adapter", "PACKAGE_DESC": "Common Expression Language Adapter for converting canonical cel.expr protobuf definitions", }, - targets = CANONICAL_AST_TARGETS, + targets = [ + ":cel_common", + ] + CANONICAL_AST_TARGETS, template_file = "pom_template.xml", ) @@ -205,7 +290,9 @@ java_export( javadocopts = JAVA_DOC_OPTIONS, maven_coordinates = "dev.cel:protobuf:%s" % CEL_VERSION, pom_template = ":cel_protobuf_pom", - runtime_deps = CANONICAL_AST_TARGETS, + exports = [ + ":cel_common", + ] + CANONICAL_AST_TARGETS, ) pom_file( @@ -226,5 +313,5 @@ java_export( javadocopts = JAVA_DOC_OPTIONS, maven_coordinates = "dev.cel:runtime-android:%s" % CEL_VERSION, pom_template = ":cel_runtime_android_pom", - runtime_deps = LITE_RUNTIME_TARGETS, + exports = LITE_RUNTIME_TARGETS, ) diff --git a/publish/pom_template.xml b/publish/pom_template.xml index 41b4aa4bb..01e86b98c 100644 --- a/publish/pom_template.xml +++ b/publish/pom_template.xml @@ -20,7 +20,7 @@ CEL_ARTIFACT_ID - {generated_bzl_deps} + {dependencies} @@ -61,4 +61,4 @@ https://github.com/google/cel-java CEL_VERSION - \ No newline at end of file + diff --git a/publish/publish.sh b/publish/publish.sh index 9dc88deda..28d0f0f53 100755 --- a/publish/publish.sh +++ b/publish/publish.sh @@ -26,7 +26,7 @@ # Note, to run script: Bazel and jq are required -ALL_TARGETS=("//publish:cel.publish" "//publish:cel_compiler.publish" "//publish:cel_runtime.publish" "//publish:cel_v1alpha1.publish" "//publish:cel_protobuf.publish" "//publish:cel_runtime_android.publish") +ALL_TARGETS=("//publish:cel_common.publish" "//publish:cel.publish" "//publish:cel_compiler.publish" "//publish:cel_runtime.publish" "//publish:cel_v1alpha1.publish" "//publish:cel_protobuf.publish" "//publish:cel_runtime_android.publish") JDK8_FLAGS="--java_language_version=8 --java_runtime_version=8" function publish_maven_remote() { From 81a869284668082430af64979daf75b652d2cd4b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 18 Feb 2026 14:06:34 -0800 Subject: [PATCH 080/100] Remove StringConversion, StringConcatenation, ListConcatenation CelOptions in favor of subsetting PiperOrigin-RevId: 872034220 --- .../test/java/dev/cel/bundle/CelImplTest.java | 53 ------------------- .../main/java/dev/cel/common/CelOptions.java | 27 ---------- .../java/dev/cel/runtime/CelRuntimeImpl.java | 14 ----- .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 11 ---- .../java/dev/cel/runtime/LiteRuntimeImpl.java | 23 +------- .../runtime/CelLiteRuntimeAndroidTest.java | 3 -- 6 files changed, 1 insertion(+), 130 deletions(-) diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 0708b37c3..9f7083c92 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -1991,59 +1991,6 @@ public void program_nativeTypeUnknownsEnabled_asCallArguments() throws Exception assertThat(result.attributes()).isEmpty(); } - @Test - @TestParameters("{expression: 'string(123)'}") - @TestParameters("{expression: 'string(123u)'}") - @TestParameters("{expression: 'string(1.5)'}") - @TestParameters("{expression: 'string(\"foo\")'}") - @TestParameters("{expression: 'string(b\"foo\")'}") - @TestParameters("{expression: 'string(timestamp(100))'}") - @TestParameters("{expression: 'string(duration(\"1h\"))'}") - public void program_stringConversionDisabled_throws(String expression) throws Exception { - Cel cel = - CelFactory.standardCelBuilder() - .setOptions( - CelOptions.current() - .enableTimestampEpoch(true) - .enableStringConversion(false) - .build()) - .build(); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); - - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); - assertThat(e).hasMessageThat().contains("No matching overload for function 'string'"); - assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND); - } - - @Test - public void program_stringConcatenationDisabled_throws() throws Exception { - Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableStringConcatenation(false).build()) - .build(); - CelAbstractSyntaxTree ast = cel.compile("'foo' + 'bar'").getAst(); - - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); - assertThat(e).hasMessageThat().contains("No matching overload for function '_+_'"); - assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND); - } - - @Test - public void program_listConcatenationDisabled_throws() throws Exception { - Cel cel = - CelFactory.standardCelBuilder() - .setOptions(CelOptions.current().enableListConcatenation(false).build()) - .build(); - CelAbstractSyntaxTree ast = cel.compile("[1] + [2]").getAst(); - - CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); - assertThat(e).hasMessageThat().contains("No matching overload for function '_+_'"); - assertThat(e.getErrorCode()).isEqualTo(CelErrorCode.OVERLOAD_NOT_FOUND); - } - @Test public void program_comprehensionDisabled_throws() throws Exception { Cel cel = diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java index d0b020697..9cf9a9caa 100644 --- a/common/src/main/java/dev/cel/common/CelOptions.java +++ b/common/src/main/java/dev/cel/common/CelOptions.java @@ -117,12 +117,6 @@ public enum ProtoUnsetFieldOptions { public abstract ProtoUnsetFieldOptions fromProtoUnsetFieldOption(); - public abstract boolean enableStringConversion(); - - public abstract boolean enableStringConcatenation(); - - public abstract boolean enableListConcatenation(); - public abstract boolean enableComprehension(); public abstract int maxRegexProgramSize(); @@ -169,9 +163,6 @@ public static Builder newBuilder() { .comprehensionMaxIterations(-1) .unwrapWellKnownTypesOnFunctionDispatch(true) .fromProtoUnsetFieldOption(ProtoUnsetFieldOptions.BIND_DEFAULT) - .enableStringConversion(true) - .enableStringConcatenation(true) - .enableListConcatenation(true) .enableComprehension(true) .maxRegexProgramSize(-1); } @@ -494,24 +485,6 @@ public abstract static class Builder { */ public abstract Builder fromProtoUnsetFieldOption(ProtoUnsetFieldOptions value); - /** - * Enables string() overloads for the runtime. This option exists to maintain parity with - * cel-cpp interpreter options. - */ - public abstract Builder enableStringConversion(boolean value); - - /** - * Enables string concatenation overload for the runtime. This option exists to maintain parity - * with cel-cpp interpreter options. - */ - public abstract Builder enableStringConcatenation(boolean value); - - /** - * Enables list concatenation overload for the runtime. This option exists to maintain parity - * with cel-cpp interpreter options. - */ - public abstract Builder enableListConcatenation(boolean value); - /** * Enables comprehension (macros) for the runtime. Setting false has the same effect with * assigning 0 for {@link #comprehensionMaxIterations()}. This option exists to maintain parity diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index 27bd1b3fc..f3894b0e1 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -338,20 +338,6 @@ private static void assertAllowedCelOptions(CelOptions celOptions) { // Disallowed options in favor of subsetting String subsettingError = "Subset the environment instead using setStandardFunctions method."; - if (!celOptions.enableStringConcatenation()) { - throw new IllegalArgumentException( - prefix + "enableStringConcatenation cannot be disabled. " + subsettingError); - } - - if (!celOptions.enableStringConversion()) { - throw new IllegalArgumentException( - prefix + "enableStringConversion cannot be disabled. " + subsettingError); - } - - if (!celOptions.enableListConcatenation()) { - throw new IllegalArgumentException( - prefix + "enableListConcatenation cannot be disabled. " + subsettingError); - } if (!celOptions.enableTimestampEpoch()) { throw new IllegalArgumentException( diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index 60c2642d7..ebd678f24 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -45,7 +45,6 @@ import dev.cel.common.types.CelTypes; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.ProtoMessageValueProvider; -import dev.cel.runtime.standard.AddOperator.AddOverload; import dev.cel.runtime.standard.IntFunction.IntOverload; import dev.cel.runtime.standard.TimestampFunction.TimestampOverload; import java.util.Arrays; @@ -381,16 +380,6 @@ private ImmutableSet newStandardFunctionBindings( return options.enableTimestampEpoch(); } break; - case STRING: - return options.enableStringConversion(); - case ADD: - if (standardOverload.equals(AddOverload.ADD_STRING)) { - return options.enableStringConcatenation(); - } - if (standardOverload.equals(AddOverload.ADD_LIST)) { - return options.enableListConcatenation(); - } - break; default: if (!options.enableHeterogeneousNumericComparisons()) { return !CelStandardFunctions.isHeterogeneousComparison( diff --git a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java index 45c322da9..0e5c5cf30 100644 --- a/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/LiteRuntimeImpl.java @@ -132,24 +132,6 @@ private static void assertAllowedCelOptions(CelOptions celOptions) { throw new IllegalArgumentException( prefix + "unwrapWellKnownTypesOnFunctionDispatch cannot be disabled."); } - if (!celOptions.enableStringConcatenation()) { - throw new IllegalArgumentException( - prefix - + "enableStringConcatenation cannot be disabled. Subset the environment instead" - + " using setStandardFunctions method."); - } - if (!celOptions.enableStringConversion()) { - throw new IllegalArgumentException( - prefix - + "enableStringConversion cannot be disabled. Subset the environment instead using" - + " setStandardFunctions method."); - } - if (!celOptions.enableListConcatenation()) { - throw new IllegalArgumentException( - prefix - + "enableListConcatenation cannot be disabled. Subset the environment instead using" - + " setStandardFunctions method."); - } } @Override @@ -201,10 +183,7 @@ public CelLiteRuntime build() { } private Builder() { - this.celOptions = - CelOptions.current() - .enableCelValue(true) - .build(); + this.celOptions = CelOptions.current().enableCelValue(true).build(); this.celValueProvider = (structType, fields) -> Optional.empty(); this.customFunctionBindings = new HashMap<>(); this.standardFunctionBuilder = ImmutableSet.builder(); diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java index 638782c2e..54ce24417 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java @@ -715,9 +715,6 @@ private enum CelOptionsTestCase { CEL_VALUE_DISABLED(newBaseTestOptions().enableCelValue(false).build()), UNSIGNED_LONG_DISABLED(newBaseTestOptions().enableUnsignedLongs(false).build()), UNWRAP_WKT_DISABLED(newBaseTestOptions().unwrapWellKnownTypesOnFunctionDispatch(false).build()), - STRING_CONCAT_DISABLED(newBaseTestOptions().enableStringConcatenation(false).build()), - STRING_CONVERSION_DISABLED(newBaseTestOptions().enableStringConversion(false).build()), - LIST_CONCATENATION_DISABLED(newBaseTestOptions().enableListConcatenation(false).build()), ; private final CelOptions celOptions; From a50bbcf8acd6bafb97a270b21bf91956b4420836 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 18 Feb 2026 16:17:04 -0800 Subject: [PATCH 081/100] Add isolated artifact tests for dev.cel:compiler and dev.cel:runtime Includes some CI optimizations for preventing protoc recompilation for Bazel and proper workflow caching PiperOrigin-RevId: 872090337 --- .bazelrc | 5 + .github/workflows/workflow.yml | 8 +- MODULE.bazel | 18 +- .../test/java/dev/cel/conformance/BUILD.bazel | 8 +- .../src/test/java/dev/cel/maven/BUILD.bazel | 47 ++ .../dev/cel/maven/CompilerArtifactTest.java | 106 +++++ .../dev/cel/maven/RuntimeArtifactTest.java | 413 ++++++++++++++++++ publish/cel_version.bzl | 2 +- 8 files changed, 598 insertions(+), 9 deletions(-) create mode 100644 conformance/src/test/java/dev/cel/maven/BUILD.bazel create mode 100644 conformance/src/test/java/dev/cel/maven/CompilerArtifactTest.java create mode 100644 conformance/src/test/java/dev/cel/maven/RuntimeArtifactTest.java diff --git a/.bazelrc b/.bazelrc index 7bec7cc95..968597053 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,9 +1,14 @@ common --enable_bzlmod +# Use built-in protoc +common --incompatible_enable_proto_toolchain_resolution --@com_google_protobuf//bazel/toolchains:prefer_prebuilt_protoc + build --java_runtime_version=remotejdk_11 build --java_language_version=11 + # Hide Java 8 deprecation warnings. common --javacopt=-Xlint:-options # Remove flag once https://github.com/google/cel-spec/issues/508 and rules_jvm_external is fixed. common --incompatible_autoload_externally=proto_library,cc_proto_library,java_proto_library,java_test + diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 19732cd9e..060b83bdd 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -33,6 +33,8 @@ jobs: disk-cache: ${{ github.workflow }} # Share repository cache between workflows. repository-cache: true + # Never write to the cache, strictly read-only + cache-save: false - name: Unwanted Dependencies run: .github/workflows/unwanted_deps.sh - name: Cross-artifact Duplicate Classes Check @@ -56,6 +58,8 @@ jobs: disk-cache: ${{ github.workflow }} # Share repository cache between workflows. repository-cache: true + # Prevent PRs from polluting cache + cache-save: ${{ github.event_name != 'pull_request' }} - name: Bazel Output Version run: bazelisk --version - name: Java 8 Build @@ -91,12 +95,14 @@ jobs: disk-cache: ${{ github.workflow }} # Share repository cache between workflows. repository-cache: true + # Never write to the cache, strictly read-only + cache-save: false - name: Verify Version Consistency if: steps.changed_file.outputs.any_changed == 'true' run: | CEL_VERSION=$(grep 'CEL_VERSION =' publish/cel_version.bzl | cut -d '"' -f 2) - MODULE_VERSION=$(grep 'dev.cel:cel' MODULE.bazel | cut -d '"' -f 2 | cut -d ':' -f 3) + MODULE_VERSION=$(grep 'CEL_VERSION =' MODULE.bazel | cut -d '"' -f 2) if [ -z "$CEL_VERSION" ] || [ -z "$MODULE_VERSION" ]; then echo "❌ Error: Could not extract one or both version strings." diff --git a/MODULE.bazel b/MODULE.bazel index b3a78627c..007adcb3c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,16 +16,16 @@ module( name = "cel_java", ) -bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "rules_jvm_external", version = "6.9") -bazel_dep(name = "protobuf", version = "29.3", repo_name = "com_google_protobuf") # see https://github.com/bazelbuild/rules_android/issues/373 +bazel_dep(name = "protobuf", version = "33.4", repo_name = "com_google_protobuf") # see https://github.com/bazelbuild/rules_android/issues/373 bazel_dep(name = "googleapis", version = "0.0.0-20241220-5e258e33.bcr.1", repo_name = "com_google_googleapis") bazel_dep(name = "rules_pkg", version = "1.0.1") bazel_dep(name = "rules_license", version = "1.0.0") bazel_dep(name = "rules_proto", version = "7.1.0") -bazel_dep(name = "rules_java", version = "8.12.0") +bazel_dep(name = "rules_java", version = "9.3.0") bazel_dep(name = "rules_android", version = "0.7.1") -bazel_dep(name = "rules_shell", version = "0.5.1") +bazel_dep(name = "rules_shell", version = "0.6.1") bazel_dep(name = "googleapis-java", version = "1.0.0") bazel_dep(name = "cel-spec", version = "0.24.0", repo_name = "cel_spec") @@ -39,7 +39,9 @@ GUAVA_VERSION = "33.5.0" TRUTH_VERSION = "1.4.4" -PROTOBUF_JAVA_VERSION = "4.33.4" +PROTOBUF_JAVA_VERSION = "4.33.5" + +CEL_VERSION = "0.12.0-SNAPSHOT" # Compile only artifacts [ @@ -114,7 +116,11 @@ maven.install( maven.install( name = "maven_conformance", - artifacts = ["dev.cel:cel:0.11.1"], + artifacts = [ + "dev.cel:cel:" + CEL_VERSION, + "dev.cel:compiler:" + CEL_VERSION, + "dev.cel:runtime:" + CEL_VERSION, + ], repositories = [ "https://maven.google.com", "https://repo1.maven.org/maven2", diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index 248bc7fe2..ab7468e54 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -41,7 +41,13 @@ java_library( ], ) -MAVEN_JAR_DEPS = ["@maven_conformance//:dev_cel_cel"] +MAVEN_JAR_DEPS = [ + "@maven_conformance//:dev_cel_compiler", + "@maven_conformance//:dev_cel_common", + "@maven_conformance//:dev_cel_runtime", + "@maven_conformance//:dev_cel_protobuf", + "@maven_conformance//:dev_cel_cel", +] java_library( name = "run_maven_jar", diff --git a/conformance/src/test/java/dev/cel/maven/BUILD.bazel b/conformance/src/test/java/dev/cel/maven/BUILD.bazel new file mode 100644 index 000000000..895339521 --- /dev/null +++ b/conformance/src/test/java/dev/cel/maven/BUILD.bazel @@ -0,0 +1,47 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_applicable_licenses = [ + "//:license", +]) + +# keep sorted +MAVEN_COMPILER_JAR_DEPS = [ + "@maven_conformance//:dev_cel_common", + "@maven_conformance//:dev_cel_compiler", +] + +# keep sorted +MAVEN_RUNTIME_JAR_DEPS = [ + "@maven_conformance//:dev_cel_common", + "@maven_conformance//:dev_cel_protobuf", + "@maven_conformance//:dev_cel_runtime", +] + +java_test( + name = "compiler_artifact_test", + srcs = ["CompilerArtifactTest.java"], + test_class = "dev.cel.maven.CompilerArtifactTest", + deps = + MAVEN_COMPILER_JAR_DEPS + [ + "//:java_truth", + "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) + +java_test( + name = "runtime_artifact_test", + srcs = ["RuntimeArtifactTest.java"], + test_class = "dev.cel.maven.RuntimeArtifactTest", + deps = + MAVEN_RUNTIME_JAR_DEPS + [ + "//:java_truth", + "@cel_spec//proto/cel/expr:checked_java_proto", + "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + "@maven//:com_google_testparameterinjector_test_parameter_injector", + "@maven//:junit_junit", + ], +) diff --git a/conformance/src/test/java/dev/cel/maven/CompilerArtifactTest.java b/conformance/src/test/java/dev/cel/maven/CompilerArtifactTest.java new file mode 100644 index 000000000..dcdedb157 --- /dev/null +++ b/conformance/src/test/java/dev/cel/maven/CompilerArtifactTest.java @@ -0,0 +1,106 @@ +// 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.maven; + +import static com.google.common.truth.Truth.assertThat; +import static dev.cel.common.CelFunctionDecl.newFunctionDeclaration; +import static dev.cel.common.CelOverloadDecl.newGlobalOverload; +import static org.junit.Assert.assertThrows; + +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.checker.CelChecker; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; +import dev.cel.common.CelOptions; +import dev.cel.common.CelValidationException; +import dev.cel.common.CelValidationResult; +import dev.cel.common.ast.CelConstant; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.types.ProtoMessageTypeProvider; +import dev.cel.common.types.SimpleType; +import dev.cel.common.types.StructTypeReference; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerFactory; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.parser.CelParser; +import dev.cel.parser.CelParserFactory; +import dev.cel.parser.CelStandardMacro; +import dev.cel.parser.CelUnparser; +import dev.cel.parser.CelUnparserFactory; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class CompilerArtifactTest { + + @Test + public void parse() throws Exception { + CelParser parser = CelParserFactory.standardCelParserBuilder().build(); + CelUnparser unparser = CelUnparserFactory.newUnparser(); + + CelAbstractSyntaxTree ast = parser.parse("'Hello World'").getAst(); + + assertThat(ast.getExpr()).isEqualTo(CelExpr.ofConstant(1L, CelConstant.ofValue("Hello World"))); + assertThat(unparser.unparse(ast)).isEqualTo("\"Hello World\""); + } + + @Test + public void typeCheck() throws Exception { + CelParser parser = CelParserFactory.standardCelParserBuilder().build(); + CelChecker checker = CelCompilerFactory.standardCelCheckerBuilder().build(); + + CelAbstractSyntaxTree ast = checker.check(parser.parse("'Hello World'").getAst()).getAst(); + + assertThat(ast.getResultType()).isEqualTo(SimpleType.STRING); + } + + @Test + public void compile() throws Exception { + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addFunctionDeclarations( + newFunctionDeclaration( + "getThree", newGlobalOverload("getThree_overload", SimpleType.INT))) + .setOptions(CelOptions.DEFAULT) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .setTypeProvider( + ProtoMessageTypeProvider.newBuilder() + .addFileDescriptors(TestAllTypes.getDescriptor().getFile()) + .build()) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) + .build(); + CelUnparser unparser = CelUnparserFactory.newUnparser(); + + CelAbstractSyntaxTree ast = + compiler.compile("msg == TestAllTypes{} && 3 == getThree()").getAst(); + + assertThat(unparser.unparse(ast)) + .isEqualTo("msg == cel.expr.conformance.proto3.TestAllTypes{} && 3 == getThree()"); + assertThat(ast.getResultType()).isEqualTo(SimpleType.BOOL); + } + + @Test + public void compile_error() { + CelCompiler compiler = CelCompilerFactory.standardCelCompilerBuilder().build(); + + CelValidationResult result = compiler.compile("'foo' + 1"); + + assertThat(result.hasError()).isTrue(); + assertThat(assertThrows(CelValidationException.class, result::getAst)) + .hasMessageThat() + .contains("found no matching overload for '_+_' applied to '(string, int)'"); + } +} diff --git a/conformance/src/test/java/dev/cel/maven/RuntimeArtifactTest.java b/conformance/src/test/java/dev/cel/maven/RuntimeArtifactTest.java new file mode 100644 index 000000000..e129b85d3 --- /dev/null +++ b/conformance/src/test/java/dev/cel/maven/RuntimeArtifactTest.java @@ -0,0 +1,413 @@ +// 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.maven; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import dev.cel.expr.CheckedExpr; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.TextFormat; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelOptions; +import dev.cel.common.CelProtoAbstractSyntaxTree; +import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.runtime.CelEvaluationException; +import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.runtime.CelStandardFunctions; +import dev.cel.runtime.CelStandardFunctions.StandardFunction; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class RuntimeArtifactTest { + + // Serialized expr: [TestAllTypes{single_int64: 1}.single_int64, 2].exists(x, x == 2) + private static final String CHECKED_EXPR = + "reference_map {\n" + + " key: 2\n" + + " value {\n" + + " name: \"cel.expr.conformance.proto3.TestAllTypes\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 7\n" + + " value {\n" + + " overload_id: \"getThree_overload\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 10\n" + + " value {\n" + + " name: \"x\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 11\n" + + " value {\n" + + " overload_id: \"greater_equals_int64\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 14\n" + + " value {\n" + + " name: \"@result\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 15\n" + + " value {\n" + + " overload_id: \"not_strictly_false\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 16\n" + + " value {\n" + + " name: \"@result\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 17\n" + + " value {\n" + + " overload_id: \"logical_and\"\n" + + " }\n" + + "}\n" + + "reference_map {\n" + + " key: 18\n" + + " value {\n" + + " name: \"@result\"\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 1\n" + + " value {\n" + + " list_type {\n" + + " elem_type {\n" + + " primitive: INT64\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 2\n" + + " value {\n" + + " message_type: \"cel.expr.conformance.proto3.TestAllTypes\"\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 4\n" + + " value {\n" + + " primitive: INT64\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 5\n" + + " value {\n" + + " primitive: INT64\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 6\n" + + " value {\n" + + " primitive: INT64\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 7\n" + + " value {\n" + + " primitive: INT64\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 10\n" + + " value {\n" + + " primitive: INT64\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 11\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 12\n" + + " value {\n" + + " primitive: INT64\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 13\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 14\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 15\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 16\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 17\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 18\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "type_map {\n" + + " key: 19\n" + + " value {\n" + + " primitive: BOOL\n" + + " }\n" + + "}\n" + + "expr {\n" + + " id: 19\n" + + " comprehension_expr {\n" + + " iter_var: \"x\"\n" + + " iter_range {\n" + + " id: 1\n" + + " list_expr {\n" + + " elements {\n" + + " id: 5\n" + + " select_expr {\n" + + " operand {\n" + + " id: 2\n" + + " struct_expr {\n" + + " message_name: \"cel.expr.conformance.proto3.TestAllTypes\"\n" + + " entries {\n" + + " id: 3\n" + + " field_key: \"single_int64\"\n" + + " value {\n" + + " id: 4\n" + + " const_expr {\n" + + " int64_value: 1\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " field: \"single_int64\"\n" + + " }\n" + + " }\n" + + " elements {\n" + + " id: 6\n" + + " const_expr {\n" + + " int64_value: 2\n" + + " }\n" + + " }\n" + + " elements {\n" + + " id: 7\n" + + " call_expr {\n" + + " function: \"getThree\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " accu_var: \"@result\"\n" + + " accu_init {\n" + + " id: 13\n" + + " const_expr {\n" + + " bool_value: true\n" + + " }\n" + + " }\n" + + " loop_condition {\n" + + " id: 15\n" + + " call_expr {\n" + + " function: \"@not_strictly_false\"\n" + + " args {\n" + + " id: 14\n" + + " ident_expr {\n" + + " name: \"@result\"\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " loop_step {\n" + + " id: 17\n" + + " call_expr {\n" + + " function: \"_&&_\"\n" + + " args {\n" + + " id: 16\n" + + " ident_expr {\n" + + " name: \"@result\"\n" + + " }\n" + + " }\n" + + " args {\n" + + " id: 11\n" + + " call_expr {\n" + + " function: \"_>=_\"\n" + + " args {\n" + + " id: 10\n" + + " ident_expr {\n" + + " name: \"x\"\n" + + " }\n" + + " }\n" + + " args {\n" + + " id: 12\n" + + " const_expr {\n" + + " int64_value: 0\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " result {\n" + + " id: 18\n" + + " ident_expr {\n" + + " name: \"@result\"\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n" + + "source_info {\n" + + " location: \"\"\n" + + " line_offsets: 75\n" + + " positions {\n" + + " key: 1\n" + + " value: 0\n" + + " }\n" + + " positions {\n" + + " key: 2\n" + + " value: 13\n" + + " }\n" + + " positions {\n" + + " key: 3\n" + + " value: 26\n" + + " }\n" + + " positions {\n" + + " key: 4\n" + + " value: 28\n" + + " }\n" + + " positions {\n" + + " key: 5\n" + + " value: 30\n" + + " }\n" + + " positions {\n" + + " key: 6\n" + + " value: 45\n" + + " }\n" + + " positions {\n" + + " key: 7\n" + + " value: 56\n" + + " }\n" + + " positions {\n" + + " key: 9\n" + + " value: 64\n" + + " }\n" + + " positions {\n" + + " key: 10\n" + + " value: 67\n" + + " }\n" + + " positions {\n" + + " key: 11\n" + + " value: 69\n" + + " }\n" + + " positions {\n" + + " key: 12\n" + + " value: 72\n" + + " }\n" + + " positions {\n" + + " key: 13\n" + + " value: 63\n" + + " }\n" + + " positions {\n" + + " key: 14\n" + + " value: 63\n" + + " }\n" + + " positions {\n" + + " key: 15\n" + + " value: 63\n" + + " }\n" + + " positions {\n" + + " key: 16\n" + + " value: 63\n" + + " }\n" + + " positions {\n" + + " key: 17\n" + + " value: 63\n" + + " }\n" + + " positions {\n" + + " key: 18\n" + + " value: 63\n" + + " }\n" + + " positions {\n" + + " key: 19\n" + + " value: 63\n" + + " }\n" + + "}\n"; + + @Test + public void eval() throws Exception { + CelRuntime runtime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .setOptions(CelOptions.DEFAULT) + .setStandardEnvironmentEnabled(false) + .addFunctionBindings( + CelFunctionBinding.fromOverloads( + "getThree", + CelFunctionBinding.from("getThree_overload", ImmutableList.of(), arg -> 3L))) + .setStandardFunctions( + CelStandardFunctions.newBuilder() + .includeFunctions( + StandardFunction.GREATER_EQUALS, + StandardFunction.LOGICAL_NOT, + StandardFunction.NOT_STRICTLY_FALSE) + .build()) + .addMessageTypes(TestAllTypes.getDescriptor()) + .build(); + CheckedExpr checkedExpr = TextFormat.parse(CHECKED_EXPR, CheckedExpr.class); + CelAbstractSyntaxTree ast = CelProtoAbstractSyntaxTree.fromCheckedExpr(checkedExpr).getAst(); + + boolean result = (boolean) runtime.createProgram(ast).eval(); + + assertThat(result).isTrue(); + } + + @Test + public void eval_error() throws Exception { + CelRuntime runtime = + CelRuntimeFactory.standardCelRuntimeBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .build(); + CheckedExpr checkedExpr = TextFormat.parse(CHECKED_EXPR, CheckedExpr.class); + CelAbstractSyntaxTree ast = CelProtoAbstractSyntaxTree.fromCheckedExpr(checkedExpr).getAst(); + + assertThat(assertThrows(CelEvaluationException.class, () -> runtime.createProgram(ast).eval())) + .hasMessageThat() + .contains("No matching overload for function 'getThree'"); + } +} diff --git a/publish/cel_version.bzl b/publish/cel_version.bzl index 7938f15a1..ea793eee5 100644 --- a/publish/cel_version.bzl +++ b/publish/cel_version.bzl @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. """Maven artifact version for CEL.""" -CEL_VERSION = "0.11.1" +CEL_VERSION = "0.12.0-SNAPSHOT" From 1c5e9d9fd8e3740af7b138a465486ee4f5ad4732 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 19 Feb 2026 14:34:19 -0800 Subject: [PATCH 082/100] Allow google.protobuf.Timestamp and google.protobuf.Duration to be created for planner PiperOrigin-RevId: 872569213 --- .../cel/common/values/ProtoCelValueConverter.java | 10 ++++++++-- .../main/java/dev/cel/runtime/planner/BUILD.bazel | 2 +- .../dev/cel/runtime/planner/EvalCreateStruct.java | 11 ++++++----- .../dev/cel/runtime/planner/ProgramPlanner.java | 13 ++++++++----- .../dev/cel/runtime/PlannerInterpreterTest.java | 6 ------ 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index 14ff87f1b..cf70f52e5 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -151,9 +151,15 @@ public Object fromProtoMessageFieldToCelValue(Message message, FieldDescriptor f return toRuntimeValue(result); case UINT32: - return UnsignedLong.valueOf((int) result); + if (!fieldDescriptor.isRepeated()) { + return UnsignedLong.valueOf((int) result); + } + break; case UINT64: - return UnsignedLong.fromLongBits((long) result); + if (!fieldDescriptor.isRepeated()) { + return UnsignedLong.fromLongBits((long) result); + } + break; default: break; } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index c7e4103af..9ede866ab 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -297,7 +297,7 @@ java_library( deps = [ ":execution_frame", ":planned_interpretable", - "//common/types", + "//common/types:type_providers", "//common/values", "//common/values:cel_value_provider", "//runtime:evaluation_exception", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java index f1d6f75e5..7d03854c2 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -15,7 +15,7 @@ package dev.cel.runtime.planner; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.types.StructType; +import dev.cel.common.types.CelType; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.StructValue; import dev.cel.runtime.CelEvaluationException; @@ -28,7 +28,7 @@ final class EvalCreateStruct extends PlannedInterpretable { private final CelValueProvider valueProvider; - private final StructType structType; + private final CelType structType; // Array contents are not mutated @SuppressWarnings("Immutable") @@ -50,7 +50,8 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval Object value = valueProvider .newValue(structType.name(), Collections.unmodifiableMap(fieldValues)) - .orElseThrow(() -> new IllegalArgumentException("Type name not found: " + structType)); + .orElseThrow( + () -> new IllegalArgumentException("Type name not found: " + structType.name())); if (value instanceof StructValue) { return ((StructValue) value).value(); @@ -62,7 +63,7 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval static EvalCreateStruct create( long exprId, CelValueProvider valueProvider, - StructType structType, + CelType structType, String[] keys, PlannedInterpretable[] values) { return new EvalCreateStruct(exprId, valueProvider, structType, keys, values); @@ -71,7 +72,7 @@ static EvalCreateStruct create( private EvalCreateStruct( long exprId, CelValueProvider valueProvider, - StructType structType, + CelType structType, String[] keys, PlannedInterpretable[] values) { super(exprId); diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index 7f8b8a0e0..fc22d4f10 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -41,7 +41,6 @@ import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.SimpleType; -import dev.cel.common.types.StructType; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.CelValueProvider; @@ -277,7 +276,7 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) { private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { CelStruct struct = celExpr.struct(); - StructType structType = resolveStructType(struct); + CelType structType = resolveStructType(struct); ImmutableList entries = struct.entries(); String[] keys = new String[entries.size()]; @@ -414,7 +413,7 @@ private ResolvedFunction resolveFunction( return ResolvedFunction.newBuilder().setFunctionName(functionName).setTarget(target).build(); } - private StructType resolveStructType(CelStruct struct) { + private CelType resolveStructType(CelStruct struct) { String messageName = struct.messageName(); for (String typeName : container.resolveCandidateNames(messageName)) { CelType structType = typeProvider.findType(typeName).orElse(null); @@ -422,13 +421,17 @@ private StructType resolveStructType(CelStruct struct) { continue; } - if (!structType.kind().equals(CelKind.STRUCT)) { + CelKind kind = structType.kind(); + + if (!kind.equals(CelKind.STRUCT) + && !kind.equals(CelKind.TIMESTAMP) + && !kind.equals(CelKind.DURATION)) { throw new IllegalArgumentException( String.format( "Expected struct type for %s, got %s", structType.name(), structType.kind())); } - return (StructType) structType; + return structType; } throw new IllegalArgumentException("Undefined type name: " + messageName); diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index ea8e52d0c..13a0f51a1 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -51,10 +51,4 @@ public void jsonFieldNames() throws Exception { // TODO: Support JSON field names for planner skipBaselineVerification(); } - - @Override - public void wrappers() throws Exception { - // TODO: Fix for planner - skipBaselineVerification(); - } } From b68d0eae2e297b32ada0ecf65e34ac8b972b5960 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 19 Feb 2026 16:10:42 -0800 Subject: [PATCH 083/100] Add JSON field name resolution to planner PiperOrigin-RevId: 872610068 --- .../java/dev/cel/common/values/BUILD.bazel | 1 + .../common/values/ProtoCelValueConverter.java | 14 +++-- .../cel/common/values/ProtoMessageValue.java | 16 +++++- .../values/ProtoMessageValueProvider.java | 13 ++++- .../values/ProtoCelValueConverterTest.java | 5 +- .../common/values/ProtoMessageValueTest.java | 57 +++++++++++++------ .../cel/runtime/planner/PlannedProgram.java | 12 +++- .../cel/runtime/PlannerInterpreterTest.java | 6 -- .../runtime/planner/ProgramPlannerTest.java | 2 +- 9 files changed, 90 insertions(+), 36 deletions(-) diff --git a/common/src/main/java/dev/cel/common/values/BUILD.bazel b/common/src/main/java/dev/cel/common/values/BUILD.bazel index bacd9d1fc..53ffdda3d 100644 --- a/common/src/main/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/values/BUILD.bazel @@ -191,6 +191,7 @@ java_library( ":base_proto_cel_value_converter", ":values", "//:auto_value", + "//common:options", "//common/annotations", "//common/internal:cel_descriptor_pools", "//common/internal:dynamic_proto", diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index cf70f52e5..c7b829e13 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -26,6 +26,7 @@ import com.google.protobuf.Message; import com.google.protobuf.MessageLiteOrBuilder; import com.google.protobuf.MessageOrBuilder; +import dev.cel.common.CelOptions; import dev.cel.common.annotations.Internal; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DynamicProto; @@ -49,11 +50,12 @@ public final class ProtoCelValueConverter extends BaseProtoCelValueConverter { private final CelDescriptorPool celDescriptorPool; private final DynamicProto dynamicProto; + private final CelOptions celOptions; /** Constructs a new instance of ProtoCelValueConverter. */ public static ProtoCelValueConverter newInstance( - CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) { - return new ProtoCelValueConverter(celDescriptorPool, dynamicProto); + CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto, CelOptions celOptions) { + return new ProtoCelValueConverter(celDescriptorPool, dynamicProto, celOptions); } @Override @@ -97,7 +99,8 @@ public Object toRuntimeValue(Object value) { WellKnownProto wellKnownProto = WellKnownProto.getByTypeName(message.getDescriptorForType().getFullName()).orElse(null); if (wellKnownProto == null) { - return ProtoMessageValue.create((Message) message, celDescriptorPool, this); + return ProtoMessageValue.create( + message, celDescriptorPool, this, celOptions.enableJsonFieldNames()); } return fromWellKnownProto(message, wellKnownProto); @@ -167,10 +170,13 @@ public Object fromProtoMessageFieldToCelValue(Message message, FieldDescriptor f return toRuntimeValue(result); } - private ProtoCelValueConverter(CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto) { + private ProtoCelValueConverter( + CelDescriptorPool celDescriptorPool, DynamicProto dynamicProto, CelOptions celOptions) { Preconditions.checkNotNull(celDescriptorPool); Preconditions.checkNotNull(dynamicProto); + Preconditions.checkNotNull(celOptions); this.celDescriptorPool = celDescriptorPool; this.dynamicProto = dynamicProto; + this.celOptions = celOptions; } } diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java index 3e809975e..e402bb429 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValue.java @@ -40,6 +40,8 @@ public abstract class ProtoMessageValue extends StructValue { abstract ProtoCelValueConverter protoCelValueConverter(); + abstract boolean enableJsonFieldNames(); + @Override public boolean isZeroValue() { return value().getDefaultInstanceForType().equals(value()); @@ -75,7 +77,8 @@ public Optional find(String field) { public static ProtoMessageValue create( Message value, CelDescriptorPool celDescriptorPool, - ProtoCelValueConverter protoCelValueConverter) { + ProtoCelValueConverter protoCelValueConverter, + boolean enableJsonFieldNames) { Preconditions.checkNotNull(value); Preconditions.checkNotNull(celDescriptorPool); Preconditions.checkNotNull(protoCelValueConverter); @@ -83,7 +86,8 @@ public static ProtoMessageValue create( value, StructTypeReference.create(value.getDescriptorForType().getFullName()), celDescriptorPool, - protoCelValueConverter); + protoCelValueConverter, + enableJsonFieldNames); } private FieldDescriptor findField( @@ -93,6 +97,14 @@ private FieldDescriptor findField( return fieldDescriptor; } + if (enableJsonFieldNames()) { + for (FieldDescriptor fd : descriptor.getFields()) { + if (fd.getJsonName().equals(fieldName)) { + return fd; + } + } + } + return celDescriptorPool .findExtensionDescriptor(descriptor, fieldName) .orElseThrow( diff --git a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java index a05658c8f..b7895d845 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java +++ b/common/src/main/java/dev/cel/common/values/ProtoMessageValueProvider.java @@ -38,6 +38,7 @@ public class ProtoMessageValueProvider implements CelValueProvider { private final ProtoAdapter protoAdapter; private final ProtoMessageFactory protoMessageFactory; private final ProtoCelValueConverter protoCelValueConverter; + private final CelOptions celOptions; @Override public CelValueConverter celValueConverter() { @@ -72,6 +73,14 @@ private FieldDescriptor findField(Descriptor descriptor, String fieldName) { return fieldDescriptor; } + if (celOptions.enableJsonFieldNames()) { + for (FieldDescriptor fd : descriptor.getFields()) { + if (fd.getJsonName().equals(fieldName)) { + return fd; + } + } + } + return protoMessageFactory .getDescriptorPool() .findExtensionDescriptor(descriptor, fieldName) @@ -91,7 +100,9 @@ public static ProtoMessageValueProvider newInstance( private ProtoMessageValueProvider(CelOptions celOptions, DynamicProto dynamicProto) { this.protoMessageFactory = dynamicProto.getProtoMessageFactory(); this.protoCelValueConverter = - ProtoCelValueConverter.newInstance(protoMessageFactory.getDescriptorPool(), dynamicProto); + ProtoCelValueConverter.newInstance( + protoMessageFactory.getDescriptorPool(), dynamicProto, celOptions); this.protoAdapter = new ProtoAdapter(dynamicProto, celOptions); + this.celOptions = celOptions; } } diff --git a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java index 61a05cf89..a517931c2 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -16,6 +16,7 @@ import static com.google.common.truth.Truth.assertThat; +import dev.cel.common.CelOptions; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; @@ -28,7 +29,9 @@ public class ProtoCelValueConverterTest { private static final ProtoCelValueConverter PROTO_CEL_VALUE_CONVERTER = ProtoCelValueConverter.newInstance( - DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); + DefaultDescriptorPool.INSTANCE, + DynamicProto.create(DefaultMessageFactory.INSTANCE), + CelOptions.DEFAULT); @Test public void unwrap_nullValue() { diff --git a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java index ad61f0c4b..365dd32b4 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueTest.java @@ -35,6 +35,7 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.common.CelDescriptorUtil; +import dev.cel.common.CelOptions; import dev.cel.common.internal.CelDescriptorPool; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; @@ -54,7 +55,9 @@ public final class ProtoMessageValueTest { private static final ProtoCelValueConverter PROTO_CEL_VALUE_CONVERTER = ProtoCelValueConverter.newInstance( - DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); + DefaultDescriptorPool.INSTANCE, + DynamicProto.create(DefaultMessageFactory.INSTANCE), + CelOptions.DEFAULT); @Test public void emptyProtoMessage() { @@ -62,7 +65,8 @@ public void emptyProtoMessage() { ProtoMessageValue.create( TestAllTypes.getDefaultInstance(), DefaultDescriptorPool.INSTANCE, - PROTO_CEL_VALUE_CONVERTER); + PROTO_CEL_VALUE_CONVERTER, + /* enableJsonFieldNames= */ false); assertThat(protoMessageValue.value()).isEqualTo(TestAllTypes.getDefaultInstance()); assertThat(protoMessageValue.isZeroValue()).isTrue(); @@ -74,7 +78,7 @@ public void constructProtoMessage() { TestAllTypes.newBuilder().setSingleBool(true).setSingleInt64(5L).build(); ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.value()).isEqualTo(testAllTypes); assertThat(protoMessageValue.isZeroValue()).isFalse(); @@ -90,7 +94,7 @@ public void findField_fieldIsSet_fieldExists() { .build(); ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.find("single_bool")).isPresent(); assertThat(protoMessageValue.find("single_int64")).isPresent(); @@ -103,7 +107,8 @@ public void findField_fieldIsUnset_fieldDoesNotExist() { ProtoMessageValue.create( TestAllTypes.getDefaultInstance(), DefaultDescriptorPool.INSTANCE, - PROTO_CEL_VALUE_CONVERTER); + PROTO_CEL_VALUE_CONVERTER, + /* enableJsonFieldNames= */ false); assertThat(protoMessageValue.find("single_int32")).isEmpty(); assertThat(protoMessageValue.find("single_uint64")).isEmpty(); @@ -116,7 +121,8 @@ public void findField_fieldIsUndeclared_throwsException() { ProtoMessageValue.create( TestAllTypes.getDefaultInstance(), DefaultDescriptorPool.INSTANCE, - PROTO_CEL_VALUE_CONVERTER); + PROTO_CEL_VALUE_CONVERTER, + /* enableJsonFieldNames= */ false); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> protoMessageValue.select("bogus")); @@ -137,7 +143,7 @@ public void findField_extensionField_success() { TestAllTypes.newBuilder().setExtension(TestAllTypesExtensions.int32Ext, 1).build(); ProtoMessageValue protoMessageValue = - ProtoMessageValue.create(proto2Message, descriptorPool, PROTO_CEL_VALUE_CONVERTER); + ProtoMessageValue.create(proto2Message, descriptorPool, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.find("cel.expr.conformance.proto2.int32_ext")).isPresent(); } @@ -149,7 +155,7 @@ public void findField_extensionField_throwsWhenDescriptorMissing() { ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - proto2Message, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + proto2Message, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); IllegalArgumentException exception = assertThrows( @@ -193,7 +199,8 @@ private enum SelectFieldTestCase { ProtoMessageValue.create( NestedMessage.getDefaultInstance(), DefaultDescriptorPool.INSTANCE, - PROTO_CEL_VALUE_CONVERTER)), + PROTO_CEL_VALUE_CONVERTER, + /* enableJsonFieldNames= */ false)), NESTED_ENUM("standalone_enum", 1L); private final String fieldName; @@ -239,7 +246,7 @@ public void selectField_success(@TestParameter SelectFieldTestCase testCase) { ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.select(testCase.fieldName)).isEqualTo(testCase.value); } @@ -253,7 +260,8 @@ public void selectField_dynamicMessage_success() { ProtoMessageValue.create( DynamicMessage.newBuilder(testAllTypes).build(), DefaultDescriptorPool.INSTANCE, - PROTO_CEL_VALUE_CONVERTER); + PROTO_CEL_VALUE_CONVERTER, + /* enableJsonFieldNames= */ false); assertThat(protoMessageValue.select("single_int32_wrapper")).isEqualTo(5); } @@ -269,7 +277,7 @@ public void selectField_timestampNanosOutOfRange_success(int nanos) { ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.select("single_timestamp")) .isEqualTo(Instant.ofEpochSecond(0, nanos)); @@ -289,7 +297,7 @@ public void selectField_durationOutOfRange_success(int seconds, int nanos) { ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.select("single_duration")) .isEqualTo(Duration.ofSeconds(seconds, nanos)); @@ -334,7 +342,7 @@ public void selectField_jsonValue(@TestParameter SelectFieldJsonValueTestCase te ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.select("single_value")).isEqualTo(testCase.value); } @@ -351,7 +359,7 @@ public void selectField_jsonStruct() { ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.select("single_struct")).isEqualTo(ImmutableMap.of("a", false)); } @@ -368,7 +376,7 @@ public void selectField_jsonList() { ProtoMessageValue protoMessageValue = ProtoMessageValue.create( - testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER); + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, false); assertThat(protoMessageValue.select("list_value")).isEqualTo(ImmutableList.of(false)); } @@ -379,7 +387,8 @@ public void selectField_wrapperFieldUnset_returnsNull() { ProtoMessageValue.create( TestAllTypes.getDefaultInstance(), DefaultDescriptorPool.INSTANCE, - PROTO_CEL_VALUE_CONVERTER); + PROTO_CEL_VALUE_CONVERTER, + /* enableJsonFieldNames= */ false); assertThat(protoMessageValue.select("single_int64_wrapper")).isEqualTo(NullValue.NULL_VALUE); } @@ -390,9 +399,21 @@ public void celTypeTest() { ProtoMessageValue.create( TestAllTypes.getDefaultInstance(), DefaultDescriptorPool.INSTANCE, - PROTO_CEL_VALUE_CONVERTER); + PROTO_CEL_VALUE_CONVERTER, + /* enableJsonFieldNames= */ false); assertThat(protoMessageValue.celType()) .isEqualTo(StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); } + + @Test + public void findField_jsonName_success() { + TestAllTypes testAllTypes = TestAllTypes.newBuilder().setSingleInt32(42).build(); + + ProtoMessageValue protoMessageValue = + ProtoMessageValue.create( + testAllTypes, DefaultDescriptorPool.INSTANCE, PROTO_CEL_VALUE_CONVERTER, true); + + assertThat(protoMessageValue.find("singleInt32")).isPresent(); + } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java index 9fcd05447..8b419cab2 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PlannedProgram.java @@ -110,12 +110,18 @@ private CelEvaluationException newCelEvaluationException(long exprId, Exception // Use the localized expr ID (most specific error location) LocalizedEvaluationException localized = (LocalizedEvaluationException) e; exprId = localized.exprId(); - builder = - CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) localized.getCause()); + Throwable cause = localized.getCause(); + if (cause instanceof CelRuntimeException) { + builder = + CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) localized.getCause()); + } else { + builder = CelEvaluationExceptionBuilder.newBuilder(cause.getMessage()).setCause(cause); + } } else if (e instanceof CelRuntimeException) { builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e); } else { - // Unhandled function dispatch failures wraps the original exception with a descriptive message + // Unhandled function dispatch failures wraps the original exception with a descriptive + // message // (e.g: "Function foo failed with...") // We need to unwrap the cause here to preserve the original exception message and its cause. Throwable cause = e.getCause() != null ? e.getCause() : e; diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 13a0f51a1..498d9f797 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -45,10 +45,4 @@ public void unknownResultSet() { // TODO: Unknown support not implemented yet skipBaselineVerification(); } - - @Override - public void jsonFieldNames() throws Exception { - // TODO: Support JSON field names for planner - skipBaselineVerification(); - } } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index e8f5c6662..657fb5ab3 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -98,7 +98,7 @@ public final class ProgramPlannerTest { private static final CelValueProvider VALUE_PROVIDER = ProtoMessageValueProvider.newInstance(CEL_OPTIONS, DYNAMIC_PROTO); private static final CelValueConverter CEL_VALUE_CONVERTER = - ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, DYNAMIC_PROTO); + ProtoCelValueConverter.newInstance(DESCRIPTOR_POOL, DYNAMIC_PROTO, CelOptions.DEFAULT); private static final CelContainer CEL_CONTAINER = CelContainer.newBuilder() .setName("cel.expr.conformance.proto3") From bff6518fea378fa13ed21cf07e360cdbc26366c5 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 19 Feb 2026 17:06:13 -0800 Subject: [PATCH 084/100] Fix error message for missing attribute PiperOrigin-RevId: 872631759 --- .../CelAttributeNotFoundException.java | 5 ++++ .../cel/runtime/planner/MissingAttribute.java | 30 +++++++++++++++---- .../planner/PresenceTestQualifier.java | 4 +-- .../runtime/planner/ProgramPlannerTest.java | 18 +++++++---- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java index 6204fd2fe..d805c9cf4 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelAttributeNotFoundException.java @@ -39,6 +39,11 @@ public static CelAttributeNotFoundException forFieldResolution(Collection attributes) { + return new CelAttributeNotFoundException( + "No such attribute(s): " + String.join(", ", attributes)); + } + private static String formatErrorMessage(Collection fields) { String maybePlural = ""; if (fields.size() > 1) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java index 596d1bae4..02b04781c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/MissingAttribute.java @@ -22,10 +22,18 @@ final class MissingAttribute implements Attribute { private final ImmutableSet missingAttributes; + private final Kind kind; @Override public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { - throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); + switch (kind) { + case ATTRIBUTE_NOT_FOUND: + throw CelAttributeNotFoundException.forMissingAttributes(missingAttributes); + case FIELD_NOT_FOUND: + throw CelAttributeNotFoundException.forFieldResolution(missingAttributes); + } + + throw new IllegalArgumentException("Unexpected kind: " + kind); } @Override @@ -33,15 +41,25 @@ public Attribute addQualifier(Qualifier qualifier) { throw new UnsupportedOperationException("Unsupported operation"); } - static MissingAttribute newMissingAttribute(String... attributeNames) { - return newMissingAttribute(ImmutableSet.copyOf(attributeNames)); + static MissingAttribute newMissingAttribute(ImmutableSet attributeNames) { + return new MissingAttribute(attributeNames, Kind.ATTRIBUTE_NOT_FOUND); } - static MissingAttribute newMissingAttribute(ImmutableSet attributeNames) { - return new MissingAttribute(attributeNames); + static MissingAttribute newMissingField(String... attributeNames) { + return newMissingField(ImmutableSet.copyOf(attributeNames)); } - private MissingAttribute(ImmutableSet missingAttributes) { + static MissingAttribute newMissingField(ImmutableSet attributeNames) { + return new MissingAttribute(attributeNames, Kind.FIELD_NOT_FOUND); + } + + private MissingAttribute(ImmutableSet missingAttributes, Kind kind) { this.missingAttributes = missingAttributes; + this.kind = kind; + } + + private enum Kind { + ATTRIBUTE_NOT_FOUND, + FIELD_NOT_FOUND } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java index 973182b9b..5c2cba1ed 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/PresenceTestQualifier.java @@ -14,7 +14,7 @@ package dev.cel.runtime.planner; -import static dev.cel.runtime.planner.MissingAttribute.newMissingAttribute; +import static dev.cel.runtime.planner.MissingAttribute.newMissingField; import dev.cel.common.values.SelectableValue; import java.util.Map; @@ -40,7 +40,7 @@ public Object qualify(Object obj) { return map.containsKey(value); } - return newMissingAttribute(value.toString()); + return newMissingField(value.toString()); } static PresenceTestQualifier create(Object value) { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 657fb5ab3..d1141777f 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -300,6 +300,15 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } + @Test + public void plan_ident_missingAttribute_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("int_var"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e).hasMessageThat().contains("evaluation error at :0: No such attribute(s)"); + } + @Test public void plan_ident_withContainer() throws Exception { CelAbstractSyntaxTree ast = compile("abbr.ident"); @@ -713,14 +722,13 @@ public void plan_select_onMapVariable() throws Exception { public void plan_select_mapVarInputMissing_throws() throws Exception { CelAbstractSyntaxTree ast = compile("map_var.foo"); Program program = PLANNER.plan(ast); - String errorMessage = "evaluation error at :7: Error resolving "; + String errorMessage = "evaluation error at :7: No such attribute(s): "; if (isParseOnly) { errorMessage += - "fields 'cel.expr.conformance.proto3.map_var, cel.expr.conformance.map_var," - + " cel.expr.map_var, cel.map_var, map_var'"; - } else { - errorMessage += "field 'map_var'"; + "cel.expr.conformance.proto3.map_var, cel.expr.conformance.map_var, cel.expr.map_var," + + " cel.map_var, "; } + errorMessage += "map_var"; CelEvaluationException e = assertThrows(CelEvaluationException.class, () -> program.eval(ImmutableMap.of())); From fb1ca329dadc47aab3688ca57bf01ae04a97c8bd Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 20 Feb 2026 10:46:41 -0800 Subject: [PATCH 085/100] Fix parsed-only evaluation for enum identifiers with containers PiperOrigin-RevId: 872979951 --- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../runtime/planner/NamespacedAttribute.java | 78 ++++++++++++------- .../runtime/planner/ProgramPlannerTest.java | 28 ++++++- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 9ede866ab..0a7ebbfb3 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -128,6 +128,7 @@ java_library( "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", ], ) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index 12b56049a..5d9bf75fa 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -25,6 +25,7 @@ import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.GlobalResolver; import java.util.NoSuchElementException; +import org.jspecify.annotations.Nullable; @Immutable final class NamespacedAttribute implements Attribute { @@ -34,6 +35,14 @@ final class NamespacedAttribute implements Attribute { private final CelValueConverter celValueConverter; private final CelTypeProvider typeProvider; + ImmutableList qualifiers() { + return qualifiers; + } + + ImmutableSet candidateVariableNames() { + return namespacedNames; + } + @Override public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { GlobalResolver inputVars = ctx; @@ -59,41 +68,56 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { } } - CelType type = typeProvider.findType(name).orElse(null); - if (type != null) { - if (qualifiers.isEmpty()) { - // Resolution of a fully qualified type name: foo.bar.baz - return TypeType.create(type); - } else { - // This is potentially a fully qualified reference to an enum value - if (type instanceof EnumType && qualifiers.size() == 1) { - EnumType enumType = (EnumType) type; - String strQualifier = (String) qualifiers.get(0).value(); - return enumType - .findNumberByName(strQualifier) - .orElseThrow( - () -> - new NoSuchElementException( - String.format( - "Field %s was not found on enum %s", - enumType.name(), strQualifier))); - } - } - - throw new IllegalStateException( - "Unexpected type resolution when there were remaining qualifiers: " + type.name()); + // Attempt to resolve the qualify type name if the name is not a variable identifier + value = findIdent(name); + if (value != null) { + return value; } } return MissingAttribute.newMissingAttribute(namespacedNames); } - ImmutableList qualifiers() { - return qualifiers; + private @Nullable Object findIdent(String name) { + CelType type = typeProvider.findType(name).orElse(null); + // If the name resolves directly, this is a fully qualified type name + // (ex: 'int' or 'google.protobuf.Timestamp') + if (type != null) { + if (qualifiers.isEmpty()) { + // Resolution of a fully qualified type name: foo.bar.baz + return TypeType.create(type); + } + + throw new IllegalStateException( + "Unexpected type resolution when there were remaining qualifiers: " + type.name()); + } + + // The name itself could be a fully qualified reference to an enum value + // (e.g: my.enum_type.BAR) + int lastDotIndex = name.lastIndexOf('.'); + if (lastDotIndex > 0) { + String enumTypeName = name.substring(0, lastDotIndex); + String enumValueQualifier = name.substring(lastDotIndex + 1); + + return typeProvider + .findType(enumTypeName) + .filter(EnumType.class::isInstance) + .map(EnumType.class::cast) + .map(enumType -> getEnumValue(enumType, enumValueQualifier)) + .orElse(null); + } + + return null; } - ImmutableSet candidateVariableNames() { - return namespacedNames; + private static Long getEnumValue(EnumType enumType, String field) { + return enumType + .findNumberByName(field) + .map(Integer::longValue) + .orElseThrow( + () -> + new NoSuchElementException( + String.format("Field %s was not found on enum %s", enumType.name(), field))); } private GlobalResolver unwrapToNonLocal(GlobalResolver resolver) { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index d1141777f..27c56a5cd 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -277,7 +277,33 @@ public void plan_ident_enum() throws Exception { Object result = program.eval(); - assertThat(result).isEqualTo(1); + assertThat(result).isEqualTo(1L); + } + + @Test + public void plan_ident_enumContainer() throws Exception { + CelContainer container = CelContainer.ofName(GlobalEnum.getDescriptor().getFullName()); + CelCompiler compiler = + CelCompilerFactory.standardCelCompilerBuilder() + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer(container) + .build(); + CelAbstractSyntaxTree ast = compile(compiler, GlobalEnum.GAR.name()); + ProgramPlanner planner = + ProgramPlanner.newPlanner( + TYPE_PROVIDER, + VALUE_PROVIDER, + newDispatcher(), + CEL_VALUE_CONVERTER, + container, + CEL_OPTIONS, + ImmutableSet.of()); + + Program program = planner.plan(ast); + + Object result = program.eval(); + + assertThat(result).isEqualTo(1L); } @Test From ac6aa13e5129847d02c0a90a4b9d631300caa78b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 20 Feb 2026 14:31:07 -0800 Subject: [PATCH 086/100] Fix parsed-only evaluation for type equality PiperOrigin-RevId: 873076223 --- .../dev/cel/runtime/planner/NamespacedAttribute.java | 7 +++++++ .../dev/cel/runtime/planner/ProgramPlannerTest.java | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index 5d9bf75fa..6bdf0c072 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -20,6 +20,7 @@ import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypeProvider; import dev.cel.common.types.EnumType; +import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; @@ -85,6 +86,12 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { if (type != null) { if (qualifiers.isEmpty()) { // Resolution of a fully qualified type name: foo.bar.baz + if (type instanceof TypeType) { + // Coalesce all type(foo) "type" into a sentinel runtime type to allow for + // erasure based type comparisons + return TypeType.create(SimpleType.DYN); + } + return TypeType.create(type); } diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 27c56a5cd..33500f217 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -326,6 +326,18 @@ public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) t assertThat(result).isEqualTo(testCase.type); } + @Test + public void planIdent_typeLiteral_equality(@TestParameter TypeLiteralTestCase testCase) + throws Exception { + // ex: type(bool) == type, type(TestAllTypes) == type + CelAbstractSyntaxTree ast = compile(String.format("type(%s) == type", testCase.expression)); + Program program = PLANNER.plan(ast); + + boolean result = (boolean) program.eval(); + + assertThat(result).isTrue(); + } + @Test public void plan_ident_missingAttribute_throws() throws Exception { CelAbstractSyntaxTree ast = compile("int_var"); From a729a203b05f4144aeba93ce1d6591c367054739 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 20 Feb 2026 14:45:48 -0800 Subject: [PATCH 087/100] Evaluate parsed only expressions in PlannerInterpreterTest PiperOrigin-RevId: 873082579 --- .../java/dev/cel/runtime/CelRuntimeImpl.java | 6 + .../src/test/java/dev/cel/runtime/BUILD.bazel | 4 + .../cel/runtime/PlannerInterpreterTest.java | 58 ++++++++ .../src/main/java/dev/cel/testing/BUILD.bazel | 1 + .../dev/cel/testing/BaseInterpreterTest.java | 134 +++++++++++------- 5 files changed, 148 insertions(+), 55 deletions(-) diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index f3894b0e1..44377db09 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -182,6 +182,7 @@ public Object advanceEvaluation(UnknownContext context) throws CelEvaluationExce static Builder newBuilder() { return new AutoValue_CelRuntimeImpl.Builder() + .setFunctionBindings(ImmutableMap.of()) .setStandardFunctions(CelStandardFunctions.newBuilder().build()) .setContainer(CelContainer.newBuilder().build()) .setExtensionRegistry(ExtensionRegistry.getEmptyRegistry()); @@ -222,6 +223,8 @@ abstract static class Builder implements CelRuntimeBuilder { abstract ExtensionRegistry extensionRegistry(); + abstract ImmutableMap functionBindings(); + abstract ImmutableSet.Builder fileDescriptorsBuilder(); abstract ImmutableSet.Builder runtimeLibrariesBuilder(); @@ -442,6 +445,9 @@ public CelRuntime build() { DescriptorTypeResolver descriptorTypeResolver = DescriptorTypeResolver.create(combinedTypeProvider); TypeFunction typeFunction = TypeFunction.create(descriptorTypeResolver); + + mutableFunctionBindings.putAll(functionBindings()); + for (CelFunctionBinding binding : typeFunction.newFunctionBindings(options(), runtimeEquality)) { mutableFunctionBindings.put(binding.getOverloadId(), binding); diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 097282f6b..569d7372d 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -131,7 +131,11 @@ java_library( "PlannerInterpreterTest.java", ], deps = [ + "//common:cel_ast", + "//common:compiler_common", + "//common:container", "//common:options", + "//common/types:type_providers", "//extensions", "//runtime", "//runtime:runtime_planner_impl", diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 498d9f797..337061afa 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -14,8 +14,13 @@ package dev.cel.runtime; +import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelContainer; import dev.cel.common.CelOptions; +import dev.cel.common.CelValidationException; +import dev.cel.common.types.CelTypeProvider; import dev.cel.extensions.CelExtensions; import dev.cel.testing.BaseInterpreterTest; import org.junit.runner.RunWith; @@ -24,6 +29,8 @@ @RunWith(TestParameterInjector.class) public class PlannerInterpreterTest extends BaseInterpreterTest { + @TestParameter boolean isParseOnly; + @Override protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) { return CelRuntimeImpl.newBuilder() @@ -34,6 +41,36 @@ protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) { .addFileTypes(TEST_FILE_DESCRIPTORS); } + @Override + protected void setContainer(CelContainer container) { + super.setContainer(container); + this.celRuntime = this.celRuntime.toRuntimeBuilder().setContainer(container).build(); + } + + @Override + protected CelAbstractSyntaxTree prepareTest(CelTypeProvider typeProvider) { + super.prepareCompiler(typeProvider); + + CelAbstractSyntaxTree ast; + try { + ast = celCompiler.parse(source, testSourceDescription()).getAst(); + } catch (CelValidationException e) { + printTestValidationError(e); + return null; + } + + if (isParseOnly) { + return ast; + } + + try { + return celCompiler.check(ast).getAst(); + } catch (CelValidationException e) { + printTestValidationError(e); + return null; + } + } + @Override public void unknownField() { // TODO: Unknown support not implemented yet @@ -45,4 +82,25 @@ public void unknownResultSet() { // TODO: Unknown support not implemented yet skipBaselineVerification(); } + + @Override + public void optional() { + if (isParseOnly) { + // TODO: Fix for parsed-only mode. + skipBaselineVerification(); + } else { + super.optional(); + } + } + + @Override + public void optional_errors() { + if (isParseOnly) { + // Parsed-only evaluation contains function name in the + // error message instead of the function overload. + skipBaselineVerification(); + } else { + super.optional_errors(); + } + } } diff --git a/testing/src/main/java/dev/cel/testing/BUILD.bazel b/testing/src/main/java/dev/cel/testing/BUILD.bazel index 5ecfd37f2..f2480a034 100644 --- a/testing/src/main/java/dev/cel/testing/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/BUILD.bazel @@ -93,6 +93,7 @@ java_library( "//runtime:late_function_binding", "//runtime:unknown_attributes", "@cel_spec//proto/cel/expr:checked_java_proto", + "@cel_spec//proto/cel/expr:syntax_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index f1ba69c6f..bc67e8218 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -20,6 +20,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import dev.cel.expr.CheckedExpr; +import dev.cel.expr.ParsedExpr; import dev.cel.expr.Type; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; @@ -87,6 +88,7 @@ import java.time.Duration; import java.time.Instant; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -115,8 +117,8 @@ public abstract class BaseInterpreterTest extends CelBaselineTestCase { .enableOptionalSyntax(true) .comprehensionMaxIterations(1_000) .build(); - private CelRuntime celRuntime; - protected CelOptions celOptions; + protected CelRuntime celRuntime; + private CelOptions celOptions; protected BaseInterpreterTest() { this.celOptions = BASE_CEL_OPTIONS; @@ -155,6 +157,10 @@ protected void prepareCompiler(CelTypeProvider typeProvider) { .build(); } + protected void setContainer(CelContainer container) { + this.container = container; + } + private CelAbstractSyntaxTree compileTestCase() { CelAbstractSyntaxTree ast = prepareTest(TEST_FILE_DESCRIPTORS); if (ast == null) { @@ -352,7 +358,7 @@ public void quantifiers() { @Test public void arithmTimestamp() { - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); declareVariable("ts1", SimpleType.TIMESTAMP); declareVariable("ts2", SimpleType.TIMESTAMP); declareVariable("d1", SimpleType.DURATION); @@ -379,7 +385,7 @@ public void arithmTimestamp() { @Test public void arithmDuration() { - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); declareVariable("d1", SimpleType.DURATION); declareVariable("d2", SimpleType.DURATION); declareVariable("d3", SimpleType.DURATION); @@ -536,14 +542,14 @@ public void messages() throws Exception { runTest(ImmutableMap.of("single_nested_message", nestedMessage.getSingleNestedMessage())); source = "TestAllTypes{single_int64: 1, single_sfixed64: 2, single_int32: 2}.single_int32 == 2"; - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); runTest(); } @Test public void messages_error() { source = "TestAllTypes{single_int32_wrapper: 12345678900}"; - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); runTest(); source = "TestAllTypes{}.map_string_string.a"; @@ -569,12 +575,12 @@ public void optional_errors() { @Test public void containers() { - container = + setContainer( CelContainer.newBuilder() .setName("dev.cel.testing.testdata.proto3.StandaloneGlobalEnum") .addAlias("test_alias", TestAllTypes.getDescriptor().getFile().getPackage()) .addAbbreviations("cel.expr.conformance.proto2", "cel.expr.conformance.proto3") - .build(); + .build()); source = "test_alias.TestAllTypes{} == cel.expr.conformance.proto3.TestAllTypes{}"; runTest(); @@ -621,7 +627,7 @@ public void duration() throws Exception { java.time.Duration d1010 = java.time.Duration.ofSeconds(10, 10); java.time.Duration d1009 = java.time.Duration.ofSeconds(10, 9); java.time.Duration d0910 = java.time.Duration.ofSeconds(9, 10); - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); source = "d1 < d2"; runTest(extend(ImmutableMap.of("d1", d1010), ImmutableMap.of("d2", d1009))); @@ -659,7 +665,7 @@ public void timestamp() throws Exception { Instant ts1010 = Instant.ofEpochSecond(10, 10); Instant ts1009 = Instant.ofEpochSecond(10, 9); Instant ts0910 = Instant.ofEpochSecond(9, 10); - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); source = "t1 < t2"; runTest(extend(ImmutableMap.of("t1", ts1010), ImmutableMap.of("t2", ts1009))); @@ -697,7 +703,7 @@ public void packUnpackAny() { skipBaselineVerification(); return; } - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); declareVariable("any", SimpleType.ANY); declareVariable("d", SimpleType.DURATION); declareVariable( @@ -745,7 +751,7 @@ public void packUnpackAny() { public void nestedEnums() { TestAllTypes nestedEnum = TestAllTypes.newBuilder().setSingleNestedEnum(NestedEnum.BAR).build(); declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); source = "x.single_nested_enum == TestAllTypes.NestedEnum.BAR"; runTest(ImmutableMap.of("x", nestedEnum)); @@ -769,7 +775,7 @@ public void globalEnums() { public void lists() throws Exception { declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); declareVariable("y", SimpleType.INT); - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); source = "([1, 2, 3] + x.repeated_int32)[3] == 4"; runTest(ImmutableMap.of("x", TestAllTypes.newBuilder().addRepeatedInt32(4).build())); @@ -809,7 +815,7 @@ public void lists_error() { @Test public void maps() throws Exception { declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); source = "{1: 2, 3: 4}[3] == 4"; runTest(); @@ -869,19 +875,19 @@ public void comprehension() throws Exception { runTest(); declareVariable("com.x", SimpleType.INT); - container = CelContainer.ofName("com"); + setContainer(CelContainer.ofName("com")); source = "[0].exists(x, x == 0)"; runTest(ImmutableMap.of("com.x", 1)); clearAllDeclarations(); declareVariable("cel.example.y", SimpleType.INT); - container = CelContainer.ofName("cel.example"); + setContainer(CelContainer.ofName("cel.example")); source = "[{'z': 0}].exists(y, y.z == 0)"; runTest(ImmutableMap.of("cel.example.y", ImmutableMap.of("z", 1))); clearAllDeclarations(); declareVariable("y.z", SimpleType.INT); - container = CelContainer.ofName("y"); + setContainer(CelContainer.ofName("y")); source = "[{'z': 0}].exists(y, y.z == 0 && .y.z == 1)"; runTest(ImmutableMap.of("y.z", 1)); @@ -942,8 +948,14 @@ public void namespacedFunctions() { ImmutableList.of(SimpleType.INT, SimpleType.INT), SimpleType.INT)); addFunctionBinding( - CelFunctionBinding.from("ns_func_overload", String.class, s -> (long) s.length()), - CelFunctionBinding.from("ns_member_overload", Long.class, Long.class, Long::sum)); + CelFunctionBinding.fromOverloads( + "ns.func", + CelFunctionBinding.from("ns_func_overload", String.class, s -> (long) s.length()))); + addFunctionBinding( + CelFunctionBinding.fromOverloads( + "member", + CelFunctionBinding.from("ns_member_overload", Long.class, Long.class, Long::sum))); + source = "ns.func('hello')"; runTest(); @@ -965,7 +977,7 @@ public void namespacedFunctions() { source = "[1, 2].map(x, x * ns.func('test'))"; runTest(); - container = CelContainer.ofName("ns"); + setContainer(CelContainer.ofName("ns")); // Call with the container set as the function's namespace source = "ns.func('hello')"; runTest(); @@ -979,12 +991,12 @@ public void namespacedFunctions() { @Test public void namespacedVariables() { - container = CelContainer.ofName("ns"); + setContainer(CelContainer.ofName("ns")); declareVariable("ns.x", SimpleType.INT); source = "x"; runTest(ImmutableMap.of("ns.x", 2)); - container = CelContainer.ofName("dev.cel.testing.testdata.proto3"); + setContainer(CelContainer.ofName("dev.cel.testing.testdata.proto3")); CelType messageType = StructTypeReference.create("cel.expr.conformance.proto3.TestAllTypes"); declareVariable("dev.cel.testing.testdata.proto3.msgVar", messageType); source = "msgVar.single_int32"; @@ -1002,7 +1014,7 @@ public void durationFunctions() { Duration d1 = Duration.ofSeconds(totalSeconds, nanos); Duration d2 = Duration.ofSeconds(-totalSeconds, -nanos); - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); source = "d1.getHours()"; runTest(ImmutableMap.of("d1", d1)); @@ -1034,7 +1046,7 @@ public void durationFunctions() { @Test public void timestampFunctions() { declareVariable("ts1", SimpleType.TIMESTAMP); - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); Instant ts1 = Instant.ofEpochSecond(1, 11000000); Instant ts2 = Instant.ofEpochSecond(-1, 0); @@ -1163,7 +1175,7 @@ public void timestampFunctions() { @Test public void unknownField() { - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); // Unknown field is accessed. @@ -1195,7 +1207,7 @@ public void unknownField() { @Test public void unknownResultSet() { - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); TestAllTypes message = TestAllTypes.newBuilder() @@ -1397,7 +1409,7 @@ public void unknownResultSet() { @Test public void timeConversions() { - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); declareVariable("t1", SimpleType.TIMESTAMP); source = "timestamp(\"1972-01-01T10:00:20.021-05:00\")"; @@ -1430,7 +1442,7 @@ public void timeConversions_error() { @Test public void sizeTests() { - container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(Type.getDescriptor().getFile().getPackage())); declareVariable("str", SimpleType.STRING); declareVariable("b", SimpleType.BYTES); @@ -1801,7 +1813,7 @@ public void dyn_error() { @Test public void jsonValueTypes() { - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); // JSON bool selection. @@ -1918,7 +1930,7 @@ public void jsonConversions() { @Test public void typeComparisons() { - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); // Test numeric types. source = @@ -2078,7 +2090,7 @@ public void wrappers() throws Exception { declareVariable("string_list", ListType.create(SimpleType.STRING)); declareVariable("bytes_list", ListType.create(SimpleType.BYTES)); - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFullName()); + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFullName())); source = "TestAllTypes{repeated_int32: int32_list}.repeated_int32 == [1] && " + "TestAllTypes{repeated_int64: int64_list}.repeated_int64 == [2] && " @@ -2266,7 +2278,7 @@ public void dynamicMessage_adapted() throws Exception { @Test public void dynamicMessage_dynamicDescriptor() throws Exception { - container = CelContainer.ofName("dev.cel.testing.testdata.serialized.proto3"); + setContainer(CelContainer.ofName("dev.cel.testing.testdata.serialized.proto3")); source = "TestAllTypes {}"; assertThat(runTest()).isInstanceOf(DynamicMessage.class); @@ -2363,8 +2375,10 @@ public void dynamicMessage_dynamicDescriptor() throws Exception { StructTypeReference.create(TEST_ALL_TYPE_DYNAMIC_DESCRIPTOR.getFullName())), SimpleType.BOOL)); addFunctionBinding( - CelFunctionBinding.from("f_msg_generated", TestAllTypes.class, unused -> true), - CelFunctionBinding.from("f_msg_dynamic", DynamicMessage.class, unused -> true)); + CelFunctionBinding.fromOverloads( + "f_msg", + CelFunctionBinding.from("f_msg_generated", TestAllTypes.class, unused -> true), + CelFunctionBinding.from("f_msg_dynamic", DynamicMessage.class, unused -> true))); input = ImmutableMap.of( "dynamic_msg", dynamicMessageBuilder.build(), @@ -2410,14 +2424,38 @@ public void lateBoundFunctions() throws Exception { assertThat(recordedValues.getRecordedValues()).containsExactly("foo", "bar"); } + @Test + public void jsonFieldNames() throws Exception { + this.celOptions = celOptions.toBuilder().enableJsonFieldNames(true).build(); + this.celRuntime = newBaseRuntimeBuilder(celOptions).build(); + + TestAllTypes message = TestAllTypes.newBuilder().setSingleInt32(42).build(); + declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); + + source = "x.singleInt32 == 42"; + assertThat(runTest(ImmutableMap.of("x", message))).isEqualTo(true); + + source = "TestAllTypes{singleInt32: 42}.singleInt32 == 42"; + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); + assertThat(runTest()).isEqualTo(true); + + skipBaselineVerification(); + } + /** * Checks that the CheckedExpr produced by CelCompiler is equal to the one reproduced by the * native CelAbstractSyntaxTree */ private static void assertAstRoundTrip(CelAbstractSyntaxTree ast) { - CheckedExpr checkedExpr = CelProtoAbstractSyntaxTree.fromCelAst(ast).toCheckedExpr(); - CelProtoAbstractSyntaxTree protoAst = CelProtoAbstractSyntaxTree.fromCelAst(ast); - assertThat(checkedExpr).isEqualTo(protoAst.toCheckedExpr()); + if (ast.isChecked()) { + CheckedExpr checkedExpr = CelProtoAbstractSyntaxTree.fromCelAst(ast).toCheckedExpr(); + CelProtoAbstractSyntaxTree protoAst = CelProtoAbstractSyntaxTree.fromCelAst(ast); + assertThat(checkedExpr).isEqualTo(protoAst.toCheckedExpr()); + } else { + ParsedExpr parsedExpr = CelProtoAbstractSyntaxTree.fromCelAst(ast).toParsedExpr(); + CelProtoAbstractSyntaxTree protoAst = CelProtoAbstractSyntaxTree.fromCelAst(ast); + assertThat(parsedExpr).isEqualTo(protoAst.toParsedExpr()); + } } private static String readResourceContent(String path) throws IOException { @@ -2505,6 +2543,10 @@ private static CelVariableResolver extend(CelVariableResolver primary, Map functionBindings) { celRuntime = celRuntime.toRuntimeBuilder().addFunctionBindings(functionBindings).build(); } @@ -2528,22 +2570,4 @@ private static Descriptor getDeserializedTestAllTypeDescriptor() { throw new RuntimeException("Error loading TestAllTypes descriptor", e); } } - - @Test - public void jsonFieldNames() throws Exception { - this.celOptions = celOptions.toBuilder().enableJsonFieldNames(true).build(); - this.celRuntime = newBaseRuntimeBuilder(celOptions).build(); - - TestAllTypes message = TestAllTypes.newBuilder().setSingleInt32(42).build(); - declareVariable("x", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); - - source = "x.singleInt32 == 42"; - assertThat(runTest(ImmutableMap.of("x", message))).isEqualTo(true); - - source = "TestAllTypes{singleInt32: 42}.singleInt32 == 42"; - container = CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage()); - assertThat(runTest()).isEqualTo(true); - - skipBaselineVerification(); - } } From 4cf2b794d62ca00de389f67430d890899ea64179 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 20 Feb 2026 16:32:40 -0800 Subject: [PATCH 088/100] Fixed parsed-only evaluation for non-special cased optional functions PiperOrigin-RevId: 873122213 --- .../cel/extensions/CelOptionalLibrary.java | 94 ++++++++++++------- .../cel/runtime/PlannerInterpreterTest.java | 10 -- 2 files changed, 62 insertions(+), 42 deletions(-) diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index bcce0453b..baa8acb59 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -17,6 +17,15 @@ 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.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 com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -97,26 +106,26 @@ public String getFunction() { 0, ImmutableSet.of( CelFunctionDecl.newFunctionDeclaration( - Function.OPTIONAL_OF.getFunction(), + OPTIONAL_OF.getFunction(), CelOverloadDecl.newGlobalOverload( "optional_of", optionalTypeV, paramTypeV)), CelFunctionDecl.newFunctionDeclaration( - Function.OPTIONAL_OF_NON_ZERO_VALUE.getFunction(), + OPTIONAL_OF_NON_ZERO_VALUE.getFunction(), CelOverloadDecl.newGlobalOverload( "optional_ofNonZeroValue", optionalTypeV, paramTypeV)), CelFunctionDecl.newFunctionDeclaration( - Function.OPTIONAL_NONE.getFunction(), + OPTIONAL_NONE.getFunction(), CelOverloadDecl.newGlobalOverload("optional_none", optionalTypeV)), CelFunctionDecl.newFunctionDeclaration( - Function.VALUE.getFunction(), + VALUE.getFunction(), CelOverloadDecl.newMemberOverload( "optional_value", paramTypeV, optionalTypeV)), CelFunctionDecl.newFunctionDeclaration( - Function.HAS_VALUE.getFunction(), + HAS_VALUE.getFunction(), CelOverloadDecl.newMemberOverload( "optional_hasValue", SimpleType.BOOL, optionalTypeV)), CelFunctionDecl.newFunctionDeclaration( - Function.OPTIONAL_UNWRAP.getFunction(), + OPTIONAL_UNWRAP.getFunction(), CelOverloadDecl.newGlobalOverload( "optional_unwrap_list", listTypeV, ListType.create(optionalTypeV))), // Note: Implementation of "or" and "orValue" are special-cased inside the @@ -193,7 +202,7 @@ public String getFunction() { .addAll(version1.functions) .add( CelFunctionDecl.newFunctionDeclaration( - Function.FIRST.functionName, + FIRST.functionName, CelOverloadDecl.newMemberOverload( "optional_list_first", "Return the first value in a list if present, otherwise" @@ -201,7 +210,7 @@ public String getFunction() { optionalTypeV, listTypeV)), CelFunctionDecl.newFunctionDeclaration( - Function.LAST.functionName, + LAST.functionName, CelOverloadDecl.newMemberOverload( "optional_list_last", "Return the last value in a list if present, otherwise" @@ -295,22 +304,44 @@ public void setRuntimeOptions(CelRuntimeBuilder runtimeBuilder) { public void setRuntimeOptions( CelRuntimeBuilder runtimeBuilder, RuntimeEquality runtimeEquality, CelOptions celOptions) { runtimeBuilder.addFunctionBindings( - CelFunctionBinding.from("optional_of", Object.class, Optional::of), - CelFunctionBinding.from( - "optional_ofNonZeroValue", - Object.class, - val -> { - if (isZeroValue(val)) { - return Optional.empty(); - } - return Optional.of(val); - }), - CelFunctionBinding.from( - "optional_unwrap_list", Collection.class, CelOptionalLibrary::elideOptionalCollection), - CelFunctionBinding.from("optional_none", ImmutableList.of(), val -> Optional.empty()), - CelFunctionBinding.from("optional_value", Object.class, val -> ((Optional) val).get()), - CelFunctionBinding.from( - "optional_hasValue", Object.class, val -> ((Optional) val).isPresent()), + 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( CelFunctionBinding.from( "select_optional_field", // This only handles map selection. Proto selection is // special cased inside the interpreter. @@ -425,19 +456,18 @@ private static Optional expandOptMap( return Optional.of( exprFactory.newGlobalCall( Operator.CONDITIONAL.getFunction(), - exprFactory.newReceiverCall(Function.HAS_VALUE.getFunction(), target), + exprFactory.newReceiverCall(HAS_VALUE.getFunction(), target), exprFactory.newGlobalCall( - Function.OPTIONAL_OF.getFunction(), + OPTIONAL_OF.getFunction(), exprFactory.fold( UNUSED_ITER_VAR, exprFactory.newList(), varName, - exprFactory.newReceiverCall( - Function.VALUE.getFunction(), exprFactory.copy(target)), + exprFactory.newReceiverCall(VALUE.getFunction(), exprFactory.copy(target)), exprFactory.newBoolLiteral(true), exprFactory.newIdentifier(varName), mapExpr)), - exprFactory.newGlobalCall(Function.OPTIONAL_NONE.getFunction()))); + exprFactory.newGlobalCall(OPTIONAL_NONE.getFunction()))); } private static Optional expandOptFlatMap( @@ -460,16 +490,16 @@ private static Optional expandOptFlatMap( return Optional.of( exprFactory.newGlobalCall( Operator.CONDITIONAL.getFunction(), - exprFactory.newReceiverCall(Function.HAS_VALUE.getFunction(), target), + exprFactory.newReceiverCall(HAS_VALUE.getFunction(), target), exprFactory.fold( UNUSED_ITER_VAR, exprFactory.newList(), varName, - exprFactory.newReceiverCall(Function.VALUE.getFunction(), exprFactory.copy(target)), + exprFactory.newReceiverCall(VALUE.getFunction(), exprFactory.copy(target)), exprFactory.newBoolLiteral(true), exprFactory.newIdentifier(varName), mapExpr), - exprFactory.newGlobalCall(Function.OPTIONAL_NONE.getFunction()))); + exprFactory.newGlobalCall(OPTIONAL_NONE.getFunction()))); } private static Object indexOptionalMap( diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 337061afa..7518951c7 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -83,16 +83,6 @@ public void unknownResultSet() { skipBaselineVerification(); } - @Override - public void optional() { - if (isParseOnly) { - // TODO: Fix for parsed-only mode. - skipBaselineVerification(); - } else { - super.optional(); - } - } - @Override public void optional_errors() { if (isParseOnly) { From 6afaf8b3bffd082a82efea458ea554730ab48c17 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 24 Feb 2026 11:27:34 -0800 Subject: [PATCH 089/100] Fix Github Action workflow Supercedes https://github.com/google/cel-java/pull/957 PiperOrigin-RevId: 874711556 --- .github/workflows/workflow.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 060b83bdd..4b206e3ec 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -21,7 +21,7 @@ jobs: steps: - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." - run: echo "🐧 Job is running on a ${{ runner.os }} server!" - - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - run: echo "🔎 The name of your branch is ${GITHUB_REF} and your repository is ${{ github.repository }}." - name: Check out repository code uses: actions/checkout@v6 - name: Setup Bazel @@ -121,4 +121,6 @@ jobs: - name: Run Conformance Maven Test on Version Change if: steps.changed_file.outputs.any_changed == 'true' run: bazelisk test //conformance/src/test/java/dev/cel/conformance:conformance_maven --test_output=errors - - run: echo "🍏 This job's status is ${{ job.status }}." + - run: echo "🍏 This job's status is ${JOB_STATUS}." + env: + JOB_STATUS: ${{ job.status }} From ffc708aec35132c0f41ea7a12b31c03546217b5e Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Tue, 24 Feb 2026 12:03:25 -0800 Subject: [PATCH 090/100] Add support for importing / exporting shared feature flags to the environment YAML format. PiperOrigin-RevId: 874728744 --- bundle/BUILD.bazel | 1 - .../src/main/java/dev/cel/bundle/BUILD.bazel | 2 + .../java/dev/cel/bundle/CelEnvironment.java | 57 +++++++++++++++++-- .../cel/bundle/CelEnvironmentExporter.java | 50 +++++++++++++++- .../cel/bundle/CelEnvironmentYamlParser.java | 48 ++++++++++++++++ .../bundle/CelEnvironmentYamlSerializer.java | 17 ++++++ .../dev/cel/bundle/CelEnvironmentTest.java | 43 ++++++++++++++ .../bundle/CelEnvironmentYamlParserTest.java | 40 ++++++++++++- .../CelEnvironmentYamlSerializerTest.java | 3 + .../dev/cel/checker/CelCheckerBuilder.java | 3 + .../dev/cel/checker/CelCheckerLegacyImpl.java | 10 ++-- .../test/resources/environment/dump_env.yaml | 5 ++ .../resources/environment/extended_env.yaml | 7 +-- 13 files changed, 267 insertions(+), 19 deletions(-) diff --git a/bundle/BUILD.bazel b/bundle/BUILD.bazel index 7f21cf219..70880e532 100644 --- a/bundle/BUILD.bazel +++ b/bundle/BUILD.bazel @@ -27,6 +27,5 @@ java_library( java_library( name = "environment_exporter", - visibility = ["//:internal"], exports = ["//bundle/src/main/java/dev/cel/bundle:environment_exporter"], ) diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel index 0201a5807..4dd81fd9e 100644 --- a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel @@ -126,6 +126,7 @@ java_library( ":environment", "//:auto_value", "//bundle:cel", + "//checker:checker_builder", "//checker:standard_decl", "//common:compiler_common", "//common:options", @@ -133,6 +134,7 @@ java_library( "//common/types:cel_proto_types", "//common/types:cel_types", "//common/types:type_providers", + "//compiler:compiler_builder", "//extensions", "//extensions:extension_library", "//parser:macro", diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java index b54e3ca51..7ec2149a7 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java @@ -48,6 +48,7 @@ import dev.cel.compiler.CelCompilerLibrary; import dev.cel.extensions.CelExtensions; import dev.cel.parser.CelStandardMacro; +import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.CelRuntimeLibrary; import java.util.Arrays; @@ -108,6 +109,9 @@ public abstract class CelEnvironment { /** Standard library subset (which macros, functions to include/exclude) */ public abstract Optional standardLibrarySubset(); + /** Feature flags to enable in the environment. */ + public abstract ImmutableSet features(); + /** Builder for {@link CelEnvironment}. */ @AutoValue.Builder public abstract static class Builder { @@ -159,6 +163,13 @@ public Builder setFunctions(FunctionDecl... functions) { public abstract Builder setStandardLibrarySubset(LibrarySubset stdLibrarySubset); + @CanIgnoreReturnValue + public Builder setFeatures(FeatureFlag... featureFlags) { + return setFeatures(ImmutableSet.copyOf(featureFlags)); + } + + public abstract Builder setFeatures(ImmutableSet macros); + abstract CelEnvironment autoBuild(); @CheckReturnValue @@ -188,18 +199,21 @@ public static Builder newBuilder() { .setDescription("") .setContainer(CelContainer.ofName("")) .setVariables(ImmutableSet.of()) - .setFunctions(ImmutableSet.of()); + .setFunctions(ImmutableSet.of()) + .setFeatures(ImmutableSet.of()); } /** Extends the provided {@link CelCompiler} environment with this configuration. */ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions) throws CelEnvironmentException { + celOptions = applyFeatureFlags(celOptions); try { CelTypeProvider celTypeProvider = celCompiler.getTypeProvider(); CelCompilerBuilder compilerBuilder = celCompiler .toCompilerBuilder() .setContainer(container()) + .setOptions(celOptions) .setTypeProvider(celTypeProvider) .addVarDeclarations( variables().stream() @@ -222,19 +236,35 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions) /** Extends the provided {@link Cel} environment with this configuration. */ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException { + celOptions = applyFeatureFlags(celOptions); try { // Casting is necessary to only extend the compiler here CelCompiler celCompiler = extend((CelCompiler) cel, celOptions); - CelRuntimeBuilder celRuntimeBuilder = cel.toRuntimeBuilder(); - addAllRuntimeExtensions(celRuntimeBuilder, celOptions); + CelRuntime celRuntime = extendRuntime(cel, celOptions); - return CelFactory.combine(celCompiler, celRuntimeBuilder.build()); + return CelFactory.combine(celCompiler, celRuntime); } catch (RuntimeException e) { throw new CelEnvironmentException(e.getMessage(), e); } } + private CelOptions applyFeatureFlags(CelOptions celOptions) { + CelOptions.Builder optionsBuilder = celOptions.toBuilder(); + for (FeatureFlag featureFlag : features()) { + if (featureFlag.name().equals("cel.feature.macro_call_tracking")) { + optionsBuilder.populateMacroCalls(featureFlag.enabled()); + } else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) { + optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled()); + } else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) { + optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled()); + } else { + throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name()); + } + } + return optionsBuilder.build(); + } + private void addAllCompilerExtensions( CelCompilerBuilder celCompilerBuilder, CelOptions celOptions) { // TODO: Add capability to accept user defined exceptions @@ -250,7 +280,9 @@ private void addAllCompilerExtensions( } } - private void addAllRuntimeExtensions(CelRuntimeBuilder celRuntimeBuilder, CelOptions celOptions) { + private CelRuntime extendRuntime(CelRuntime celRuntime, CelOptions celOptions) { + CelRuntimeBuilder celRuntimeBuilder = celRuntime.toRuntimeBuilder(); + celRuntimeBuilder.setOptions(celOptions); // TODO: Add capability to accept user defined exceptions for (ExtensionConfig extensionConfig : extensions()) { CanonicalCelExtension extension = getExtensionOrThrow(extensionConfig.name()); @@ -262,6 +294,7 @@ private void addAllRuntimeExtensions(CelRuntimeBuilder celRuntimeBuilder, CelOpt celRuntimeBuilder.addLibraries(celRuntimeLibrary); } } + return celRuntimeBuilder.build(); } private void applyStandardLibrarySubset(CelCompilerBuilder compilerBuilder) { @@ -625,6 +658,20 @@ public CelType toCelType(CelTypeProvider celTypeProvider) { } } + /** Represents a feature flag that can be enabled in the environment. */ + @AutoValue + public abstract static class FeatureFlag { + /** Normalized name of the feature flag. */ + public abstract String name(); + + /** Whether the feature is enabled or disabled. */ + public abstract boolean enabled(); + + public static FeatureFlag create(String name, boolean enabled) { + return new AutoValue_CelEnvironment_FeatureFlag(name, enabled); + } + } + /** * Represents a configuration for a canonical CEL extension that can be enabled in the * environment. diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java index 01410ad0d..1ed113db7 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java @@ -22,6 +22,7 @@ import dev.cel.expr.Decl.FunctionDecl; import dev.cel.expr.Decl.FunctionDecl.Overload; import com.google.auto.value.AutoValue; +import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; @@ -30,6 +31,7 @@ import dev.cel.bundle.CelEnvironment.LibrarySubset; import dev.cel.bundle.CelEnvironment.LibrarySubset.FunctionSelector; import dev.cel.bundle.CelEnvironment.OverloadDecl; +import dev.cel.checker.CelCheckerBuilder; import dev.cel.checker.CelStandardDeclarations.StandardFunction; import dev.cel.checker.CelStandardDeclarations.StandardIdentifier; import dev.cel.common.CelFunctionDecl; @@ -41,6 +43,7 @@ import dev.cel.common.types.CelProtoTypes; import dev.cel.common.types.CelType; import dev.cel.common.types.CelTypes; +import dev.cel.compiler.CelCompiler; import dev.cel.extensions.CelExtensionLibrary; import dev.cel.extensions.CelExtensions; import dev.cel.parser.CelMacro; @@ -161,9 +164,24 @@ public static CelEnvironmentExporter.Builder newBuilder() { * */ public CelEnvironment export(Cel cel) { - CelEnvironment.Builder envBuilder = - CelEnvironment.newBuilder().setContainer(cel.toCheckerBuilder().container()); + return export((CelCompiler) cel); + } + /** + * Exports a {@link CelEnvironment} that describes the configuration of the given {@link + * CelCompiler} instance. + * + *

    The exported environment includes: + * + *

      + *
    • Standard library subset: functions and their overloads that are either included or + * excluded from the standard library. + *
    • Extension libraries: names and versions of the extension libraries that are used. + *
    • Custom declarations: functions and variables that are not part of the standard library or + * any of the extension libraries. + *
    + */ + public CelEnvironment export(CelCompiler cel) { // Inventory is a full set of declarations and macros that are found in the configuration of // the supplied CEL instance. // @@ -172,6 +190,14 @@ public CelEnvironment export(Cel cel) { // // Whatever is left will be included in the Environment as custom declarations. + // Checker builder is used to access some parts of the config not exposed in the EnvVisitable + // interface. + CelCheckerBuilder checkerBuilder = cel.toCheckerBuilder(); + + CelEnvironment.Builder envBuilder = + CelEnvironment.newBuilder().setContainer(checkerBuilder.container()); + addOptions(envBuilder, checkerBuilder.options()); + Set inventory = new HashSet<>(); collectInventory(inventory, cel); addExtensionConfigsAndRemoveFromInventory(envBuilder, inventory); @@ -180,11 +206,29 @@ public CelEnvironment export(Cel cel) { return envBuilder.build(); } + private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) { + // The set of features supported in the exported environment in Go is pretty limited right now. + ImmutableSet.Builder featureFlags = ImmutableSet.builder(); + if (options.enableHeterogeneousNumericComparisons()) { + featureFlags.add( + CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true)); + } + if (options.enableQuotedIdentifierSyntax()) { + featureFlags.add( + CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true)); + } + if (options.populateMacroCalls()) { + featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true)); + } + envBuilder.setFeatures(featureFlags.build()); + } + /** * Collects all function overloads, variable declarations and macros from the given {@link Cel} * instance and stores them in a map. */ - private void collectInventory(Set inventory, Cel cel) { + private void collectInventory(Set inventory, CelCompiler cel) { + Preconditions.checkArgument(cel instanceof EnvVisitable); ((EnvVisitable) cel) .accept( new EnvVisitor() { diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java index 8c19fcfa6..2fa8923f1 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java @@ -143,6 +143,51 @@ private CelContainer parseContainer(ParserContext ctx, Node node) { return builder.build(); } + private ImmutableSet parseFeatures( + ParserContext ctx, Node node) { + long valueId = ctx.collectMetadata(node); + if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) { + ctx.reportError(valueId, "Unsupported features format"); + } + + ImmutableSet.Builder featureFlags = ImmutableSet.builder(); + + SequenceNode featureListNode = (SequenceNode) node; + for (Node featureMapNode : featureListNode.getValue()) { + long featureMapId = ctx.collectMetadata(featureMapNode); + if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) { + continue; + } + + MappingNode featureMap = (MappingNode) featureMapNode; + String name = ""; + boolean enabled = true; + for (NodeTuple nodeTuple : featureMap.getValue()) { + Node keyNode = nodeTuple.getKeyNode(); + long keyId = ctx.collectMetadata(keyNode); + Node valueNode = nodeTuple.getValueNode(); + String keyName = ((ScalarNode) keyNode).getValue(); + switch (keyName) { + case "name": + name = newString(ctx, valueNode); + break; + case "enabled": + enabled = newBoolean(ctx, valueNode); + break; + default: + ctx.reportError(keyId, String.format("Unsupported feature tag: %s", keyName)); + break; + } + } + if (name.isEmpty()) { + ctx.reportError(featureMapId, "Missing required attribute(s): name"); + continue; + } + featureFlags.add(CelEnvironment.FeatureFlag.create(name, enabled)); + } + return featureFlags.build(); + } + private ImmutableSet parseAliases(ParserContext ctx, Node node) { ImmutableSet.Builder aliasSetBuilder = ImmutableSet.builder(); long valueId = ctx.collectMetadata(node); @@ -756,6 +801,9 @@ private CelEnvironment.Builder parseConfig(ParserContext ctx, Node node) { case "stdlib": builder.setStandardLibrarySubset(parseLibrarySubset(ctx, valueNode)); break; + case "features": + builder.setFeatures(parseFeatures(ctx, valueNode)); + break; default: ctx.reportError(id, "Unknown config tag: " + fieldName); // continue handling the rest of the nodes diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java index 81f206b94..2cc229dc9 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java @@ -60,6 +60,7 @@ private CelEnvironmentYamlSerializer() { CelEnvironment.LibrarySubset.OverloadSelector.class, new RepresentOverloadSelector()); this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias()); this.multiRepresenters.put(CelContainer.class, new RepresentContainer()); + this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag()); } public static String toYaml(CelEnvironment environment) { @@ -94,6 +95,9 @@ public Node representData(Object data) { if (environment.standardLibrarySubset().isPresent()) { configMap.put("stdlib", environment.standardLibrarySubset().get()); } + if (!environment.features().isEmpty()) { + configMap.put("features", environment.features().asList()); + } return represent(configMap.buildOrThrow()); } } @@ -258,4 +262,17 @@ public Node representData(Object data) { return represent(ImmutableMap.of("id", overloadSelector.id())); } } + + private final class RepresentFeatureFlag implements Represent { + + @Override + public Node representData(Object data) { + CelEnvironment.FeatureFlag featureFlag = (CelEnvironment.FeatureFlag) data; + return represent( + ImmutableMap.builder() + .put("name", featureFlag.name()) + .put("enabled", featureFlag.enabled()) + .buildOrThrow()); + } + } } diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java index 6bc84a48f..3386bdaae 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java @@ -100,6 +100,49 @@ public void extend_allExtensions() throws Exception { assertThat(result).isTrue(); } + @Test + public void extend_allFeatureFlags() throws Exception { + CelEnvironment environment = + CelEnvironment.newBuilder() + .setFeatures( + CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true), + CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true), + CelEnvironment.FeatureFlag.create( + "cel.feature.cross_type_numeric_comparisons", true)) + .build(); + + Cel cel = + environment.extend( + CelFactory.standardCelBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .build(), + CelOptions.DEFAULT); + CelAbstractSyntaxTree ast = + cel.compile("[{'foo.bar': 1}, {'foo.bar': 2}].all(e, e.`foo.bar` < 2.5)").getAst(); + assertThat(ast.getSource().getMacroCalls()).hasSize(1); + boolean result = (boolean) cel.createProgram(ast).eval(); + assertThat(result).isTrue(); + } + + @Test + public void extend_unsupportedFeatureFlag_throws() throws Exception { + CelEnvironment environment = + CelEnvironment.newBuilder() + .setFeatures(CelEnvironment.FeatureFlag.create("unknown.feature", true)) + .build(); + + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> + environment.extend( + CelFactory.standardCelBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .build(), + CelOptions.DEFAULT)); + assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature"); + } + @Test public void extensionVersion_specific() throws Exception { CelEnvironment environment = diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java index d69d0517b..98ce55ecc 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java @@ -40,8 +40,8 @@ import dev.cel.common.types.SimpleType; import dev.cel.parser.CelUnparserFactory; import dev.cel.runtime.CelEvaluationListener; -import dev.cel.runtime.CelLateFunctionBindings; import dev.cel.runtime.CelFunctionBinding; +import dev.cel.runtime.CelLateFunctionBindings; import java.io.IOException; import java.net.URL; import java.util.Optional; @@ -81,6 +81,33 @@ public void environment_setBasicProperties() throws Exception { .build()); } + @Test + public void environment_setFeatures() throws Exception { + String yamlConfig = + "name: hello\n" + + "description: empty\n" + + "features:\n" + + " - name: 'cel.feature.macro_call_tracking'\n" + + " enabled: true\n" + + " - name: 'cel.feature.backtick_escape_syntax'\n" + + " enabled: false"; + + CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig); + + assertThat(environment) + .isEqualTo( + CelEnvironment.newBuilder() + .setSource(environment.source().get()) + .setName("hello") + .setDescription("empty") + .setFeatures( + ImmutableSet.of( + CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true), + CelEnvironment.FeatureFlag.create( + "cel.feature.backtick_escape_syntax", false))) + .build()); + } + @Test public void environment_setExtensions() throws Exception { String yamlConfig = @@ -672,6 +699,16 @@ private enum EnvironmentParseErrorTestcase { "ERROR: :6:7: Unsupported alias tag: unknown_tag\n" + " | unknown_tag: 'test_value'\n" + " | ......^"), + ILLEGAL_FEATURE_TAG( + "features:\n" + " - name: 'test_feature'\n" + " unknown_tag: 'test_value'\n", + "ERROR: :3:5: Unsupported feature tag: unknown_tag\n" + + " | unknown_tag: 'test_value'\n" + + " | ....^"), + MISSING_FEATURE_NAME( + "features:\n" + " - enabled: true\n", + "ERROR: :2:5: Missing required attribute(s): name\n" + + " | - enabled: true\n" + + " | ....^"), ; private final String yamlConfig; @@ -793,6 +830,7 @@ private enum EnvironmentYamlResourceTestCase { .build()) .setReturnType(TypeDecl.create("bool")) .build()))) + .setFeatures(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true)) .build()), LIBRARY_SUBSET_ENV( diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java index 7e4be0912..1c56370b2 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java @@ -126,6 +126,9 @@ public void toYaml_success() throws Exception { FunctionSelector.create( "_+_", ImmutableSet.of("add_bytes", "add_list")))) .build()) + .setFeatures( + CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true), + CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", false)) .build(); String yamlOutput = CelEnvironmentYamlSerializer.toYaml(environment); diff --git a/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java b/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java index e19cf5b70..a7d531f88 100644 --- a/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java +++ b/checker/src/main/java/dev/cel/checker/CelCheckerBuilder.java @@ -35,6 +35,9 @@ public interface CelCheckerBuilder { @CanIgnoreReturnValue CelCheckerBuilder setOptions(CelOptions options); + /** Retrieves the currently configured {@link CelOptions} in the builder. */ + CelOptions options(); + /** * Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and * functions. diff --git a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java index df8a82f43..ceab0fa93 100644 --- a/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java +++ b/checker/src/main/java/dev/cel/checker/CelCheckerLegacyImpl.java @@ -202,6 +202,11 @@ public CelCheckerBuilder setOptions(CelOptions celOptions) { return this; } + @Override + public CelOptions options() { + return this.celOptions; + } + @Override public CelCheckerBuilder setContainer(CelContainer container) { checkNotNull(container); @@ -421,11 +426,6 @@ CelStandardDeclarations standardDeclarations() { return this.standardDeclarations; } - @VisibleForTesting - CelOptions options() { - return this.celOptions; - } - @VisibleForTesting CelTypeProvider celTypeProvider() { return this.celTypeProvider; diff --git a/testing/src/test/resources/environment/dump_env.yaml b/testing/src/test/resources/environment/dump_env.yaml index 6a885ea51..18f96fbcc 100644 --- a/testing/src/test/resources/environment/dump_env.yaml +++ b/testing/src/test/resources/environment/dump_env.yaml @@ -82,3 +82,8 @@ stdlib: overloads: - id: add_bytes - id: add_list +features: +- name: cel.feature.macro_call_tracking + enabled: true +- name: cel.feature.backtick_escape_syntax + enabled: false diff --git a/testing/src/test/resources/environment/extended_env.yaml b/testing/src/test/resources/environment/extended_env.yaml index fbed2b9d5..c420ad4db 100644 --- a/testing/src/test/resources/environment/extended_env.yaml +++ b/testing/src/test/resources/environment/extended_env.yaml @@ -38,6 +38,9 @@ functions: is_type_param: true return: type_name: "bool" +features: + - name: cel.feature.macro_call_tracking + enabled: true # TODO: Add support for below #validators: #- name: cel.validator.duration @@ -46,7 +49,3 @@ functions: #- name: cel.validator.nesting_comprehension_limit # config: # limit: 2 -# TODO: Add support for below -#features: -#- name: cel.feature.macro_call_tracking -# enabled: true \ No newline at end of file From e8079d0e4876c236b23de36516a8f2a6ccc2e2d2 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Tue, 24 Feb 2026 19:13:16 -0800 Subject: [PATCH 091/100] Implement optionals for planner PiperOrigin-RevId: 874895299 --- bundle/BUILD.bazel | 12 +- .../src/main/java/dev/cel/bundle/BUILD.bazel | 54 ++- .../src/main/java/dev/cel/bundle/CelImpl.java | 20 +- .../src/test/java/dev/cel/bundle/BUILD.bazel | 1 + .../cel/extensions/CelOptionalLibrary.java | 108 +++-- .../test/java/dev/cel/extensions/BUILD.bazel | 4 + .../extensions/CelOptionalLibraryTest.java | 389 +++++++++++------- runtime/BUILD.bazel | 1 - .../java/dev/cel/runtime/CelRuntimeImpl.java | 17 +- .../dev/cel/runtime/CelRuntimeLegacyImpl.java | 27 +- .../dev/cel/runtime/DefaultDispatcher.java | 93 ++++- .../dev/cel/runtime/DefaultInterpreter.java | 39 +- .../dev/cel/runtime/FunctionBindingImpl.java | 6 +- .../java/dev/cel/runtime/planner/BUILD.bazel | 46 +++ .../cel/runtime/planner/EvalCreateList.java | 32 +- .../cel/runtime/planner/EvalCreateMap.java | 42 +- .../cel/runtime/planner/EvalCreateStruct.java | 36 +- .../cel/runtime/planner/EvalOptionalOr.java | 53 +++ .../runtime/planner/EvalOptionalOrValue.java | 53 +++ .../planner/EvalOptionalSelectField.java | 89 ++++ .../cel/runtime/planner/ProgramPlanner.java | 81 +++- 21 files changed, 945 insertions(+), 258 deletions(-) create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOr.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOrValue.java create mode 100644 runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalSelectField.java diff --git a/bundle/BUILD.bazel b/bundle/BUILD.bazel index 70880e532..1eaf0bec8 100644 --- a/bundle/BUILD.bazel +++ b/bundle/BUILD.bazel @@ -7,7 +7,10 @@ package( java_library( name = "cel", - exports = ["//bundle/src/main/java/dev/cel/bundle:cel"], + exports = [ + "//bundle/src/main/java/dev/cel/bundle:cel", + "//bundle/src/main/java/dev/cel/bundle:cel_factory", + ], ) java_library( @@ -29,3 +32,10 @@ java_library( name = "environment_exporter", exports = ["//bundle/src/main/java/dev/cel/bundle:environment_exporter"], ) + +java_library( + name = "cel_impl", + testonly = 1, + visibility = ["//:internal"], + exports = ["//bundle/src/main/java/dev/cel/bundle:cel_impl"], +) diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel index 4dd81fd9e..0a014ec73 100644 --- a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel @@ -11,8 +11,6 @@ package( CEL_SOURCES = [ "Cel.java", "CelBuilder.java", - "CelFactory.java", - "CelImpl.java", ] java_library( @@ -21,31 +19,74 @@ java_library( tags = [ ], deps = [ + "//checker:checker_legacy_environment", + "//checker:proto_type_mask", + "//checker:standard_decl", + "//common:compiler_common", + "//common:container", + "//common:options", + "//common/types:type_providers", + "//common/values:cel_value_provider", + "//compiler:compiler_builder", + "//parser:macro", + "//runtime", + "//runtime:function_binding", + "//runtime:standard_functions", + "@cel_spec//proto/cel/expr:checked_java_proto", + "@maven//:com_google_code_findbugs_annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "cel_factory", + srcs = ["CelFactory.java"], + tags = [ + ], + deps = [ + ":cel", + ":cel_impl", "//checker", + "//common:options", + "//compiler", + "//compiler:compiler_builder", + "//parser", + "//runtime", + ], +) + +java_library( + name = "cel_impl", + srcs = ["CelImpl.java"], + tags = [ + ], + deps = [ + ":cel", "//checker:checker_builder", - "//checker:checker_legacy_environment", "//checker:proto_type_mask", "//checker:standard_decl", + "//checker:type_provider_legacy", "//common:cel_ast", "//common:cel_source", "//common:compiler_common", "//common:container", "//common:options", + "//common/annotations", "//common/internal:env_visitor", "//common/internal:file_descriptor_converter", "//common/types:cel_proto_types", "//common/types:type_providers", "//common/values:cel_value_provider", - "//compiler", "//compiler:compiler_builder", - "//parser", "//parser:macro", "//parser:parser_builder", "//runtime", "//runtime:function_binding", + "//runtime:runtime_planner_impl", "//runtime:standard_functions", "@cel_spec//proto/cel/expr:checked_java_proto", - "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -60,6 +101,7 @@ java_library( tags = [ ], deps = [ + ":cel_factory", ":environment_exception", ":required_fields_checker", "//:auto_value", diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java index bc92cca7a..51fe2dc38 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java +++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java @@ -38,6 +38,7 @@ import dev.cel.common.CelSource; import dev.cel.common.CelValidationResult; import dev.cel.common.CelVarDecl; +import dev.cel.common.annotations.Internal; import dev.cel.common.internal.EnvVisitable; import dev.cel.common.internal.EnvVisitor; import dev.cel.common.internal.FileDescriptorSetConverter; @@ -54,6 +55,7 @@ import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeBuilder; +import dev.cel.runtime.CelRuntimeImpl; import dev.cel.runtime.CelRuntimeLibrary; import dev.cel.runtime.CelStandardFunctions; import java.util.Arrays; @@ -63,9 +65,14 @@ * Implementation of the synchronous CEL stack. * *

    Note, the underlying {@link CelCompiler} and {@link CelRuntime} values are constructed lazily. + * + *

    CEL Library Internals. Do Not Use. Consumers should use {@code CelFactory} instead. + * + *

    TODO: Restrict visibility once factory is introduced */ @Immutable -final class CelImpl implements Cel, EnvVisitable { +@Internal +public final class CelImpl implements Cel, EnvVisitable { // The lazily constructed compiler and runtime values are memoized and guaranteed to be // constructed only once without side effects, thus making them effectively immutable. @@ -142,8 +149,13 @@ static CelImpl combine(CelCompiler compiler, CelRuntime runtime) { * Create a new builder for constructing a {@code CelImpl} instance. * *

    By default, {@link CelOptions#DEFAULT} are enabled, as is the CEL standard environment. + * + *

    CEL Library Internals. Do Not Use. Consumers should use {@code CelFactory} instead. + * + *

    TODO: Restrict visibility once factory is introduced */ - static CelBuilder newBuilder( + @Internal + public static CelBuilder newBuilder( CelCompilerBuilder compilerBuilder, CelRuntimeBuilder celRuntimeBuilder) { return new CelImpl.Builder(compilerBuilder, celRuntimeBuilder); } @@ -199,6 +211,10 @@ public CelContainer container() { @Override public CelBuilder setContainer(CelContainer container) { compilerBuilder.setContainer(container); + if (runtimeBuilder instanceof CelRuntimeImpl.Builder) { + runtimeBuilder.setContainer(container); + } + return this; } diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel index cd33dd67d..ffa3322fe 100644 --- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel @@ -17,6 +17,7 @@ java_library( deps = [ "//:java_truth", "//bundle:cel", + "//bundle:cel_impl", "//bundle:environment", "//bundle:environment_exception", "//bundle:environment_exporter", diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index baa8acb59..a3777c759 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -17,6 +17,9 @@ 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; @@ -342,54 +345,69 @@ public void setRuntimeOptions( "optional_hasValue", Object.class, val -> ((Optional) val).isPresent()))); runtimeBuilder.addFunctionBindings( - CelFunctionBinding.from( - "select_optional_field", // This only handles map selection. Proto selection is - // special cased inside the interpreter. - Map.class, - String.class, - runtimeEquality::findInMap), - 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)), - CelFunctionBinding.from( - "optional_map_index_value", - Optional.class, - Object.class, - (Optional optionalMap, Object key) -> - indexOptionalMap(optionalMap, key, runtimeEquality)), - CelFunctionBinding.from( - "optional_list_index_int", - Optional.class, - Long.class, - CelOptionalLibrary::indexOptionalList), - 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)); + 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( - CelFunctionBinding.from( - "optional_list_first", Collection.class, CelOptionalLibrary::listOptionalFirst), - CelFunctionBinding.from( - "optional_list_last", Collection.class, CelOptionalLibrary::listOptionalLast)); + fromOverloads( + "first", + CelFunctionBinding.from( + "optional_list_first", Collection.class, CelOptionalLibrary::listOptionalFirst))); + runtimeBuilder.addFunctionBindings( + fromOverloads( + "last", + CelFunctionBinding.from( + "optional_list_last", Collection.class, CelOptionalLibrary::listOptionalLast))); } } diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index d5155f662..b441c33cf 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -10,6 +10,8 @@ java_library( deps = [ "//:java_truth", "//bundle:cel", + "//bundle:cel_impl", + "//checker", "//common:cel_ast", "//common:compiler_common", "//common:container", @@ -30,6 +32,7 @@ java_library( "//extensions:sets", "//extensions:sets_function", "//extensions:strings", + "//parser", "//parser:macro", "//parser:unparser", "//runtime", @@ -37,6 +40,7 @@ java_library( "//runtime:interpreter_util", "//runtime:lite_runtime", "//runtime:lite_runtime_factory", + "//runtime:runtime_planner_impl", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/test:simple_java_proto", diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index dd94333c3..0f0c649f8 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -27,6 +27,8 @@ import dev.cel.bundle.Cel; import dev.cel.bundle.CelBuilder; import dev.cel.bundle.CelFactory; +import dev.cel.bundle.CelImpl; +import dev.cel.checker.CelCheckerLegacyImpl; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; @@ -43,13 +45,17 @@ import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; import dev.cel.common.values.NullValue; +import dev.cel.compiler.CelCompiler; +import dev.cel.compiler.CelCompilerImpl; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage; import dev.cel.parser.CelMacro; +import dev.cel.parser.CelParserImpl; import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; +import dev.cel.runtime.CelRuntimeImpl; import dev.cel.runtime.InterpreterUtil; import java.time.Duration; import java.time.Instant; @@ -63,6 +69,14 @@ @SuppressWarnings({"unchecked", "SingleTestParameter"}) public class CelOptionalLibraryTest { + private enum TestMode { + PLANNER_PARSE_ONLY, + PLANNER_CHECKED, + LEGACY_CHECKED + } + + @TestParameter TestMode testMode; + @SuppressWarnings("ImmutableEnumChecker") // Test only private enum ConstantTestCases { INT("5", "0", SimpleType.INT, 5L), @@ -92,15 +106,40 @@ private enum ConstantTestCases { } } - private static CelBuilder newCelBuilder() { + private static CelBuilder plannerCelBuilder() { + // TODO: Replace with factory once available. + return CelImpl.newBuilder( + CelCompilerImpl.newBuilder( + CelParserImpl.newBuilder(), + CelCheckerLegacyImpl.newBuilder().setStandardEnvironmentEnabled(true)), + CelRuntimeImpl.newBuilder()) + // CEL-Internal-2 + .setOptions(CelOptions.current().build()); + } + + private CelBuilder newCelBuilder() { return newCelBuilder(Integer.MAX_VALUE); } - private static CelBuilder newCelBuilder(int version) { - return CelFactory.standardCelBuilder() + private CelBuilder newCelBuilder(int version) { + CelBuilder celBuilder; + switch (testMode) { + case PLANNER_PARSE_ONLY: + case PLANNER_CHECKED: + celBuilder = plannerCelBuilder(); + break; + case LEGACY_CHECKED: + celBuilder = CelFactory.standardCelBuilder(); + break; + default: + throw new IllegalArgumentException("Unknown test mode: " + testMode); + } + + return celBuilder .setOptions( CelOptions.current() .enableTimestampEpoch(true) + .enableHeterogeneousNumericComparisons(true) .build()) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) @@ -181,7 +220,7 @@ public void optionalOf_constant_success(@TestParameter ConstantTestCases testCas throws Exception { Cel cel = newCelBuilder().setResultType(OptionalType.create(testCase.type)).build(); String expression = String.format("optional.of(%s)", testCase.sourceWithNonZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -198,7 +237,7 @@ public void optionalType_runtimeEquality(@TestParameter ConstantTestCases testCa .addVar("b", OptionalType.create(testCase.type)) .setResultType(SimpleType.BOOL) .build(); - CelAbstractSyntaxTree ast = cel.compile("a == b").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "a == b"); boolean result = (boolean) @@ -216,7 +255,7 @@ public void optionalType_runtimeEquality(@TestParameter ConstantTestCases testCa @Test public void optionalType_adaptsIntegerToLong_success() throws Exception { Cel cel = newCelBuilder().addVar("a", OptionalType.create(SimpleType.INT)).build(); - CelAbstractSyntaxTree ast = cel.compile("a").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "a"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("a", Optional.of(5))); @@ -227,7 +266,7 @@ public void optionalType_adaptsIntegerToLong_success() throws Exception { @Test public void optionalType_adaptsFloatToLong_success() throws Exception { Cel cel = newCelBuilder().addVar("a", OptionalType.create(SimpleType.DOUBLE)).build(); - CelAbstractSyntaxTree ast = cel.compile("a").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "a"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("a", Optional.of(5.5f))); @@ -239,7 +278,7 @@ public void optionalType_adaptsFloatToLong_success() throws Exception { public void optionalOf_nullValue_success() throws Exception { Cel cel = newCelBuilder().setResultType(SimpleType.DYN).build(); String expression = "optional.of(TestAllTypes{}.single_value)"; - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -252,7 +291,7 @@ public void optionalOfNonZeroValue_withZeroValue_returnsEmptyOptionalValue( @TestParameter ConstantTestCases testCase) throws Exception { Cel cel = newCelBuilder().setResultType(OptionalType.create(testCase.type)).build(); String expression = String.format("optional.ofNonZeroValue(%s)", testCase.sourceWithZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -266,7 +305,7 @@ public void optionalOfNonZeroValue_withNonZeroValue_returnsOptionalValue( Cel cel = newCelBuilder().setResultType(OptionalType.create(testCase.type)).build(); String expression = String.format("optional.ofNonZeroValue(%s)", testCase.sourceWithNonZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -278,7 +317,7 @@ public void optionalOfNonZeroValue_withNonZeroValue_returnsOptionalValue( public void optionalOfNonZeroValue_withNullValue_returnsEmptyOptionalValue() throws Exception { Cel cel = newCelBuilder().setResultType(SimpleType.DYN).build(); CelAbstractSyntaxTree ast = - cel.compile("optional.ofNonZeroValue(TestAllTypes{}.single_value)").getAst(); + compile(cel, "optional.ofNonZeroValue(TestAllTypes{}.single_value)"); Object result = cel.createProgram(ast).eval(); @@ -289,7 +328,7 @@ public void optionalOfNonZeroValue_withNullValue_returnsEmptyOptionalValue() thr @Test public void optionalOfNonZeroValue_withEmptyMessage_returnsEmptyOptionalValue() throws Exception { Cel cel = newCelBuilder().setResultType(SimpleType.DYN).build(); - CelAbstractSyntaxTree ast = cel.compile("optional.ofNonZeroValue(TestAllTypes{})").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optional.ofNonZeroValue(TestAllTypes{})"); Object result = cel.createProgram(ast).eval(); @@ -300,7 +339,7 @@ public void optionalOfNonZeroValue_withEmptyMessage_returnsEmptyOptionalValue() @Test public void optionalNone_success() throws Exception { Cel cel = newCelBuilder().setResultType(SimpleType.DYN).build(); - CelAbstractSyntaxTree ast = cel.compile("optional.none()").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optional.none()"); Object result = cel.createProgram(ast).eval(); @@ -312,7 +351,7 @@ public void optionalNone_success() throws Exception { public void optionalValue_success(@TestParameter ConstantTestCases testCase) throws Exception { Cel cel = newCelBuilder().setResultType(testCase.type).build(); String expression = String.format("optional.of(%s).value()", testCase.sourceWithNonZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -322,7 +361,7 @@ public void optionalValue_success(@TestParameter ConstantTestCases testCase) thr @Test public void optionalValue_whenOptionalValueEmpty_throws() throws Exception { Cel cel = newCelBuilder().build(); - CelAbstractSyntaxTree ast = cel.compile("optional.none().value()").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optional.none().value()"); assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); } @@ -333,7 +372,7 @@ public void optionalHasValue_whenOptionalValuePresent_returnsTrue( Cel cel = newCelBuilder().setResultType(SimpleType.BOOL).build(); String expression = String.format("optional.of(%s).hasValue()", testCase.sourceWithNonZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -346,7 +385,7 @@ public void optionalHasValue_whenOptionalValueEmpty_returnsFalse( Cel cel = newCelBuilder().setResultType(SimpleType.BOOL).build(); String expression = String.format("optional.ofNonZeroValue(%s).hasValue()", testCase.sourceWithZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -361,7 +400,7 @@ public void optionalOr_success() throws Exception { .addVar("y", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelRuntime.Program program = cel.createProgram(cel.compile("x.or(y)").getAst()); + CelRuntime.Program program = cel.createProgram(compile(cel, "x.or(y)")); Object resultLhs = program.eval(ImmutableMap.of("x", Optional.of(5), "y", Optional.empty())); Object resultRhs = program.eval(ImmutableMap.of("x", Optional.empty(), "y", Optional.of(10))); @@ -381,15 +420,18 @@ public void optionalOr_shortCircuits() throws Exception { CelOverloadDecl.newGlobalOverload( "error_overload", OptionalType.create(SimpleType.INT)))) .addFunctionBindings( - CelFunctionBinding.from( - "error_overload", - ImmutableList.of(), - val -> { - throw new IllegalStateException("This function should not have been called!"); - })) + CelFunctionBinding.fromOverloads( + "errorFunc", + CelFunctionBinding.from( + "error_overload", + ImmutableList.of(), + val -> { + throw new IllegalStateException( + "This function should not have been called!"); + }))) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelRuntime.Program program = cel.createProgram(cel.compile("x.or(errorFunc())").getAst()); + CelRuntime.Program program = cel.createProgram(compile(cel, "x.or(errorFunc())")); Object resultLhs = program.eval(ImmutableMap.of("x", Optional.of(5))); @@ -398,22 +440,17 @@ public void optionalOr_shortCircuits() throws Exception { @Test public void optionalOr_producesNonOptionalValue_throws() throws Exception { - Cel cel = - CelFactory.standardCelBuilder() - .addFunctionDeclarations( - CelFunctionDecl.newFunctionDeclaration( - "or", - CelOverloadDecl.newMemberOverload( - "optional_or_optional", SimpleType.INT, SimpleType.INT, SimpleType.INT))) - .addFunctionBindings( - CelFunctionBinding.from("optional_or_optional", Long.class, Long.class, Long::sum)) - .build(); + Cel cel = newCelBuilder().addVar("x", OptionalType.create(SimpleType.INT)).build(); - CelAbstractSyntaxTree ast = cel.compile("5.or(10)").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.or(optional.of(10))"); CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); - assertThat(e).hasMessageThat().contains("expected optional value, found: 5"); + assertThrows( + CelEvaluationException.class, + () -> cel.createProgram(ast).eval(ImmutableMap.of("x", 5L))); + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :4: No matching overload for function 'or'."); } @Test @@ -424,7 +461,7 @@ public void optionalOrValue_lhsHasValue_success(@TestParameter ConstantTestCases String.format( "optional.of(%s).orValue(%s)", testCase.sourceWithNonZeroValue, testCase.sourceWithZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -441,15 +478,18 @@ public void optionalOrValue_shortCircuits() throws Exception { "errorFunc", CelOverloadDecl.newGlobalOverload("error_overload", SimpleType.INT))) .addFunctionBindings( - CelFunctionBinding.from( - "error_overload", - ImmutableList.of(), - val -> { - throw new IllegalStateException("This function should not have been called!"); - })) + CelFunctionBinding.fromOverloads( + "errorFunc", + CelFunctionBinding.from( + "error_overload", + ImmutableList.of(), + val -> { + throw new IllegalStateException( + "This function should not have been called!"); + }))) .setResultType(SimpleType.INT) .build(); - CelRuntime.Program program = cel.createProgram(cel.compile("x.orValue(errorFunc())").getAst()); + CelRuntime.Program program = cel.createProgram(compile(cel, "x.orValue(errorFunc())")); Object resultLhs = program.eval(ImmutableMap.of("x", Optional.of(5))); @@ -462,7 +502,7 @@ public void optionalOrValue_rhsHasValue_success(@TestParameter ConstantTestCases Cel cel = newCelBuilder().setResultType(testCase.type).build(); String expression = String.format("optional.none().orValue(%s)", testCase.sourceWithNonZeroValue); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -475,32 +515,29 @@ public void optionalOrValue_rhsHasValue_success(@TestParameter ConstantTestCases @TestParameters("{source: 5.orValue(optional.of(10))}") @TestParameters("{source: 5.orValue(optional.none())}") public void optionalOrValue_unmatchingTypes_throwsCompilationException(String source) { + if (testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + return; + } Cel cel = newCelBuilder().build(); CelValidationException e = - assertThrows(CelValidationException.class, () -> cel.compile(source).getAst()); + assertThrows(CelValidationException.class, () -> compile(cel, source)); assertThat(e).hasMessageThat().contains("found no matching overload for 'orValue'"); } @Test public void optionalOrValue_producesNonOptionalValue_throws() throws Exception { - Cel cel = - CelFactory.standardCelBuilder() - .addFunctionDeclarations( - CelFunctionDecl.newFunctionDeclaration( - "orValue", - CelOverloadDecl.newMemberOverload( - "optional_orValue_value", SimpleType.INT, SimpleType.INT, SimpleType.INT))) - .addFunctionBindings( - CelFunctionBinding.from( - "optional_orValue_value", Long.class, Long.class, Long::sum)) - .build(); + Cel cel = newCelBuilder().addVar("x", OptionalType.create(SimpleType.INT)).build(); - CelAbstractSyntaxTree ast = cel.compile("5.orValue(10)").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.orValue(10)"); CelEvaluationException e = - assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); - assertThat(e).hasMessageThat().contains("expected optional value, found: 5"); + assertThrows( + CelEvaluationException.class, + () -> cel.createProgram(ast).eval(ImmutableMap.of("x", 5))); + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :9: No matching overload for function 'orValue'."); } @Test @@ -508,7 +545,7 @@ public void optionalOrValue_producesNonOptionalValue_throws() throws Exception { @TestParameters("{source: optional.none().or(optional.none()).orValue(42) == 42}") public void optionalChainedFunctions_constants_success(String source) throws Exception { Cel cel = newCelBuilder().setResultType(SimpleType.BOOL).build(); - CelAbstractSyntaxTree ast = cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = compile(cel, source); boolean result = (boolean) cel.createProgram(ast).eval(); @@ -527,7 +564,7 @@ public void optionalChainedFunctions_nestedMaps_success() throws Exception { .build(); String expression = "optional.ofNonZeroValue('').or(optional.of(m.c['dashed-index'])).orValue('default value')"; - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); String result = (String) @@ -550,7 +587,7 @@ public void optionalChainedFunctions_nestedMapsInvalidAccess_throws() throws Exc .setResultType(SimpleType.STRING) .build(); String expression = "optional.ofNonZeroValue(m.a.z).orValue(m.c['dashed-index'])"; - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); CelEvaluationException e = assertThrows( @@ -567,7 +604,7 @@ public void optionalChainedFunctions_nestedMapsInvalidAccess_throws() throws Exc @Test public void optionalFieldSelection_onMap_returnsOptionalEmpty() throws Exception { Cel cel = newCelBuilder().setResultType(OptionalType.create(SimpleType.INT)).build(); - CelAbstractSyntaxTree ast = cel.compile("{'a': 2}.?x").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "{'a': 2}.?x"); Object result = cel.createProgram(ast).eval(); @@ -577,7 +614,7 @@ public void optionalFieldSelection_onMap_returnsOptionalEmpty() throws Exception @Test public void optionalFieldSelection_onMap_returnsOptionalValue() throws Exception { Cel cel = newCelBuilder().setResultType(OptionalType.create(SimpleType.INT)).build(); - CelAbstractSyntaxTree ast = cel.compile("{'a': 2}.?a").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "{'a': 2}.?a"); Optional result = (Optional) cel.createProgram(ast).eval(); @@ -591,7 +628,7 @@ public void optionalFieldSelection_onProtoMessage_returnsOptionalEmpty() throws .setResultType(OptionalType.create(SimpleType.INT)) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .build(); - CelAbstractSyntaxTree ast = cel.compile("msg.?single_int32").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "msg.?single_int32"); Optional result = (Optional) @@ -607,7 +644,7 @@ public void optionalFieldSelection_onProtoMessage_returnsOptionalValue() throws .setResultType(OptionalType.create(SimpleType.INT)) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .build(); - CelAbstractSyntaxTree ast = cel.compile("msg.?single_int32").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "msg.?single_int32"); Optional result = (Optional) @@ -621,8 +658,8 @@ public void optionalFieldSelection_onProtoMessage_returnsOptionalValue() throws public void optionalFieldSelection_onProtoMessage_listValue() throws Exception { Cel cel = newCelBuilder().build(); CelAbstractSyntaxTree ast = - cel.compile("optional.of(TestAllTypes{repeated_string: ['foo']}).?repeated_string.value()") - .getAst(); + compile( + cel, "optional.of(TestAllTypes{repeated_string: ['foo']}).?repeated_string.value()"); List result = (List) cel.createProgram(ast).eval(); @@ -633,9 +670,8 @@ public void optionalFieldSelection_onProtoMessage_listValue() throws Exception { public void optionalFieldSelection_onProtoMessage_indexValue() throws Exception { Cel cel = newCelBuilder().build(); CelAbstractSyntaxTree ast = - cel.compile( - "optional.of(TestAllTypes{repeated_string: ['foo']}).?repeated_string[0].value()") - .getAst(); + compile( + cel, "optional.of(TestAllTypes{repeated_string: ['foo']}).?repeated_string[0].value()"); String result = (String) cel.createProgram(ast).eval(); @@ -656,7 +692,7 @@ public void optionalFieldSelection_onProtoMessage_chainedSuccess() throws Except StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())))) .build(); CelAbstractSyntaxTree ast = - cel.compile("m.?c.missing.or(m.?c['dashed-index']).value().?single_int32").getAst(); + compile(cel, "m.?c.missing.or(m.?c['dashed-index']).value().?single_int32"); Optional result = (Optional) @@ -674,7 +710,10 @@ public void optionalFieldSelection_onProtoMessage_chainedSuccess() throws Except } @Test - public void optionalFieldSelection_indexerOnProtoMessage_throwsException() { + public void optionalFieldSelection_indexerOnProtoMessage_typeCheck_throwsException() { + if (testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + return; + } Cel cel = newCelBuilder() .setResultType(OptionalType.create(SimpleType.INT)) @@ -682,8 +721,7 @@ public void optionalFieldSelection_indexerOnProtoMessage_throwsException() { .build(); CelValidationException e = - assertThrows( - CelValidationException.class, () -> cel.compile("msg[?single_int32]").getAst()); + assertThrows(CelValidationException.class, () -> compile(cel, "msg[?single_int32]")); assertThat(e).hasMessageThat().contains("undeclared reference to 'single_int32'"); } @@ -696,8 +734,7 @@ public void optionalFieldSelection_onProtoMessage_presenceTest() throws Exceptio .setResultType(SimpleType.BOOL) .build(); CelAbstractSyntaxTree ast = - cel.compile("!has(msg.?single_nested_message.bb) && has(msg.?standalone_message.bb)") - .getAst(); + compile(cel, "!has(msg.?single_nested_message.bb) && has(msg.?standalone_message.bb)"); boolean result = (boolean) @@ -718,7 +755,7 @@ public void optionalFieldSelection_onProtoMessage_presenceTest() throws Exceptio public void optionalFieldSelection_onMap_hasValueReturnsBoolean( String source, boolean expectedResult) throws Exception { Cel cel = newCelBuilder().setResultType(SimpleType.BOOL).build(); - CelAbstractSyntaxTree ast = cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = compile(cel, source); boolean result = (boolean) cel.createProgram(ast).eval(); @@ -735,7 +772,7 @@ public void optionalFieldSelection_onMap_hasMacroReturnsTrue() throws Exception SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .setResultType(SimpleType.BOOL) .build(); - CelAbstractSyntaxTree ast = cel.compile("has(m.?x.y)").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "has(m.?x.y)"); boolean result = (boolean) @@ -755,7 +792,7 @@ public void optionalFieldSelection_onMap_hasMacroReturnsFalse() throws Exception SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .setResultType(SimpleType.BOOL) .build(); - CelAbstractSyntaxTree ast = cel.compile("has(m.?x.y)").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "has(m.?x.y)"); boolean result = (boolean) cel.createProgram(ast).eval(ImmutableMap.of("m", ImmutableMap.of())); @@ -773,7 +810,7 @@ public void optionalFieldSelection_onOptionalMap_presenceTest() throws Exception SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING)))) .setResultType(SimpleType.BOOL) .build(); - CelAbstractSyntaxTree ast = cel.compile("has(optm.c) && !has(optm.c.missing)").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "has(optm.c) && !has(optm.c.missing)"); boolean result = (boolean) @@ -798,7 +835,7 @@ public void optionalIndex_onOptionalMap_returnsOptionalValue() throws Exception SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING)))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("optm.c[?'dashed-index']").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optm.c[?'dashed-index']"); Object result = cel.createProgram(ast) @@ -821,7 +858,7 @@ public void optionalIndex_onOptionalMap_returnsOptionalEmpty() throws Exception SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING)))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("optm.c[?'dashed-index']").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optm.c[?'dashed-index']"); Object result = cel.createProgram(ast) @@ -840,7 +877,7 @@ public void optionalIndex_onMap_returnsOptionalEmpty() throws Exception { SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("m.c[?'dashed-index']").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "m.c[?'dashed-index']"); Object result = cel.createProgram(ast).eval(ImmutableMap.of("m", ImmutableMap.of("c", ImmutableMap.of()))); @@ -858,7 +895,7 @@ public void optionalIndex_onMap_returnsOptionalValue() throws Exception { SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("m.c[?'dashed-index']").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "m.c[?'dashed-index']"); Object result = cel.createProgram(ast) @@ -875,8 +912,12 @@ public void optionalIndex_onMap_returnsOptionalValue() throws Exception { @TestParameters("{source: '{?x: x}'}") public void optionalIndex_onMapWithUnknownInput_returnsUnknownResult(String source) throws Exception { + if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + // TODO: Uncomment once unknowns is implemented + return; + } Cel cel = newCelBuilder().addVar("x", OptionalType.create(SimpleType.INT)).build(); - CelAbstractSyntaxTree ast = cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = compile(cel, source); Object result = cel.createProgram(ast).eval(); @@ -894,7 +935,7 @@ public void optionalIndex_onOptionalMapUsingFieldSelection_returnsOptionalValue( SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("{?'key': optional.of('test')}.?key").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "{?'key': optional.of('test')}.?key"); Object result = cel.createProgram(ast).eval(); @@ -908,7 +949,7 @@ public void optionalIndex_onList_returnsOptionalEmpty() throws Exception { .addVar("l", ListType.create(SimpleType.STRING)) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("l[?0]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "l[?0]"); CelRuntime.Program program = cel.createProgram(ast); assertThat(program.eval(ImmutableMap.of("l", ImmutableList.of()))).isEqualTo(Optional.empty()); @@ -921,7 +962,7 @@ public void optionalIndex_onList_returnsOptionalValue() throws Exception { .addVar("l", ListType.create(SimpleType.STRING)) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("l[?0]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "l[?0]"); Object result = cel.createProgram(ast).eval(ImmutableMap.of("l", ImmutableList.of("hello"))); @@ -935,7 +976,7 @@ public void optionalIndex_onOptionalList_returnsOptionalEmpty() throws Exception .addVar("optl", OptionalType.create(ListType.create(SimpleType.STRING))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("optl[?0]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optl[?0]"); CelRuntime.Program program = cel.createProgram(ast); assertThat(program.eval(ImmutableMap.of("optl", Optional.empty()))).isEqualTo(Optional.empty()); @@ -950,7 +991,7 @@ public void optionalIndex_onOptionalList_returnsOptionalValue() throws Exception .addVar("optl", OptionalType.create(ListType.create(SimpleType.STRING))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("optl[?0]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optl[?0]"); Object result = cel.createProgram(ast) @@ -961,12 +1002,16 @@ public void optionalIndex_onOptionalList_returnsOptionalValue() throws Exception @Test public void optionalIndex_onListWithUnknownInput_returnsUnknownResult() throws Exception { + if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + // TODO: Uncomment once unknowns is implemented + return; + } Cel cel = newCelBuilder() .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(ListType.create(SimpleType.INT)) .build(); - CelAbstractSyntaxTree ast = cel.compile("[?x]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "[?x]"); Object result = cel.createProgram(ast).eval(); @@ -980,7 +1025,7 @@ public void traditionalIndex_onOptionalList_returnsOptionalEmpty() throws Except .addVar("optl", OptionalType.create(ListType.create(SimpleType.STRING))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("optl[0]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optl[0]"); Object result = cel.createProgram(ast).eval(ImmutableMap.of("optl", Optional.empty())); @@ -996,12 +1041,16 @@ public void traditionalIndex_onOptionalList_returnsOptionalEmpty() throws Except @TestParameters("{expression: 'optional.none().orValue(optx)'}") public void optionalChainedFunctions_lhsIsUnknown_returnsUnknown(String expression) throws Exception { + if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + // TODO: Uncomment once unknowns is implemented + return; + } Cel cel = newCelBuilder() .addVar("optx", OptionalType.create(SimpleType.INT)) .addVar("x", SimpleType.INT) .build(); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); Object result = cel.createProgram(ast).eval(); @@ -1021,7 +1070,7 @@ public void optionalChainedFunctions_lhsIsError_returnsError(String expression) .addVar("optx", OptionalType.create(SimpleType.INT)) .addVar("x", SimpleType.INT) .build(); - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); assertThrows(CelEvaluationException.class, () -> cel.createProgram(ast).eval()); } @@ -1033,7 +1082,7 @@ public void traditionalIndex_onOptionalList_returnsOptionalValue() throws Except .addVar("optl", OptionalType.create(ListType.create(SimpleType.STRING))) .setResultType(OptionalType.create(SimpleType.STRING)) .build(); - CelAbstractSyntaxTree ast = cel.compile("optl[0]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optl[0]"); Object result = cel.createProgram(ast) @@ -1056,7 +1105,7 @@ public void optionalFieldSelection_onMap_chainedWithSelectorAndIndexer(String so SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .setResultType(SimpleType.BOOL) .build(); - CelAbstractSyntaxTree ast = cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = compile(cel, source); boolean result = (boolean) @@ -1084,7 +1133,7 @@ public void traditionalIndexSelection_onOptionalMap_chainedOperatorSuccess(Strin SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING)))) .setResultType(SimpleType.BOOL) .build(); - CelAbstractSyntaxTree ast = cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = compile(cel, source); boolean result = (boolean) @@ -1110,8 +1159,7 @@ public void traditionalIndexSelection_onOptionalMap_orChainedList() throws Excep .setResultType(SimpleType.STRING) .build(); - CelAbstractSyntaxTree ast = - cel.compile("optm.c.missing.or(optl[0]).orValue('default value')").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optm.c.missing.or(optl[0]).orValue('default value')"); String result = (String) @@ -1129,7 +1177,7 @@ public void traditionalIndexSelection_onOptionalMap_orChainedList() throws Excep @Test public void optionalMapCreation_valueIsEmpty_returnsEmptyMap() throws Exception { Cel cel = newCelBuilder().build(); - CelAbstractSyntaxTree ast = cel.compile("{?'key': optional.none()}").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "{?'key': optional.none()}"); Map result = (Map) cel.createProgram(ast).eval(); @@ -1139,7 +1187,7 @@ public void optionalMapCreation_valueIsEmpty_returnsEmptyMap() throws Exception @Test public void optionalMapCreation_valueIsPresent_returnsMap() throws Exception { Cel cel = newCelBuilder().build(); - CelAbstractSyntaxTree ast = cel.compile("{?'key': optional.of(5)}").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "{?'key': optional.of(5)}"); Map result = (Map) cel.createProgram(ast).eval(); @@ -1161,7 +1209,7 @@ public void optionalMapCreation_withNestedMap_returnsNestedMap() throws Exceptio SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .build(); CelAbstractSyntaxTree ast = - cel.compile("{?'nested_map': optional.ofNonZeroValue({?'map': m.?c})}").getAst(); + compile(cel, "{?'nested_map': optional.ofNonZeroValue({?'map': m.?c})}"); Map>> result = (Map>>) @@ -1190,8 +1238,7 @@ public void optionalMapCreation_withNestedMapContainingEmptyValue_emptyValueStri .build(); CelAbstractSyntaxTree ast = - cel.compile("{?'nested_map': optional.ofNonZeroValue({?'map': m.?c}), 'singleton': true}") - .getAst(); + compile(cel, "{?'nested_map': optional.ofNonZeroValue({?'map': m.?c}), 'singleton': true}"); Object result = cel.createProgram(ast).eval(ImmutableMap.of("m", ImmutableMap.of())); @@ -1199,11 +1246,14 @@ public void optionalMapCreation_withNestedMapContainingEmptyValue_emptyValueStri } @Test - public void optionalMapCreation_valueIsNonOptional_throws() { + public void optionalMapCreation_valueIsNonOptional_typeCheck_throws() { + if (testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + return; + } Cel cel = newCelBuilder().build(); CelValidationException e = - assertThrows(CelValidationException.class, () -> cel.compile("{?'hi': 'world'}").getAst()); + assertThrows(CelValidationException.class, () -> compile(cel, "{?'hi': 'world'}")); assertThat(e) .hasMessageThat() @@ -1217,7 +1267,7 @@ public void optionalMessageCreation_fieldValueIsEmpty_returnsEmptyMessage() thro .setResultType(StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .build(); CelAbstractSyntaxTree ast = - cel.compile("TestAllTypes{?single_double_wrapper: optional.ofNonZeroValue(0.0)}").getAst(); + compile(cel, "TestAllTypes{?single_double_wrapper: optional.ofNonZeroValue(0.0)}"); TestAllTypes result = (TestAllTypes) cel.createProgram(ast).eval(); @@ -1228,7 +1278,7 @@ public void optionalMessageCreation_fieldValueIsEmpty_returnsEmptyMessage() thro public void optionalMessageCreation_fieldValueIsPresent_returnsMessage() throws Exception { Cel cel = newCelBuilder().build(); CelAbstractSyntaxTree ast = - cel.compile("TestAllTypes{?single_double_wrapper: optional.ofNonZeroValue(5.0)}").getAst(); + compile(cel, "TestAllTypes{?single_double_wrapper: optional.ofNonZeroValue(5.0)}"); TestAllTypes result = (TestAllTypes) cel.createProgram(ast).eval(); @@ -1253,7 +1303,7 @@ public void optionalMessageCreation_fieldValueContainsEmptyMap_returnsEmptyMessa MapType.create( SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .build(); - CelAbstractSyntaxTree ast = cel.compile(source).getAst(); + CelAbstractSyntaxTree ast = compile(cel, source); TestAllTypes result = (TestAllTypes) cel.createProgram(ast).eval(ImmutableMap.of("m", ImmutableMap.of())); @@ -1270,8 +1320,7 @@ public void optionalMessageCreation_fieldValueContainsMap_returnsEmptyMessage() MapType.create( SimpleType.STRING, MapType.create(SimpleType.STRING, SimpleType.STRING))) .build(); - CelAbstractSyntaxTree ast = - cel.compile("TestAllTypes{?map_string_string: m[?'nested']}").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "TestAllTypes{?map_string_string: m[?'nested']}"); TestAllTypes result = (TestAllTypes) @@ -1293,7 +1342,7 @@ public void optionalListCreation_allElementsAreEmpty_returnsEmptyList() throws E .addVar("x", OptionalType.create(SimpleType.INT)) .addVar("y", OptionalType.create(SimpleType.DYN)) .build(); - CelAbstractSyntaxTree ast = cel.compile("[?m.?c, ?x, ?y]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "[?m.?c, ?x, ?y]"); List result = (List) @@ -1315,7 +1364,7 @@ public void optionalListCreation_containsEmptyElements_emptyElementsAreStripped( .addVar("x", OptionalType.create(SimpleType.INT)) .addVar("y", OptionalType.create(SimpleType.DYN)) .build(); - CelAbstractSyntaxTree ast = cel.compile("[?m.?c, ?x, ?y]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "[?m.?c, ?x, ?y]"); List result = (List) @@ -1340,7 +1389,7 @@ public void optionalListCreation_containsMixedTypeElements_success() throws Exce .addVar("y", OptionalType.create(SimpleType.DYN)) .addVar("z", SimpleType.STRING) .build(); - CelAbstractSyntaxTree ast = cel.compile("[?m.?c, ?x, ?y, z]").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "[?m.?c, ?x, ?y, z]"); List result = (List) @@ -1362,7 +1411,10 @@ public void optionalListCreation_containsMixedTypeElements_success() throws Exce @Test public void - optionalListCreation_containsMixedTypeElements_throwsWhenHomogeneousLiteralsEnabled() { + optionalListCreation_containsMixedTypeElements_typeCheck_throwsWhenHomogeneousLiteralsEnabled() { + if (testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + return; + } Cel cel = newCelBuilder() .setOptions(CelOptions.current().enableHomogeneousLiterals(true).build()) @@ -1375,7 +1427,7 @@ public void optionalListCreation_containsMixedTypeElements_success() throws Exce .build(); CelValidationException e = - assertThrows(CelValidationException.class, () -> cel.compile("[?m.?c, ?x, ?y]").getAst()); + assertThrows(CelValidationException.class, () -> compile(cel, "[?m.?c, ?x, ?y]")); assertThat(e).hasMessageThat().contains("expected type 'map(string, string)' but found 'int'"); } @@ -1392,7 +1444,7 @@ public void optionalListCreation_withinProtoMessage_success() throws Exception { String expression = "TestAllTypes{repeated_string: ['greetings', ?m.nested.?hello], ?repeated_int32:" + " optional.ofNonZeroValue([?x, ?y])}"; - CelAbstractSyntaxTree ast = cel.compile(expression).getAst(); + CelAbstractSyntaxTree ast = compile(cel, expression); TestAllTypes result = (TestAllTypes) @@ -1418,7 +1470,7 @@ public void optionalMapMacro_receiverIsEmpty_returnsOptionalEmpty() throws Excep .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelAbstractSyntaxTree ast = cel.compile("x.optMap(y, y + 1)").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.optMap(y, y + 1)"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("x", Optional.empty())); @@ -1433,7 +1485,7 @@ public void optionalMapMacro_receiverHasValue_returnsOptionalValue() throws Exce .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelAbstractSyntaxTree ast = cel.compile("x.optMap(y, y + 1)").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.optMap(y, y + 1)"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("x", Optional.of(42L))); @@ -1450,8 +1502,7 @@ public void optionalMapMacro_withNonIdent_throws() { .build(); CelValidationException e = - assertThrows( - CelValidationException.class, () -> cel.compile("x.optMap(y.z, y.z + 1)").getAst()); + assertThrows(CelValidationException.class, () -> compile(cel, "x.optMap(y.z, y.z + 1)")); assertThat(e).hasMessageThat().contains("optMap() variable name must be a simple identifier"); } @@ -1463,7 +1514,7 @@ public void optionalFlatMapMacro_receiverIsEmpty_returnsOptionalEmpty() throws E .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelAbstractSyntaxTree ast = cel.compile("x.optFlatMap(y, optional.of(y + 1))").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.optFlatMap(y, optional.of(y + 1))"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("x", Optional.empty())); @@ -1478,7 +1529,7 @@ public void optionalFlatMapMacro_receiverHasValue_returnsOptionalValue() throws .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelAbstractSyntaxTree ast = cel.compile("x.optFlatMap(y, optional.of(y + 1))").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.optFlatMap(y, optional.of(y + 1))"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("x", Optional.of(42L))); @@ -1494,8 +1545,7 @@ public void optionalFlatMapMacro_withOptionalOfNonZeroValue_optionalEmptyWhenVal .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelAbstractSyntaxTree ast = - cel.compile("x.optFlatMap(y, optional.ofNonZeroValue(y - 1))").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.optFlatMap(y, optional.ofNonZeroValue(y - 1))"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("x", Optional.of(1L))); @@ -1511,8 +1561,7 @@ public void optionalFlatMapMacro_withOptionalOfNonZeroValue_optionalValueWhenVal .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); - CelAbstractSyntaxTree ast = - cel.compile("x.optFlatMap(y, optional.ofNonZeroValue(y + 1))").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "x.optFlatMap(y, optional.ofNonZeroValue(y + 1))"); Optional result = (Optional) cel.createProgram(ast).eval(ImmutableMap.of("x", Optional.of(1L))); @@ -1521,15 +1570,18 @@ public void optionalFlatMapMacro_withOptionalOfNonZeroValue_optionalValueWhenVal } @Test - public void optionalFlatMapMacro_mappingExprIsNonOptional_throws() { + public void optionalFlatMapMacro_mappingExprIsNonOptional_typeCheck_throws() { + if (testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + return; + } + Cel cel = newCelBuilder() .addVar("x", OptionalType.create(SimpleType.INT)) .setResultType(OptionalType.create(SimpleType.INT)) .build(); CelValidationException e = - assertThrows( - CelValidationException.class, () -> cel.compile("x.optFlatMap(y, y + 1)").getAst()); + assertThrows(CelValidationException.class, () -> compile(cel, "x.optFlatMap(y, y + 1)")); assertThat(e).hasMessageThat().contains("found no matching overload for '_?_:_'"); } @@ -1544,7 +1596,7 @@ public void optionalFlatMapMacro_withNonIdent_throws() { CelValidationException e = assertThrows( - CelValidationException.class, () -> cel.compile("x.optFlatMap(y.z, y.z + 1)").getAst()); + CelValidationException.class, () -> compile(cel, "x.optFlatMap(y.z, y.z + 1)")); assertThat(e) .hasMessageThat() @@ -1554,7 +1606,7 @@ public void optionalFlatMapMacro_withNonIdent_throws() { @Test public void optionalType_typeResolution() throws Exception { Cel cel = newCelBuilder().build(); - CelAbstractSyntaxTree ast = cel.compile("optional_type").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "optional_type"); TypeType optionalRuntimeType = (TypeType) cel.createProgram(ast).eval(); @@ -1566,7 +1618,7 @@ public void optionalType_typeResolution() throws Exception { public void optionalType_typeComparison() throws Exception { Cel cel = newCelBuilder().build(); - CelAbstractSyntaxTree ast = cel.compile("type(optional.none()) == optional_type").getAst(); + CelAbstractSyntaxTree ast = compile(cel, "type(optional.none()) == optional_type"); assertThat(cel.createProgram(ast).eval()).isEqualTo(true); } @@ -1576,7 +1628,7 @@ public void optionalType_typeComparison() throws Exception { @TestParameters("{expression: '[\"a\",\"b\",\"c\"].first().value() == \"a\"'}") public void listFirst_success(String expression) throws Exception { Cel cel = newCelBuilder().build(); - boolean result = (boolean) cel.createProgram(cel.compile(expression).getAst()).eval(); + boolean result = (boolean) cel.createProgram(compile(cel, expression)).eval(); assertThat(result).isTrue(); } @@ -1585,15 +1637,18 @@ public void listFirst_success(String expression) throws Exception { @TestParameters("{expression: '[1, 2, 3].last().value() == 3'}") public void listLast_success(String expression) throws Exception { Cel cel = newCelBuilder().build(); - boolean result = (boolean) cel.createProgram(cel.compile(expression).getAst()).eval(); + boolean result = (boolean) cel.createProgram(compile(cel, expression)).eval(); assertThat(result).isTrue(); } @Test @TestParameters("{expression: '[1].first()', expectedError: 'undeclared reference to ''first'''}") @TestParameters("{expression: '[2].last()', expectedError: 'undeclared reference to ''last'''}") - public void listFirstAndLast_throws_earlyVersion(String expression, String expectedError) - throws Exception { + public void listFirstAndLast_typeCheck_throws_earlyVersion( + String expression, String expectedError) throws Exception { + if (testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + return; + } // Configure Cel with an earlier version of the 'optional' library, which did not support // 'first' and 'last' Cel cel = newCelBuilder(1).build(); @@ -1601,9 +1656,59 @@ public void listFirstAndLast_throws_earlyVersion(String expression, String expec assertThrows( CelValidationException.class, () -> { - cel.createProgram(cel.compile(expression).getAst()).eval(); + cel.createProgram(compile(cel, expression)).eval(); })) .hasMessageThat() .contains(expectedError); } + + @Test + public void optionalMapCreation_mapKeySetOnNonOptional_throws() { + String expression = "{?1: dyn(\"one\")}"; + Cel cel = newCelBuilder().build(); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, () -> cel.createProgram(compile(cel, expression)).eval()); + assertThat(e) + .hasMessageThat() + .contains("Cannot initialize optional entry '1' from non-optional value one"); + } + + @Test + public void optionalListCreation_listKeySetOnNonOptional_throws() { + String expression = "[?dyn(1)]"; + Cel cel = newCelBuilder().build(); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, () -> cel.createProgram(compile(cel, expression)).eval()); + assertThat(e) + .hasMessageThat() + .contains("Cannot initialize optional list element from non-optional value 1"); + } + + @Test + public void optionalMessageCreation_fieldKeySetOnNonOptional_throws() { + String expression = "TestAllTypes{?single_double_wrapper: dyn('foo')}"; + Cel cel = newCelBuilder().build(); + + CelEvaluationException e = + assertThrows( + CelEvaluationException.class, () -> cel.createProgram(compile(cel, expression)).eval()); + assertThat(e) + .hasMessageThat() + .contains( + "Cannot initialize optional entry 'single_double_wrapper' from non-optional value foo"); + } + + private CelAbstractSyntaxTree compile(CelCompiler compiler, String expression) + throws CelValidationException { + CelAbstractSyntaxTree ast = compiler.parse(expression).getAst(); + if (testMode.equals(TestMode.PLANNER_PARSE_ONLY)) { + return ast; + } + + return compiler.check(ast).getAst(); + } } diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 074ef2059..72ec02d12 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -338,7 +338,6 @@ java_library( java_library( name = "runtime_planner_impl", - testonly = 1, # TODO: Move to factory when ready for exposure visibility = ["//:internal"], exports = [ "//runtime/src/main/java/dev/cel/runtime:runtime_planner_impl", diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index 44377db09..e910c77a9 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -54,10 +54,15 @@ import java.util.function.Function; import org.jspecify.annotations.Nullable; +/** + * {@link CelRuntime} implementation based on the {@link ProgramPlanner}. + * + *

    CEL Library Internals. Do Not Use. + */ @AutoValue @Internal @Immutable -abstract class CelRuntimeImpl implements CelRuntime { +public abstract class CelRuntimeImpl implements CelRuntime { abstract ProgramPlanner planner(); @@ -180,7 +185,12 @@ public Object advanceEvaluation(UnknownContext context) throws CelEvaluationExce @Override public abstract Builder toRuntimeBuilder(); - static Builder newBuilder() { + /** + * CEL Library Internals. Do not use. Consumers should use {@code CelRuntimeFactory} instead. + * + *

    TODO: Restrict visibility once factory is introduced + */ + public static Builder newBuilder() { return new AutoValue_CelRuntimeImpl.Builder() .setFunctionBindings(ImmutableMap.of()) .setStandardFunctions(CelStandardFunctions.newBuilder().build()) @@ -188,8 +198,9 @@ static Builder newBuilder() { .setExtensionRegistry(ExtensionRegistry.getEmptyRegistry()); } + /** Builder for {@link CelRuntimeImpl}. */ @AutoValue.Builder - abstract static class Builder implements CelRuntimeBuilder { + public abstract static class Builder implements CelRuntimeBuilder { public abstract Builder setPlanner(ProgramPlanner planner); diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java index ebd678f24..8ae4a9e3e 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeLegacyImpl.java @@ -19,7 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import javax.annotation.concurrent.ThreadSafe; @@ -303,23 +302,23 @@ public CelRuntimeLegacyImpl build() { } } - ImmutableMap.Builder functionBindingsBuilder = - ImmutableMap.builder(); + DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); for (CelFunctionBinding standardFunctionBinding : newStandardFunctionBindings(runtimeEquality)) { - functionBindingsBuilder.put( - standardFunctionBinding.getOverloadId(), standardFunctionBinding); + dispatcherBuilder.addOverload( + standardFunctionBinding.getOverloadId(), + standardFunctionBinding.getArgTypes(), + standardFunctionBinding.isStrict(), + standardFunctionBinding.getDefinition()); } - functionBindingsBuilder.putAll(customFunctionBindings); - - DefaultDispatcher.Builder dispatcherBuilder = DefaultDispatcher.newBuilder(); - functionBindingsBuilder - .buildOrThrow() - .forEach( - (String overloadId, CelFunctionBinding func) -> - dispatcherBuilder.addOverload( - overloadId, func.getArgTypes(), func.isStrict(), func.getDefinition())); + for (CelFunctionBinding customBinding : customFunctionBindings.values()) { + dispatcherBuilder.addOverload( + customBinding.getOverloadId(), + customBinding.getArgTypes(), + customBinding.isStrict(), + customBinding.getDefinition()); + } RuntimeTypeProvider runtimeTypeProvider; diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java index 0d13f13be..35e3b76a3 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultDispatcher.java @@ -17,10 +17,11 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.auto.value.AutoBuilder; +import com.google.auto.value.AutoValue; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import dev.cel.common.CelErrorCode; @@ -29,6 +30,7 @@ import dev.cel.runtime.FunctionBindingImpl.DynamicDispatchOverload; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -124,16 +126,28 @@ Optional findSingleNonStrictOverload(List overloadI } public static Builder newBuilder() { - return new AutoBuilder_DefaultDispatcher_Builder(); + return new Builder(); } /** Builder for {@link DefaultDispatcher}. */ - @AutoBuilder(ofClass = DefaultDispatcher.class) - public abstract static class Builder { + public static class Builder { - abstract ImmutableMap overloads(); + @AutoValue + @Immutable + abstract static class OverloadEntry { + abstract ImmutableList> argTypes(); - abstract ImmutableMap.Builder overloadsBuilder(); + abstract boolean isStrict(); + + abstract CelFunctionOverload overload(); + + private static OverloadEntry of( + ImmutableList> argTypes, boolean isStrict, CelFunctionOverload overload) { + return new AutoValue_DefaultDispatcher_Builder_OverloadEntry(argTypes, isStrict, overload); + } + } + + private final Map overloads; @CanIgnoreReturnValue public Builder addOverload( @@ -146,18 +160,67 @@ public Builder addOverload( checkNotNull(argTypes); checkNotNull(overload); - overloadsBuilder() - .put( - overloadId, - CelResolvedOverload.of( - overloadId, - args -> guardedOp(overloadId, args, argTypes, isStrict, overload), - isStrict, - argTypes)); + OverloadEntry newEntry = OverloadEntry.of(argTypes, isStrict, overload); + + overloads.merge( + overloadId, + newEntry, + (existing, incoming) -> mergeDynamicDispatchesOrThrow(overloadId, existing, incoming)); + return this; } - public abstract DefaultDispatcher build(); + private OverloadEntry mergeDynamicDispatchesOrThrow( + String overloadId, OverloadEntry existing, OverloadEntry incoming) { + if (existing.overload() instanceof DynamicDispatchOverload + && incoming.overload() instanceof DynamicDispatchOverload) { + + DynamicDispatchOverload existingOverload = (DynamicDispatchOverload) existing.overload(); + DynamicDispatchOverload incomingOverload = (DynamicDispatchOverload) incoming.overload(); + + DynamicDispatchOverload mergedOverload = + new DynamicDispatchOverload( + overloadId, + ImmutableSet.builder() + .addAll(existingOverload.getOverloadBindings()) + .addAll(incomingOverload.getOverloadBindings()) + .build()); + + boolean isStrict = + mergedOverload.getOverloadBindings().stream().allMatch(CelFunctionBinding::isStrict); + + return OverloadEntry.of(incoming.argTypes(), isStrict, mergedOverload); + } + + throw new IllegalArgumentException("Duplicate overload ID binding: " + overloadId); + } + + public DefaultDispatcher build() { + ImmutableMap.Builder resolvedOverloads = ImmutableMap.builder(); + for (Map.Entry entry : overloads.entrySet()) { + String overloadId = entry.getKey(); + OverloadEntry overloadEntry = entry.getValue(); + resolvedOverloads.put( + overloadId, + CelResolvedOverload.of( + overloadId, + args -> + guardedOp( + overloadId, + args, + overloadEntry.argTypes(), + overloadEntry.isStrict(), + overloadEntry.overload()), + overloadEntry.isStrict(), + overloadEntry.argTypes())); + } + + return new DefaultDispatcher(resolvedOverloads.buildOrThrow()); + } + + private Builder() { + this.overloads = new HashMap<>(); + } } /** Creates an invocation guard around the overload definition. */ diff --git a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java index e49658190..9abc3716c 100644 --- a/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java +++ b/runtime/src/main/java/dev/cel/runtime/DefaultInterpreter.java @@ -431,9 +431,9 @@ private IntermediateResult evalCall(ExecutionFrame frame, CelExpr expr, CelCall case "type": return evalType(frame, callExpr); case "optional_or_optional": - return evalOptionalOr(frame, callExpr); + return evalOptionalOr(frame, expr); case "optional_orValue_value": - return evalOptionalOrValue(frame, callExpr); + return evalOptionalOrValue(frame, expr); case "select_optional_field": Optional result = maybeEvalOptionalSelectField(frame, expr, callExpr); if (result.isPresent()) { @@ -721,19 +721,19 @@ private IntermediateResult evalType(ExecutionFrame frame, CelCall callExpr) typeResolver.resolveObjectType(argResult.value(), checkedTypeValue)); } - private IntermediateResult evalOptionalOr(ExecutionFrame frame, CelCall callExpr) + private IntermediateResult evalOptionalOr(ExecutionFrame frame, CelExpr expr) throws CelEvaluationException { - return evalOptionalOrInternal(frame, callExpr, /* unwrapOptional= */ false); + return evalOptionalOrInternal(frame, expr, /* unwrapOptional= */ false); } - private IntermediateResult evalOptionalOrValue(ExecutionFrame frame, CelCall callExpr) + private IntermediateResult evalOptionalOrValue(ExecutionFrame frame, CelExpr expr) throws CelEvaluationException { - return evalOptionalOrInternal(frame, callExpr, /* unwrapOptional= */ true); + return evalOptionalOrInternal(frame, expr, /* unwrapOptional= */ true); } private IntermediateResult evalOptionalOrInternal( - ExecutionFrame frame, CelCall callExpr, boolean unwrapOptional) - throws CelEvaluationException { + ExecutionFrame frame, CelExpr expr, boolean unwrapOptional) throws CelEvaluationException { + CelCall callExpr = expr.call(); CelExpr lhsExpr = callExpr .target() @@ -746,10 +746,11 @@ private IntermediateResult evalOptionalOrInternal( } if (!(lhsResult.value() instanceof Optional)) { + String functionName = unwrapOptional ? "orValue" : "or"; throw CelEvaluationExceptionBuilder.newBuilder( - "expected optional value, found: %s", lhsResult.value()) + "No matching overload for function '%s'.", functionName) .setErrorCode(CelErrorCode.INVALID_ARGUMENT) - .setMetadata(metadata, lhsExpr.id()) + .setMetadata(metadata, expr.id()) .build(); } @@ -832,6 +833,11 @@ private IntermediateResult evalList(ExecutionFrame frame, CelExpr unusedExpr, Ce // optionals. && optionalIndicesSet.contains(i) && !isUnknownValue(value)) { + if (!(value instanceof Optional)) { + throw new IllegalArgumentException( + String.format( + "Cannot initialize optional list element from non-optional value %s", value)); + } Optional optionalVal = (Optional) value; if (!optionalVal.isPresent()) { continue; @@ -870,6 +876,12 @@ private IntermediateResult evalMap(ExecutionFrame frame, CelMap mapExpr) Object value = valueResult.value(); if (entry.optionalEntry() && !isUnknownValue(value)) { + if (!(value instanceof Optional)) { + throw new IllegalArgumentException( + String.format( + "Cannot initialize optional entry '%s' from non-optional value %s", + keyResult.value(), value)); + } Optional optionalVal = (Optional) value; if (!optionalVal.isPresent()) { // This is a no-op currently but will be semantically correct when extended proto @@ -905,6 +917,13 @@ private IntermediateResult evalStruct(ExecutionFrame frame, CelExpr expr, CelStr Object value = fieldResult.value(); if (entry.optionalEntry()) { + if (!(value instanceof Optional)) { + throw new IllegalArgumentException( + String.format( + "Cannot initialize optional entry 'single_double_wrapper' from non-optional" + + " value %s", + value)); + } Optional optionalVal = (Optional) value; if (!optionalVal.isPresent()) { // This is a no-op currently but will be semantically correct when extended proto diff --git a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java index 48c0eb47a..faea853f8 100644 --- a/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/FunctionBindingImpl.java @@ -145,7 +145,11 @@ public Object apply(Object[] args) throws CelEvaluationException { .collect(toImmutableList())); } - private DynamicDispatchOverload( + ImmutableSet getOverloadBindings() { + return overloadBindings; + } + + DynamicDispatchOverload( String functionName, ImmutableSet overloadBindings) { this.functionName = functionName; this.overloadBindings = overloadBindings; diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index 0a7ebbfb3..b827afed5 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -24,6 +24,9 @@ java_library( ":eval_create_struct", ":eval_fold", ":eval_late_bound_call", + ":eval_optional_or", + ":eval_optional_or_value", + ":eval_optional_select_field", ":eval_or", ":eval_test_only", ":eval_unary", @@ -54,6 +57,7 @@ java_library( "@maven//:com_google_code_findbugs_annotations", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", ], ) @@ -412,3 +416,45 @@ java_library( "@maven//:com_google_errorprone_error_prone_annotations", ], ) + +java_library( + name = "eval_optional_or", + srcs = ["EvalOptionalOr.java"], + deps = [ + ":eval_helpers", + ":execution_frame", + ":planned_interpretable", + "//common/exceptions:overload_not_found", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_optional_or_value", + srcs = ["EvalOptionalOrValue.java"], + deps = [ + ":eval_helpers", + ":execution_frame", + ":planned_interpretable", + "//common/exceptions:overload_not_found", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) + +java_library( + name = "eval_optional_select_field", + srcs = ["EvalOptionalSelectField.java"], + deps = [ + ":eval_helpers", + ":execution_frame", + ":planned_interpretable", + "//common/values", + "//runtime:interpretable", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", + ], +) diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java index 389a21a82..773272ea3 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java @@ -18,29 +18,49 @@ import com.google.errorprone.annotations.Immutable; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.GlobalResolver; +import java.util.Optional; @Immutable final class EvalCreateList extends PlannedInterpretable { - // Array contents are not mutated @SuppressWarnings("Immutable") private final PlannedInterpretable[] values; + @SuppressWarnings("Immutable") + private final boolean[] isOptional; + @Override public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(values.length); - for (PlannedInterpretable value : values) { - builder.add(EvalHelpers.evalStrictly(value, resolver, frame)); + for (int i = 0; i < values.length; i++) { + Object element = EvalHelpers.evalStrictly(values[i], resolver, frame); + + if (isOptional[i]) { + if (!(element instanceof Optional)) { + throw new IllegalArgumentException( + String.format( + "Cannot initialize optional list element from non-optional value %s", element)); + } + + Optional opt = (Optional) element; + if (!opt.isPresent()) { + continue; + } + element = opt.get(); + } + + builder.add(element); } return builder.build(); } - static EvalCreateList create(long exprId, PlannedInterpretable[] values) { - return new EvalCreateList(exprId, values); + static EvalCreateList create(long exprId, PlannedInterpretable[] values, boolean[] isOptional) { + return new EvalCreateList(exprId, values, isOptional); } - private EvalCreateList(long exprId, PlannedInterpretable[] values) { + private EvalCreateList(long exprId, PlannedInterpretable[] values, boolean[] isOptional) { super(exprId); this.values = values; + this.isOptional = isOptional; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java index 4c5a1f0bf..1ab0f7e5b 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -22,6 +22,7 @@ import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.GlobalResolver; import java.util.HashSet; +import java.util.Optional; @Immutable final class EvalCreateMap extends PlannedInterpretable { @@ -34,6 +35,10 @@ final class EvalCreateMap extends PlannedInterpretable { @SuppressWarnings("Immutable") private final PlannedInterpretable[] values; + // Array contents are not mutated + @SuppressWarnings("Immutable") + private final boolean[] isOptional; + @Override public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { ImmutableMap.Builder builder = @@ -42,26 +47,55 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval for (int i = 0; i < keys.length; i++) { Object key = keys[i].eval(resolver, frame); + Object val = values[i].eval(resolver, frame); if (!keysSeen.add(key)) { throw new LocalizedEvaluationException(CelDuplicateKeyException.of(key), keys[i].exprId()); } - builder.put(key, values[i].eval(resolver, frame)); + if (isOptional[i]) { + if (!(val instanceof Optional)) { + throw new IllegalArgumentException( + String.format( + "Cannot initialize optional entry '%s' from non-optional value %s", key, val)); + } + + Optional opt = (Optional) val; + if (!opt.isPresent()) { + // This is a no-op currently but will be semantically correct when extended proto + // support allows proto mutation. + keysSeen.remove(key); + continue; + } + val = opt.get(); + } else { + System.out.println(); + } + + builder.put(key, val); } return builder.buildOrThrow(); } static EvalCreateMap create( - long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) { - return new EvalCreateMap(exprId, keys, values); + long exprId, + PlannedInterpretable[] keys, + PlannedInterpretable[] values, + boolean[] isOptional) { + return new EvalCreateMap(exprId, keys, values, isOptional); } - private EvalCreateMap(long exprId, PlannedInterpretable[] keys, PlannedInterpretable[] values) { + private EvalCreateMap( + long exprId, + PlannedInterpretable[] keys, + PlannedInterpretable[] values, + boolean[] isOptional) { super(exprId); Preconditions.checkArgument(keys.length == values.length); + Preconditions.checkArgument(keys.length == isOptional.length); this.keys = keys; this.values = values; + this.isOptional = isOptional; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java index 7d03854c2..4edc87b79 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateStruct.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; @Immutable final class EvalCreateStruct extends PlannedInterpretable { @@ -38,11 +39,35 @@ final class EvalCreateStruct extends PlannedInterpretable { @SuppressWarnings("Immutable") private final PlannedInterpretable[] values; + // Array contents are not mutated + @SuppressWarnings("Immutable") + private final boolean[] isOptional; + @Override public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException { Map fieldValues = new HashMap<>(); for (int i = 0; i < keys.length; i++) { Object value = values[i].eval(resolver, frame); + + if (isOptional[i]) { + if (!(value instanceof Optional)) { + throw new IllegalArgumentException( + String.format( + "Cannot initialize optional entry 'single_double_wrapper' from non-optional value" + + " %s", + value)); + } + + Optional opt = (Optional) value; + if (!opt.isPresent()) { + // This is a no-op currently but will be semantically correct when extended proto + // support allows proto mutation. + fieldValues.remove(keys[i]); + continue; + } + value = opt.get(); + } + fieldValues.put(keys[i], value); } @@ -54,7 +79,7 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval () -> new IllegalArgumentException("Type name not found: " + structType.name())); if (value instanceof StructValue) { - return ((StructValue) value).value(); + return ((StructValue) value).value(); } return value; @@ -65,8 +90,9 @@ static EvalCreateStruct create( CelValueProvider valueProvider, CelType structType, String[] keys, - PlannedInterpretable[] values) { - return new EvalCreateStruct(exprId, valueProvider, structType, keys, values); + PlannedInterpretable[] values, + boolean[] isOptional) { + return new EvalCreateStruct(exprId, valueProvider, structType, keys, values, isOptional); } private EvalCreateStruct( @@ -74,11 +100,13 @@ private EvalCreateStruct( CelValueProvider valueProvider, CelType structType, String[] keys, - PlannedInterpretable[] values) { + PlannedInterpretable[] values, + boolean[] isOptional) { super(exprId); this.valueProvider = valueProvider; this.structType = structType; this.keys = keys; this.values = values; + this.isOptional = isOptional; } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOr.java new file mode 100644 index 000000000..70009d567 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOr.java @@ -0,0 +1,53 @@ +// 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.runtime.planner; + +import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.exceptions.CelOverloadNotFoundException; +import dev.cel.runtime.GlobalResolver; +import java.util.Optional; + +@Immutable +final class EvalOptionalOr extends PlannedInterpretable { + private final PlannedInterpretable lhs; + private final PlannedInterpretable rhs; + + @Override + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { + Object lhsValue = EvalHelpers.evalStrictly(lhs, resolver, frame); + + if (!(lhsValue instanceof Optional)) { + throw new CelOverloadNotFoundException("or"); + } + + Optional optionalLhs = (Optional) lhsValue; + if (optionalLhs.isPresent()) { + return optionalLhs; + } + + return EvalHelpers.evalStrictly(rhs, resolver, frame); + } + + static EvalOptionalOr create(long exprId, PlannedInterpretable lhs, PlannedInterpretable rhs) { + return new EvalOptionalOr(exprId, lhs, rhs); + } + + private EvalOptionalOr(long exprId, PlannedInterpretable lhs, PlannedInterpretable rhs) { + super(exprId); + this.lhs = Preconditions.checkNotNull(lhs); + this.rhs = Preconditions.checkNotNull(rhs); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOrValue.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOrValue.java new file mode 100644 index 000000000..7a4940c7c --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalOrValue.java @@ -0,0 +1,53 @@ +// 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.runtime.planner; + +import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.exceptions.CelOverloadNotFoundException; +import dev.cel.runtime.GlobalResolver; +import java.util.Optional; + +@Immutable +final class EvalOptionalOrValue extends PlannedInterpretable { + private final PlannedInterpretable lhs; + private final PlannedInterpretable rhs; + + @Override + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { + Object lhsValue = EvalHelpers.evalStrictly(lhs, resolver, frame); + if (!(lhsValue instanceof Optional)) { + throw new CelOverloadNotFoundException("orValue"); + } + + Optional optionalLhs = (Optional) lhsValue; + if (optionalLhs.isPresent()) { + return optionalLhs.get(); + } + + return EvalHelpers.evalStrictly(rhs, resolver, frame); + } + + static EvalOptionalOrValue create( + long exprId, PlannedInterpretable lhs, PlannedInterpretable rhs) { + return new EvalOptionalOrValue(exprId, lhs, rhs); + } + + private EvalOptionalOrValue(long exprId, PlannedInterpretable lhs, PlannedInterpretable rhs) { + super(exprId); + this.lhs = Preconditions.checkNotNull(lhs); + this.rhs = Preconditions.checkNotNull(rhs); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalSelectField.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalSelectField.java new file mode 100644 index 000000000..bc14149f3 --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOptionalSelectField.java @@ -0,0 +1,89 @@ +// 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.runtime.planner; + +import com.google.common.base.Preconditions; +import com.google.errorprone.annotations.Immutable; +import dev.cel.common.values.CelValueConverter; +import dev.cel.common.values.SelectableValue; +import dev.cel.runtime.GlobalResolver; +import java.util.Map; +import java.util.Optional; + +@Immutable +final class EvalOptionalSelectField extends PlannedInterpretable { + private final PlannedInterpretable operand; + private final PlannedInterpretable selectAttribute; + private final String field; + private final CelValueConverter celValueConverter; + + @Override + public Object eval(GlobalResolver resolver, ExecutionFrame frame) { + Object operandValue = EvalHelpers.evalStrictly(operand, resolver, frame); + + if (operandValue instanceof Optional) { + Optional opt = (Optional) operandValue; + if (!opt.isPresent()) { + return Optional.empty(); + } + operandValue = opt.get(); + } + + Object runtimeOperandValue = celValueConverter.toRuntimeValue(operandValue); + boolean hasField = false; + + if (runtimeOperandValue instanceof SelectableValue) { + // Guaranteed to be a string. Anything other than string is an error. + @SuppressWarnings("unchecked") + SelectableValue selectableValue = (SelectableValue) runtimeOperandValue; + hasField = selectableValue.find(field).isPresent(); + } else if (runtimeOperandValue instanceof Map) { + hasField = ((Map) runtimeOperandValue).containsKey(field); + } + if (!hasField) { + return Optional.empty(); + } + + Object resultValue = EvalHelpers.evalStrictly(selectAttribute, resolver, frame); + + if (resultValue instanceof Optional) { + return resultValue; + } + + return Optional.of(resultValue); + } + + static EvalOptionalSelectField create( + long exprId, + PlannedInterpretable operand, + String field, + PlannedInterpretable selectAttribute, + CelValueConverter celValueConverter) { + return new EvalOptionalSelectField(exprId, operand, field, selectAttribute, celValueConverter); + } + + private EvalOptionalSelectField( + long exprId, + PlannedInterpretable operand, + String field, + PlannedInterpretable selectAttribute, + CelValueConverter celValueConverter) { + super(exprId); + this.operand = Preconditions.checkNotNull(operand); + this.field = Preconditions.checkNotNull(field); + this.selectAttribute = Preconditions.checkNotNull(selectAttribute); + this.celValueConverter = Preconditions.checkNotNull(celValueConverter); + } +} diff --git a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java index fc22d4f10..7935e4838 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java @@ -52,6 +52,7 @@ import java.util.HashMap; import java.util.NoSuchElementException; import java.util.Optional; +import org.jspecify.annotations.Nullable; /** * {@code ProgramPlanner} resolves functions, types, and identifiers at plan time given a @@ -244,6 +245,13 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) { resolvedOverload = dispatcher.findOverload(functionName).orElse(null); } + PlannedInterpretable optionalCall = + maybeInterceptOptionalCalls(resolvedOverload, functionName, evaluatedArgs, expr) + .orElse(null); + if (optionalCall != null) { + return optionalCall; + } + if (resolvedOverload == null) { if (!lateBoundFunctionNames.contains(functionName)) { CelReference reference = ctx.referenceMap().get(expr.id()); @@ -274,6 +282,62 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) { } } + /** + * Intercepts a potential optional function call. + * + *

    This is analogous to cel-go's decorator. This could be moved to {@code CelOptionalLibrary} + * once we add the support for it. + */ + private Optional maybeInterceptOptionalCalls( + @Nullable CelResolvedOverload resolvedOverload, + String functionName, + PlannedInterpretable[] evaluatedArgs, + CelExpr expr) { + if (evaluatedArgs.length != 2) { + return Optional.empty(); + } + + String overloadId = resolvedOverload == null ? "" : resolvedOverload.getOverloadId(); + + switch (functionName) { + case "or": + if (overloadId.isEmpty() || overloadId.equals("optional_or_optional")) { + return Optional.of(EvalOptionalOr.create(expr.id(), evaluatedArgs[0], evaluatedArgs[1])); + } + + return Optional.empty(); + case "orValue": + if (overloadId.isEmpty() || overloadId.equals("optional_orValue_value")) { + return Optional.of( + EvalOptionalOrValue.create(expr.id(), evaluatedArgs[0], evaluatedArgs[1])); + } + + return Optional.empty(); + default: + break; + } + + if (Operator.OPTIONAL_SELECT.getFunction().equals(functionName)) { + String field = expr.call().args().get(1).constant().stringValue(); + InterpretableAttribute attribute; + if (evaluatedArgs[0] instanceof EvalAttribute) { + attribute = (EvalAttribute) evaluatedArgs[0]; + } else { + attribute = + EvalAttribute.create( + expr.id(), attributeFactory.newRelativeAttribute(evaluatedArgs[0])); + } + Qualifier qualifier = StringQualifier.create(field); + PlannedInterpretable selectAttribute = attribute.addQualifier(expr.id(), qualifier); + + return Optional.of( + EvalOptionalSelectField.create( + expr.id(), evaluatedArgs[0], field, selectAttribute, celValueConverter)); + } + + return Optional.empty(); + } + private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) { CelStruct struct = celExpr.struct(); CelType structType = resolveStructType(struct); @@ -281,19 +345,21 @@ private PlannedInterpretable planCreateStruct(CelExpr celExpr, PlannerContext ct ImmutableList entries = struct.entries(); String[] keys = new String[entries.size()]; PlannedInterpretable[] values = new PlannedInterpretable[entries.size()]; + boolean[] isOptional = new boolean[entries.size()]; for (int i = 0; i < entries.size(); i++) { Entry entry = entries.get(i); keys[i] = entry.fieldKey(); values[i] = plan(entry.value(), ctx); + isOptional[i] = entry.optionalEntry(); } - return EvalCreateStruct.create(celExpr.id(), valueProvider, structType, keys, values); + return EvalCreateStruct.create( + celExpr.id(), valueProvider, structType, keys, values, isOptional); } private PlannedInterpretable planCreateList(CelExpr celExpr, PlannerContext ctx) { CelList list = celExpr.list(); - ImmutableList elements = list.elements(); PlannedInterpretable[] values = new PlannedInterpretable[elements.size()]; @@ -301,7 +367,12 @@ private PlannedInterpretable planCreateList(CelExpr celExpr, PlannerContext ctx) values[i] = plan(elements.get(i), ctx); } - return EvalCreateList.create(celExpr.id(), values); + boolean[] isOptional = new boolean[elements.size()]; + for (int optionalIndex : list.optionalIndices()) { + isOptional[optionalIndex] = true; + } + + return EvalCreateList.create(celExpr.id(), values, isOptional); } private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) { @@ -310,14 +381,16 @@ private PlannedInterpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) ImmutableList entries = map.entries(); PlannedInterpretable[] keys = new PlannedInterpretable[entries.size()]; PlannedInterpretable[] values = new PlannedInterpretable[entries.size()]; + boolean[] isOptional = new boolean[entries.size()]; for (int i = 0; i < entries.size(); i++) { CelMap.Entry entry = entries.get(i); keys[i] = plan(entry.key(), ctx); values[i] = plan(entry.value(), ctx); + isOptional[i] = entry.optionalEntry(); } - return EvalCreateMap.create(celExpr.id(), keys, values); + return EvalCreateMap.create(celExpr.id(), keys, values, isOptional); } private PlannedInterpretable planComprehension(CelExpr expr, PlannerContext ctx) { From 50063902f8114b1c331ef81a3b9e3007082d8d9a Mon Sep 17 00:00:00 2001 From: Jonathan Tatum Date: Wed, 25 Feb 2026 14:41:09 -0800 Subject: [PATCH 092/100] Add support for importing/exporting common limits to YAML environment configs. PiperOrigin-RevId: 875343469 --- .../java/dev/cel/bundle/CelEnvironment.java | 82 ++++++++++++++++--- .../cel/bundle/CelEnvironmentExporter.java | 17 ++++ .../cel/bundle/CelEnvironmentYamlParser.java | 68 +++++++++++++++ .../bundle/CelEnvironmentYamlSerializer.java | 17 ++++ .../bundle/CelEnvironmentExporterTest.java | 29 +++++++ .../dev/cel/bundle/CelEnvironmentTest.java | 50 +++++++++++ .../bundle/CelEnvironmentYamlParserTest.java | 57 +++++++++++++ .../CelEnvironmentYamlSerializerTest.java | 4 + .../test/resources/environment/dump_env.yaml | 7 ++ .../resources/environment/extended_env.yaml | 4 + 10 files changed, 323 insertions(+), 12 deletions(-) diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java index 7ec2149a7..c8e424217 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java @@ -53,6 +53,7 @@ import dev.cel.runtime.CelRuntimeLibrary; import java.util.Arrays; import java.util.Optional; +import java.util.function.ObjIntConsumer; /** * CelEnvironment is a native representation of a CEL environment for compiler and runtime. This @@ -74,6 +75,24 @@ public abstract class CelEnvironment { "strings", CanonicalCelExtension.STRINGS, "comprehensions", CanonicalCelExtension.COMPREHENSIONS); + private static final ImmutableMap> LIMIT_HANDLERS = + ImmutableMap.of( + "cel.limit.expression_code_points", + (options, value) -> options.maxExpressionCodePointSize(value), + "cel.limit.parse_error_recovery", + (options, value) -> options.maxParseErrorRecoveryLimit(value), + "cel.limit.parse_recursion_depth", + (options, value) -> options.maxParseRecursionDepth(value)); + + private static final ImmutableMap FEATURE_HANDLERS = + ImmutableMap.of( + "cel.feature.macro_call_tracking", + (options, enabled) -> options.populateMacroCalls(enabled), + "cel.feature.backtick_escape_syntax", + (options, enabled) -> options.enableQuotedIdentifierSyntax(enabled), + "cel.feature.cross_type_numeric_comparisons", + (options, enabled) -> options.enableHeterogeneousNumericComparisons(enabled)); + /** Environment source in textual format (ex: textproto, YAML). */ public abstract Optional source(); @@ -112,6 +131,9 @@ public abstract class CelEnvironment { /** Feature flags to enable in the environment. */ public abstract ImmutableSet features(); + /** Limits to set in the environment. */ + public abstract ImmutableSet limits(); + /** Builder for {@link CelEnvironment}. */ @AutoValue.Builder public abstract static class Builder { @@ -168,7 +190,14 @@ public Builder setFeatures(FeatureFlag... featureFlags) { return setFeatures(ImmutableSet.copyOf(featureFlags)); } - public abstract Builder setFeatures(ImmutableSet macros); + public abstract Builder setFeatures(ImmutableSet featureFlags); + + @CanIgnoreReturnValue + public Builder setLimits(Limit... limits) { + return setLimits(ImmutableSet.copyOf(limits)); + } + + public abstract Builder setLimits(ImmutableSet limits); abstract CelEnvironment autoBuild(); @@ -200,13 +229,14 @@ public static Builder newBuilder() { .setContainer(CelContainer.ofName("")) .setVariables(ImmutableSet.of()) .setFunctions(ImmutableSet.of()) - .setFeatures(ImmutableSet.of()); + .setFeatures(ImmutableSet.of()) + .setLimits(ImmutableSet.of()); } /** Extends the provided {@link CelCompiler} environment with this configuration. */ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions) throws CelEnvironmentException { - celOptions = applyFeatureFlags(celOptions); + celOptions = applyEnvironmentOptions(celOptions); try { CelTypeProvider celTypeProvider = celCompiler.getTypeProvider(); CelCompilerBuilder compilerBuilder = @@ -236,7 +266,7 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions) /** Extends the provided {@link Cel} environment with this configuration. */ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException { - celOptions = applyFeatureFlags(celOptions); + celOptions = applyEnvironmentOptions(celOptions); try { // Casting is necessary to only extend the compiler here CelCompiler celCompiler = extend((CelCompiler) cel, celOptions); @@ -249,18 +279,22 @@ public Cel extend(Cel cel, CelOptions celOptions) throws CelEnvironmentException } } - private CelOptions applyFeatureFlags(CelOptions celOptions) { + private CelOptions applyEnvironmentOptions(CelOptions celOptions) { CelOptions.Builder optionsBuilder = celOptions.toBuilder(); for (FeatureFlag featureFlag : features()) { - if (featureFlag.name().equals("cel.feature.macro_call_tracking")) { - optionsBuilder.populateMacroCalls(featureFlag.enabled()); - } else if (featureFlag.name().equals("cel.feature.backtick_escape_syntax")) { - optionsBuilder.enableQuotedIdentifierSyntax(featureFlag.enabled()); - } else if (featureFlag.name().equals("cel.feature.cross_type_numeric_comparisons")) { - optionsBuilder.enableHeterogeneousNumericComparisons(featureFlag.enabled()); - } else { + BooleanOptionConsumer consumer = FEATURE_HANDLERS.get(featureFlag.name()); + if (consumer == null) { throw new IllegalArgumentException("Unknown feature flag: " + featureFlag.name()); } + consumer.accept(optionsBuilder, featureFlag.enabled()); + } + for (Limit limit : limits()) { + int value = limit.value() < 0 ? -1 : limit.value(); + ObjIntConsumer consumer = LIMIT_HANDLERS.get(limit.name()); + if (consumer == null) { + throw new IllegalArgumentException("Unknown limit: " + limit.name()); + } + consumer.accept(optionsBuilder, value); } return optionsBuilder.build(); } @@ -672,6 +706,25 @@ public static FeatureFlag create(String name, boolean enabled) { } } + /** + * Represents a configurable limit in the environment. + * + *

    A negative value indicates no limit. If not specified, the limit should be set to the + * library default. + */ + @AutoValue + public abstract static class Limit { + /** Normalized name of the limit (e.g. cel.limit.expression_code_points */ + public abstract String name(); + + /** The value of the limit, -1 means no limit. */ + public abstract int value(); + + public static Limit create(String name, int value) { + return new AutoValue_CelEnvironment_Limit(name, value); + } + } + /** * Represents a configuration for a canonical CEL extension that can be enabled in the * environment. @@ -995,4 +1048,9 @@ public static OverloadSelector.Builder newBuilder() { } } } + + @FunctionalInterface + private static interface BooleanOptionConsumer { + void accept(CelOptions.Builder options, boolean value); + } } diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java index 1ed113db7..f86787090 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentExporter.java @@ -221,6 +221,23 @@ private void addOptions(CelEnvironment.Builder envBuilder, CelOptions options) { featureFlags.add(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true)); } envBuilder.setFeatures(featureFlags.build()); + ImmutableSet.Builder limits = ImmutableSet.builder(); + if (options.maxExpressionCodePointSize() != CelOptions.DEFAULT.maxExpressionCodePointSize()) { + limits.add( + CelEnvironment.Limit.create( + "cel.limit.expression_code_points", options.maxExpressionCodePointSize())); + } + if (options.maxParseErrorRecoveryLimit() != CelOptions.DEFAULT.maxParseErrorRecoveryLimit()) { + limits.add( + CelEnvironment.Limit.create( + "cel.limit.parse_error_recovery", options.maxParseErrorRecoveryLimit())); + } + if (options.maxParseRecursionDepth() != CelOptions.DEFAULT.maxParseRecursionDepth()) { + limits.add( + CelEnvironment.Limit.create( + "cel.limit.parse_recursion_depth", options.maxParseRecursionDepth())); + } + envBuilder.setLimits(limits.build()); } /** diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java index 2fa8923f1..ce8857654 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java @@ -43,6 +43,7 @@ import dev.cel.common.formats.YamlHelper.YamlNodeType; import dev.cel.common.formats.YamlParserContextImpl; import dev.cel.common.internal.CelCodePointArray; +import java.util.Optional; import org.jspecify.annotations.Nullable; import org.yaml.snakeyaml.DumperOptions.FlowStyle; import org.yaml.snakeyaml.nodes.MappingNode; @@ -188,6 +189,70 @@ private ImmutableSet parseFeatures( return featureFlags.build(); } + private ImmutableSet parseLimits(ParserContext ctx, Node node) { + long valueId = ctx.collectMetadata(node); + if (!validateYamlType(node, YamlNodeType.LIST, YamlNodeType.TEXT)) { + ctx.reportError(valueId, "Unsupported limits format"); + } + + ImmutableSet.Builder limits = ImmutableSet.builder(); + + SequenceNode featureListNode = (SequenceNode) node; + for (Node featureMapNode : featureListNode.getValue()) { + long featureMapId = ctx.collectMetadata(featureMapNode); + if (!assertYamlType(ctx, featureMapId, featureMapNode, YamlNodeType.MAP)) { + continue; + } + + MappingNode featureMap = (MappingNode) featureMapNode; + String name = ""; + Optional value = Optional.empty(); + // Shorthand syntax for limit: "cel.limit.foo: 1" + if (featureMap.getValue().size() == 1) { + NodeTuple nodeTuple = featureMap.getValue().get(0); + Node keyNode = nodeTuple.getKeyNode(); + Node valueNode = nodeTuple.getValueNode(); + String keyName = ((ScalarNode) keyNode).getValue(); + if (!keyName.equals("name") && !keyName.equals("value")) { + limits.add(CelEnvironment.Limit.create(keyName, newInteger(ctx, valueNode))); + continue; + } + // Fall through to check against the long syntax. + } + // Long syntax for limit: + // limits: + // - name: cel.limit.foo + // value: 1 + for (NodeTuple nodeTuple : featureMap.getValue()) { + Node keyNode = nodeTuple.getKeyNode(); + long keyId = ctx.collectMetadata(keyNode); + Node valueNode = nodeTuple.getValueNode(); + String keyName = ((ScalarNode) keyNode).getValue(); + switch (keyName) { + case "name": + name = newString(ctx, valueNode); + break; + case "value": + value = Optional.of(newInteger(ctx, valueNode)); + break; + default: + ctx.reportError(keyId, String.format("Unsupported limits tag: %s", keyName)); + break; + } + } + if (name.isEmpty()) { + ctx.reportError(featureMapId, "Missing required attribute(s): name"); + continue; + } + if (!value.isPresent()) { + ctx.reportError(featureMapId, "Missing required attribute(s): value"); + continue; + } + limits.add(CelEnvironment.Limit.create(name, value.get())); + } + return limits.build(); + } + private ImmutableSet parseAliases(ParserContext ctx, Node node) { ImmutableSet.Builder aliasSetBuilder = ImmutableSet.builder(); long valueId = ctx.collectMetadata(node); @@ -804,6 +869,9 @@ private CelEnvironment.Builder parseConfig(ParserContext ctx, Node node) { case "features": builder.setFeatures(parseFeatures(ctx, valueNode)); break; + case "limits": + builder.setLimits(parseLimits(ctx, valueNode)); + break; default: ctx.reportError(id, "Unknown config tag: " + fieldName); // continue handling the rest of the nodes diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java index 2cc229dc9..179faf2ac 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlSerializer.java @@ -61,6 +61,7 @@ private CelEnvironmentYamlSerializer() { this.multiRepresenters.put(CelEnvironment.Alias.class, new RepresentAlias()); this.multiRepresenters.put(CelContainer.class, new RepresentContainer()); this.multiRepresenters.put(CelEnvironment.FeatureFlag.class, new RepresentFeatureFlag()); + this.multiRepresenters.put(CelEnvironment.Limit.class, new RepresentLimit()); } public static String toYaml(CelEnvironment environment) { @@ -98,6 +99,9 @@ public Node representData(Object data) { if (!environment.features().isEmpty()) { configMap.put("features", environment.features().asList()); } + if (!environment.limits().isEmpty()) { + configMap.put("limits", environment.limits().asList()); + } return represent(configMap.buildOrThrow()); } } @@ -275,4 +279,17 @@ public Node representData(Object data) { .buildOrThrow()); } } + + private final class RepresentLimit implements Represent { + + @Override + public Node representData(Object data) { + CelEnvironment.Limit limit = (CelEnvironment.Limit) data; + return represent( + ImmutableMap.builder() + .put("name", limit.name()) + .put("value", limit.value() < 0 ? -1 : limit.value()) + .buildOrThrow()); + } + } } diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java index d6608a9d4..f70f1d466 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentExporterTest.java @@ -260,5 +260,34 @@ public void container() { assertThat(container.abbreviations()).containsExactly("foo.Bar", "baz.Qux").inOrder(); assertThat(container.aliases()).containsAtLeast("nm", "user.name", "id", "user.id").inOrder(); } + + @Test + public void options() { + Cel cel = + CelFactory.standardCelBuilder() + .setOptions( + CelOptions.current() + .maxExpressionCodePointSize(100) + .maxParseErrorRecoveryLimit(10) + .maxParseRecursionDepth(10) + .enableQuotedIdentifierSyntax(true) + .enableHeterogeneousNumericComparisons(true) + .populateMacroCalls(true) + .build()) + .build(); + + CelEnvironmentExporter exporter = CelEnvironmentExporter.newBuilder().build(); + CelEnvironment celEnvironment = exporter.export(cel); + assertThat(celEnvironment.features()) + .containsExactly( + CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", true), + CelEnvironment.FeatureFlag.create("cel.feature.cross_type_numeric_comparisons", true), + CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true)); + assertThat(celEnvironment.limits()) + .containsExactly( + CelEnvironment.Limit.create("cel.limit.expression_code_points", 100), + CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10), + CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10)); + } } diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java index 3386bdaae..f7eb254d7 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java @@ -124,6 +124,37 @@ public void extend_allFeatureFlags() throws Exception { assertThat(result).isTrue(); } + @Test + public void extend_allLimits() throws Exception { + CelEnvironment environment = + CelEnvironment.newBuilder() + .setLimits( + CelEnvironment.Limit.create("cel.limit.expression_code_points", 20), + CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10), + CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 10)) + .build(); + + Cel cel = + environment.extend( + CelFactory.standardCelBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .build(), + CelOptions.DEFAULT); + CelOptions checkerOptions = cel.toCheckerBuilder().options(); + assertThat(checkerOptions.maxExpressionCodePointSize()).isEqualTo(20); + assertThat(checkerOptions.maxParseErrorRecoveryLimit()).isEqualTo(10); + assertThat(checkerOptions.maxParseRecursionDepth()).isEqualTo(10); + + CelAbstractSyntaxTree ast = cel.compile("1 + 2 + 3 + 4 + 5").getAst(); + Long result = (Long) cel.createProgram(ast).eval(); + assertThat(result).isEqualTo(15L); + + CelValidationResult validationResult = cel.compile("1 + 2 + 3 + 4 + 5 + 6"); + assertThat(validationResult.hasError()).isTrue(); + assertThat(validationResult.getErrorString()) + .contains("expression code point size exceeds limit: size: 21, limit 20"); + } + @Test public void extend_unsupportedFeatureFlag_throws() throws Exception { CelEnvironment environment = @@ -143,6 +174,25 @@ public void extend_unsupportedFeatureFlag_throws() throws Exception { assertThat(e).hasMessageThat().contains("Unknown feature flag: unknown.feature"); } + @Test + public void extend_unsupportedLimit_throws() throws Exception { + CelEnvironment environment = + CelEnvironment.newBuilder() + .setLimits(CelEnvironment.Limit.create("unknown.limit", 5)) + .build(); + + IllegalArgumentException e = + assertThrows( + IllegalArgumentException.class, + () -> + environment.extend( + CelFactory.standardCelBuilder() + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .build(), + CelOptions.DEFAULT)); + assertThat(e).hasMessageThat().contains("Unknown limit: unknown.limit"); + } + @Test public void extensionVersion_specific() throws Exception { CelEnvironment environment = diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java index 98ce55ecc..e98f6110e 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlParserTest.java @@ -108,6 +108,35 @@ public void environment_setFeatures() throws Exception { .build()); } + @Test + public void environment_setLimits() throws Exception { + String yamlConfig = + "name: hello\n" + + "description: empty\n" + + "limits:\n" + + " - name: 'cel.limit.expression_code_points'\n" + + " value: 1000\n" + + " - name: 'cel.limit.parse_error_recovery'\n" + + " value: 10\n" + + " - name: 'cel.limit.parse_recursion_depth'\n" + + " value: 7"; + + CelEnvironment environment = ENVIRONMENT_PARSER.parse(yamlConfig); + + assertThat(environment) + .isEqualTo( + CelEnvironment.newBuilder() + .setSource(environment.source().get()) + .setName("hello") + .setDescription("empty") + .setLimits( + ImmutableSet.of( + CelEnvironment.Limit.create("cel.limit.expression_code_points", 1000), + CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10), + CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 7))) + .build()); + } + @Test public void environment_setExtensions() throws Exception { String yamlConfig = @@ -699,6 +728,30 @@ private enum EnvironmentParseErrorTestcase { "ERROR: :6:7: Unsupported alias tag: unknown_tag\n" + " | unknown_tag: 'test_value'\n" + " | ......^"), + UNSUPPORTED_LIMIT_TAG( + "limits:\n" + + " - name: 'test_limit'\n" + + " unknown_tag: 'test_value'\n" + + " value: 100\n", + "ERROR: :3:5: Unsupported limits tag: unknown_tag\n" + + " | unknown_tag: 'test_value'\n" + + " | ....^"), + MISSING_LIMIT_NAME( + "limits:\n" + " - value: 100\n", + "ERROR: :2:5: Missing required attribute(s): name\n" + + " | - value: 100\n" + + " | ....^"), + MISSING_LIMIT_VALUE( + "limits:\n" + " - name: 'test_limit'\n", + "ERROR: :2:5: Missing required attribute(s): value\n" + + " | - name: 'test_limit'\n" + + " | ....^"), + ILLEGAL_LIMIT_VALUE( + "limits:\n" + " - cel.limit.foo: 'not_a_number'\n", + "ERROR: :2:21: Got yaml node type tag:yaml.org,2002:str, wanted type(s)" + + " [tag:yaml.org,2002:int]\n" + + " | - cel.limit.foo: 'not_a_number'\n" + + " | ....................^"), ILLEGAL_FEATURE_TAG( "features:\n" + " - name: 'test_feature'\n" + " unknown_tag: 'test_value'\n", "ERROR: :3:5: Unsupported feature tag: unknown_tag\n" @@ -831,6 +884,10 @@ private enum EnvironmentYamlResourceTestCase { .setReturnType(TypeDecl.create("bool")) .build()))) .setFeatures(CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true)) + .setLimits( + ImmutableSet.of( + CelEnvironment.Limit.create("cel.limit.expression_code_points", 1000), + CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 7))) .build()), LIBRARY_SUBSET_ENV( diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java index 1c56370b2..0235cb2f4 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentYamlSerializerTest.java @@ -129,6 +129,10 @@ public void toYaml_success() throws Exception { .setFeatures( CelEnvironment.FeatureFlag.create("cel.feature.macro_call_tracking", true), CelEnvironment.FeatureFlag.create("cel.feature.backtick_escape_syntax", false)) + .setLimits( + CelEnvironment.Limit.create("cel.limit.expression_code_points", 1000), + CelEnvironment.Limit.create("cel.limit.parse_error_recovery", 10), + CelEnvironment.Limit.create("cel.limit.parse_recursion_depth", 7)) .build(); String yamlOutput = CelEnvironmentYamlSerializer.toYaml(environment); diff --git a/testing/src/test/resources/environment/dump_env.yaml b/testing/src/test/resources/environment/dump_env.yaml index 18f96fbcc..1e7e7880b 100644 --- a/testing/src/test/resources/environment/dump_env.yaml +++ b/testing/src/test/resources/environment/dump_env.yaml @@ -87,3 +87,10 @@ features: enabled: true - name: cel.feature.backtick_escape_syntax enabled: false +limits: +- name: cel.limit.expression_code_points + value: 1000 +- name: cel.limit.parse_error_recovery + value: 10 +- name: cel.limit.parse_recursion_depth + value: 7 diff --git a/testing/src/test/resources/environment/extended_env.yaml b/testing/src/test/resources/environment/extended_env.yaml index c420ad4db..4763c868f 100644 --- a/testing/src/test/resources/environment/extended_env.yaml +++ b/testing/src/test/resources/environment/extended_env.yaml @@ -41,6 +41,10 @@ functions: features: - name: cel.feature.macro_call_tracking enabled: true +limits: +- name: cel.limit.expression_code_points + value: 1000 +- cel.limit.parse_recursion_depth: 7 # TODO: Add support for below #validators: #- name: cel.validator.duration From 07376918815d16893ec63c24a366540d2e3d5bb7 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Wed, 25 Feb 2026 17:25:55 -0800 Subject: [PATCH 093/100] Add conformance tests for planner PiperOrigin-RevId: 875410045 --- MODULE.bazel | 15 ++-- .../java/dev/cel/bundle/CelEnvironment.java | 4 + .../java/dev/cel/common/types/SimpleType.java | 1 - .../values/BaseProtoCelValueConverter.java | 16 ++++ .../cel/common/values/CelValueConverter.java | 4 + .../test/java/dev/cel/conformance/BUILD.bazel | 80 ++++++++++++++++--- .../dev/cel/conformance/ConformanceTest.java | 46 ++++++++--- .../conformance/ConformanceTestRunner.java | 7 +- .../dev/cel/conformance/conformance_test.bzl | 19 ++--- .../dev/cel/extensions/CelMathExtensions.java | 16 +++- .../cel/extensions/CelRegexExtensions.java | 3 +- .../cel/extensions/CelStringExtensions.java | 3 +- .../src/main/java/dev/cel/runtime/BUILD.bazel | 2 +- .../java/dev/cel/runtime/CelRuntimeImpl.java | 4 +- .../java/dev/cel/runtime/planner/EvalAnd.java | 7 +- .../java/dev/cel/runtime/planner/EvalOr.java | 7 +- .../runtime/planner/NamespacedAttribute.java | 6 +- .../runtime/planner/ProgramPlannerTest.java | 1 - 18 files changed, 183 insertions(+), 58 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 007adcb3c..fd35e41a2 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,18 +16,23 @@ module( name = "cel_java", ) -bazel_dep(name = "bazel_skylib", version = "1.8.2") -bazel_dep(name = "rules_jvm_external", version = "6.9") +bazel_dep(name = "bazel_skylib", version = "1.9.0") +bazel_dep(name = "rules_jvm_external", version = "6.10") bazel_dep(name = "protobuf", version = "33.4", repo_name = "com_google_protobuf") # see https://github.com/bazelbuild/rules_android/issues/373 -bazel_dep(name = "googleapis", version = "0.0.0-20241220-5e258e33.bcr.1", repo_name = "com_google_googleapis") -bazel_dep(name = "rules_pkg", version = "1.0.1") +bazel_dep(name = "googleapis", version = "0.0.0-20260223-edfe7983", repo_name = "com_google_googleapis") +bazel_dep(name = "rules_pkg", version = "1.2.0") bazel_dep(name = "rules_license", version = "1.0.0") bazel_dep(name = "rules_proto", version = "7.1.0") bazel_dep(name = "rules_java", version = "9.3.0") bazel_dep(name = "rules_android", version = "0.7.1") bazel_dep(name = "rules_shell", version = "0.6.1") bazel_dep(name = "googleapis-java", version = "1.0.0") -bazel_dep(name = "cel-spec", version = "0.24.0", repo_name = "cel_spec") +bazel_dep(name = "cel-spec", version = "0.25.1", repo_name = "cel_spec") +bazel_dep(name = "rules_go", version = "0.50.1") + +# Required by cel-spec to satisfy gazelle transitive dependency +go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") +go_sdk.download(version = "1.23.0") switched_rules = use_extension("@com_google_googleapis//:extensions.bzl", "switched_rules") switched_rules.use_languages(java = True) diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java index c8e424217..8614b87b5 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java @@ -672,6 +672,10 @@ public CelType toCelType(CelTypeProvider celTypeProvider) { return TypeParamType.create(name()); } + if (name().equals("dyn")) { + return SimpleType.DYN; + } + CelType simpleType = SimpleType.findByName(name()).orElse(null); if (simpleType != null) { return simpleType; diff --git a/common/src/main/java/dev/cel/common/types/SimpleType.java b/common/src/main/java/dev/cel/common/types/SimpleType.java index 6c43ab53f..93bd5326d 100644 --- a/common/src/main/java/dev/cel/common/types/SimpleType.java +++ b/common/src/main/java/dev/cel/common/types/SimpleType.java @@ -46,7 +46,6 @@ public abstract class SimpleType extends CelType { public static final ImmutableMap TYPE_MAP = ImmutableMap.of( - DYN.name(), DYN, BOOL.name(), BOOL, BYTES.name(), BYTES, DOUBLE.name(), DOUBLE, diff --git a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java index b05a21e24..6851deed5 100644 --- a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -17,6 +17,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; +import com.google.common.base.CaseFormat; +import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -27,6 +29,7 @@ import com.google.protobuf.BytesValue; import com.google.protobuf.DoubleValue; import com.google.protobuf.Duration; +import com.google.protobuf.FieldMask; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; import com.google.protobuf.Int64Value; @@ -41,6 +44,8 @@ import dev.cel.common.annotations.Internal; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.internal.WellKnownProto; +import java.util.ArrayList; +import java.util.List; /** * {@code BaseProtoCelValueConverter} contains the common logic for converting between native Java @@ -98,6 +103,17 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder message, WellKnownProto return UnsignedLong.valueOf(((UInt32Value) message).getValue()); case UINT64_VALUE: return UnsignedLong.fromLongBits(((UInt64Value) message).getValue()); + case FIELD_MASK: + FieldMask fieldMask = (FieldMask) message; + List paths = new ArrayList<>(fieldMask.getPathsCount()); + for (String path : fieldMask.getPathsList()) { + if (!path.isEmpty()) { + paths.add(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, path)); + } + } + return normalizePrimitive(Joiner.on(",").join(paths)); + case EMPTY: + return ImmutableMap.of(); default: throw new UnsupportedOperationException( "Unsupported well known proto conversion - " + wellKnownProto); diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index ae0b40ef7..fda014f31 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -54,6 +54,10 @@ public Object unwrap(CelValue celValue) { return Optional.of(optionalValue.value()); } + if (celValue instanceof ErrorValue) { + return celValue; + } + return celValue.value(); } diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index ab7468e54..3ac7ffcba 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -29,6 +29,7 @@ java_library( "//parser:parser_builder", "//parser:parser_factory", "//runtime", + "//runtime:runtime_planner_impl", "//testing:expr_value_utils", "@cel_spec//proto/cel/expr:expr_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", @@ -57,6 +58,8 @@ java_library( deps = MAVEN_JAR_DEPS + [ "//:java_truth", "//compiler:compiler_builder", + "//parser:parser_factory", + "//runtime:runtime_planner_impl", "//testing:expr_value_utils", "@cel_spec//proto/cel/expr:expr_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", @@ -100,14 +103,8 @@ _ALL_TESTS = [ "@cel_spec//tests/simple:testdata/wrappers.textproto", ] -_TESTS_TO_SKIP = [ - # Tests which require spec changes. - # TODO: Deprecate Duration.get_milliseconds - "timestamps/duration_converters/get_milliseconds", - +_TESTS_TO_SKIP_LEGACY = [ # Broken test cases which should be supported. - # TODO: Invalid bytes to string conversion should error. - "conversions/string/bytes_invalid", # TODO: Support setting / getting enum values out of the defined enum value range. "enums/legacy_proto2/select_big,select_neg", "enums/legacy_proto2/assign_standalone_int_big,assign_standalone_int_neg", @@ -116,7 +113,6 @@ _TESTS_TO_SKIP = [ # TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms. "conversions/int/double_int_min_range", # TODO: Duration and timestamp operations should error on overflow. - "timestamps/duration_range/from_string_under,from_string_over", "timestamps/timestamp_range/sub_time_duration_over,sub_time_duration_under", # TODO: Ensure adding negative duration values is appropriately supported. "timestamps/timestamp_arithmetic/add_time_to_duration_nanos_negative", @@ -159,17 +155,74 @@ _TESTS_TO_SKIP = [ "type_deductions/legacy_nullable_types/null_assignable_to_timestamp_parameter_candidate", ] +_TESTS_TO_SKIP_PLANNER = [ + # TODO: Add strings.format and strings.quote. + "string_ext/quote", + "string_ext/format", + "string_ext/format_errors", + + # TODO: Check behavior for go/cpp + "basic/functions/unbound", + "basic/functions/unbound_is_runtime_error", + + # TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms. + "conversions/int/double_int_min_range", + "enums/legacy_proto3/assign_standalone_int_too_big", + "enums/legacy_proto3/assign_standalone_int_too_neg", + + # TODO: Duration and timestamp operations should error on overflow. + "timestamps/timestamp_range/sub_time_duration_over", + "timestamps/timestamp_range/sub_time_duration_under", + + # Skip until fixed. + "fields/qualified_identifier_resolution/map_key_float", + "fields/qualified_identifier_resolution/map_key_null", + "fields/qualified_identifier_resolution/map_value_repeat_key_heterogeneous", + "optionals/optionals/map_null_entry_no_such_key", + "optionals/optionals/map_present_key_invalid_field", + "parse/receiver_function_names", + "proto2/extensions_get/package_scoped_test_all_types_ext", + "proto2/extensions_get/package_scoped_repeated_test_all_types", + "proto2/extensions_get/message_scoped_nested_ext", + "proto2/extensions_get/message_scoped_repeated_test_all_types", + "proto2_ext/get_ext/package_scoped_repeated_test_all_types", + "proto2_ext/get_ext/message_scoped_repeated_test_all_types", + + # TODO: Fix null assignment to a field + "proto2/set_null/single_message", + "proto2/set_null/single_duration", + "proto2/set_null/single_timestamp", + "proto3/set_null/single_message", + "proto3/set_null/single_duration", + "proto3/set_null/single_timestamp", + + # Type inference edgecases around null(able) assignability. + # These type check, but resolve to a different type. + # list(int), want list(wrapper(int)) + "type_deductions/wrappers/wrapper_promotion", + # list(null), want list(Message) + "type_deductions/legacy_nullable_types/null_assignable_to_message_parameter_candidate", + "type_deductions/legacy_nullable_types/null_assignable_to_abstract_parameter_candidate", + "type_deductions/legacy_nullable_types/null_assignable_to_duration_parameter_candidate", + "type_deductions/legacy_nullable_types/null_assignable_to_timestamp_parameter_candidate", + + # Future features for CEL 1.0 + # TODO: Strong typing support for enums, specified but not implemented. + "enums/strong_proto2", + "enums/strong_proto3", +] + conformance_test( name = "conformance", data = _ALL_TESTS, - skip_tests = _TESTS_TO_SKIP, + skip_tests = _TESTS_TO_SKIP_LEGACY, ) conformance_test( name = "conformance_maven", data = _ALL_TESTS, mode = MODE.MAVEN_TEST, - skip_tests = _TESTS_TO_SKIP, + skip_tests = _TESTS_TO_SKIP_LEGACY, ) conformance_test( @@ -177,3 +230,10 @@ conformance_test( data = _ALL_TESTS, mode = MODE.DASHBOARD, ) + +conformance_test( + name = "conformance_planner", + data = _ALL_TESTS, + skip_tests = _TESTS_TO_SKIP_PLANNER, + use_planner = True, +) diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java index dcd226fa5..437e50fea 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java @@ -45,7 +45,9 @@ import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntime.Program; +import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.CelRuntimeFactory; +import dev.cel.runtime.CelRuntimeImpl; import dev.cel.runtime.CelRuntimeLibrary; import java.util.Map; import org.junit.runners.model.Statement; @@ -118,15 +120,25 @@ private static CelChecker getChecker(SimpleTest test) throws Exception { .build(); } - private static final CelRuntime RUNTIME = - CelRuntimeFactory.standardCelRuntimeBuilder() - .setOptions(OPTIONS) - .addLibraries(CANONICAL_RUNTIME_EXTENSIONS) - .setExtensionRegistry(DEFAULT_EXTENSION_REGISTRY) - .addMessageTypes(dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor()) - .addMessageTypes(dev.cel.expr.conformance.proto3.TestAllTypes.getDescriptor()) - .addFileTypes(dev.cel.expr.conformance.proto2.TestAllTypesExtensions.getDescriptor()) - .build(); + private static CelRuntime getRuntime(SimpleTest test, boolean usePlanner) { + CelRuntimeBuilder builder = + usePlanner ? CelRuntimeImpl.newBuilder() : CelRuntimeFactory.standardCelRuntimeBuilder(); + + builder + // CEL-Internal-2 + .setOptions(OPTIONS) + .addLibraries(CANONICAL_RUNTIME_EXTENSIONS) + .setExtensionRegistry(DEFAULT_EXTENSION_REGISTRY) + .addMessageTypes(dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor()) + .addMessageTypes(dev.cel.expr.conformance.proto3.TestAllTypes.getDescriptor()) + .addFileTypes(dev.cel.expr.conformance.proto2.TestAllTypesExtensions.getDescriptor()); + + if (usePlanner) { + builder.setContainer(CelContainer.ofName(test.getContainer())); + } + + return builder.build(); + } private static ImmutableMap getBindings(SimpleTest test) throws Exception { ImmutableMap.Builder bindings = @@ -157,13 +169,15 @@ private static SimpleTest defaultTestMatcherToTrueIfUnset(SimpleTest test) { private final String name; private final SimpleTest test; private final boolean skip; + private final boolean usePlanner; - public ConformanceTest(String name, SimpleTest test, boolean skip) { + public ConformanceTest(String name, SimpleTest test, boolean skip, boolean usePlanner) { this.name = Preconditions.checkNotNull(name); this.test = Preconditions.checkNotNull( defaultTestMatcherToTrueIfUnset(Preconditions.checkNotNull(test))); this.skip = skip; + this.usePlanner = usePlanner; } public String getName() { @@ -178,7 +192,9 @@ public boolean shouldSkip() { public void evaluate() throws Throwable { CelValidationResult response = getParser(test).parse(test.getExpr(), test.getName()); assertThat(response.hasError()).isFalse(); - response = getChecker(test).check(response.getAst()); + if (!test.getDisableCheck()) { + response = getChecker(test).check(response.getAst()); + } assertThat(response.hasError()).isFalse(); Type resultType = CelProtoTypes.celTypeToType(response.getAst().getResultType()); @@ -188,7 +204,13 @@ public void evaluate() throws Throwable { return; } - Program program = RUNTIME.createProgram(response.getAst()); + if (!usePlanner && test.getDisableCheck()) { + // Only planner supports parsed-only evaluation + return; + } + + CelRuntime runtime = getRuntime(test, usePlanner); + Program program = runtime.createProgram(response.getAst()); ExprValue result = null; CelEvaluationException error = null; try { diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java index 89598ed3e..dc3d5021e 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java @@ -43,6 +43,7 @@ public final class ConformanceTestRunner extends ParentRunner { private final ImmutableSortedMap testFiles; private final ImmutableList testsToSkip; + private final boolean usePlanner; private static ImmutableSortedMap loadTestFiles() { List testPaths = @@ -75,6 +76,9 @@ public ConformanceTestRunner(Class clazz) throws InitializationError { ImmutableList.copyOf( SPLITTER.splitToList( System.getProperty("dev.cel.conformance.ConformanceTests.skip_tests"))); + usePlanner = + Boolean.parseBoolean( + System.getProperty("dev.cel.conformance.ConformanceTests.use_planner", "false")); } private boolean shouldSkipTest(String name) { @@ -97,8 +101,7 @@ protected List getChildren() { for (SimpleTest test : testSection.getTestList()) { String name = String.format("%s/%s/%s", testFile.getName(), testSection.getName(), test.getName()); - tests.add( - new ConformanceTest(name, test, test.getDisableCheck() || shouldSkipTest(name))); + tests.add(new ConformanceTest(name, test, shouldSkipTest(name), usePlanner)); } } } diff --git a/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl b/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl index f884a2a84..b91ce4a8b 100644 --- a/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl +++ b/conformance/src/test/java/dev/cel/conformance/conformance_test.bzl @@ -38,11 +38,12 @@ def _expand_tests_to_skip(tests_to_skip): result.append(test_to_skip[0:slash] + part) return result -def _conformance_test_args(data, skip_tests): - args = [] - args.append("-Ddev.cel.conformance.ConformanceTests.skip_tests={}".format(",".join(_expand_tests_to_skip(skip_tests)))) - args.append("-Ddev.cel.conformance.ConformanceTests.tests={}".format(",".join(["$(location " + test + ")" for test in data]))) - return args +def _conformance_test_args(data, skip_tests, use_planner): + return [ + "-Ddev.cel.conformance.ConformanceTests.skip_tests={}".format(",".join(_expand_tests_to_skip(skip_tests))), + "-Ddev.cel.conformance.ConformanceTests.tests={}".format(",".join(["$(location {})".format(t) for t in data])), + "-Ddev.cel.conformance.ConformanceTests.use_planner={}".format("true" if use_planner else "false"), + ] MODE = struct( # Standard test execution against HEAD @@ -53,7 +54,7 @@ MODE = struct( DASHBOARD = "dashboard", ) -def conformance_test(name, data, mode = MODE.TEST, skip_tests = []): +def conformance_test(name, data, mode = MODE.TEST, skip_tests = [], use_planner = False): """Executes conformance tests Args: @@ -69,7 +70,7 @@ def conformance_test(name, data, mode = MODE.TEST, skip_tests = []): if mode == MODE.DASHBOARD: java_test( name = "_" + name, - jvm_flags = _conformance_test_args(data, skip_tests), + jvm_flags = _conformance_test_args(data, skip_tests, use_planner), data = data, size = "small", test_class = "dev.cel.conformance.ConformanceTests", @@ -95,7 +96,7 @@ def conformance_test(name, data, mode = MODE.TEST, skip_tests = []): elif mode == MODE.TEST: java_test( name = name, - jvm_flags = _conformance_test_args(data, skip_tests), + jvm_flags = _conformance_test_args(data, skip_tests, use_planner), data = data, size = "small", test_class = "dev.cel.conformance.ConformanceTests", @@ -104,7 +105,7 @@ def conformance_test(name, data, mode = MODE.TEST, skip_tests = []): elif mode == MODE.MAVEN_TEST: java_test( name = name, - jvm_flags = _conformance_test_args(data, skip_tests), + jvm_flags = _conformance_test_args(data, skip_tests, use_planner), data = data, size = "small", test_class = "dev.cel.conformance.ConformanceTests", diff --git a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java index 57c8c1378..22336eb22 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelMathExtensions.java @@ -663,9 +663,19 @@ String getFunction() { ImmutableSet functionBindingsULongSigned, ImmutableSet functionBindingsULongUnsigned) { this.functionDecl = functionDecl; - this.functionBindings = functionBindings; - this.functionBindingsULongSigned = functionBindingsULongSigned; - this.functionBindingsULongUnsigned = functionBindingsULongUnsigned; + this.functionBindings = + functionBindings.isEmpty() + ? ImmutableSet.of() + : CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindings); + this.functionBindingsULongSigned = + functionBindingsULongSigned.isEmpty() + ? ImmutableSet.of() + : CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindingsULongSigned); + this.functionBindingsULongUnsigned = + functionBindingsULongUnsigned.isEmpty() + ? ImmutableSet.of() + : CelFunctionBinding.fromOverloads( + functionDecl.name(), functionBindingsULongUnsigned); } } diff --git a/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java index f1ed3b478..564422cd4 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelRegexExtensions.java @@ -123,7 +123,8 @@ String getFunction() { Function(CelFunctionDecl functionDecl, ImmutableSet functionBindings) { this.functionDecl = functionDecl; - this.functionBindings = functionBindings; + this.functionBindings = + CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindings); } } diff --git a/extensions/src/main/java/dev/cel/extensions/CelStringExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelStringExtensions.java index 37b2a368b..10caa7db8 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelStringExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelStringExtensions.java @@ -238,7 +238,8 @@ String getFunction() { Function(CelFunctionDecl functionDecl, CelFunctionBinding... functionBindings) { this.functionDecl = functionDecl; - this.functionBindings = ImmutableSet.copyOf(functionBindings); + this.functionBindings = + CelFunctionBinding.fromOverloads(functionDecl.name(), functionBindings); } } diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 0746a5b83..70568cd90 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -827,7 +827,7 @@ java_library( ":function_binding", ":function_resolver", ":program", - ":proto_message_runtime_helpers", + ":proto_message_runtime_equality", ":runtime", ":runtime_equality", ":standard_functions", diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java index e910c77a9..346b25ae9 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java @@ -424,9 +424,7 @@ public CelRuntime build() { CombinedCelValueProvider.combine(protoMessageValueProvider, valueProvider()); } - RuntimeEquality runtimeEquality = - RuntimeEquality.create( - ProtoMessageRuntimeHelpers.create(dynamicProto, options()), options()); + RuntimeEquality runtimeEquality = ProtoMessageRuntimeEquality.create(dynamicProto, options()); ImmutableSet runtimeLibraries = runtimeLibrariesBuilder().build(); // Add libraries, such as extensions for (CelRuntimeLibrary celLibrary : runtimeLibraries) { diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java index b09191e9f..763f8faba 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java @@ -39,8 +39,11 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) { errorValue = (ErrorValue) argVal; } else { // TODO: Handle unknowns - throw new IllegalArgumentException( - String.format("Expected boolean value, found: %s", argVal)); + errorValue = + ErrorValue.create( + arg.exprId(), + new IllegalArgumentException( + String.format("Expected boolean value, found: %s", argVal))); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java index 8c8f5954d..22fc56a7f 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalOr.java @@ -39,8 +39,11 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) { errorValue = (ErrorValue) argVal; } else { // TODO: Handle unknowns - throw new IllegalArgumentException( - String.format("Expected boolean value, found: %s", argVal)); + errorValue = + ErrorValue.create( + arg.exprId(), + new IllegalArgumentException( + String.format("Expected boolean value, found: %s", argVal))); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index 6bdf0c072..de1a90291 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -62,11 +62,7 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { Object value = resolver.resolve(name); if (value != null) { - if (!qualifiers.isEmpty()) { - return applyQualifiers(value, celValueConverter, qualifiers); - } else { - return value; - } + return applyQualifiers(value, celValueConverter, qualifiers); } // Attempt to resolve the qualify type name if the name is not a variable identifier diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 33500f217..7bdcaaac1 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -1055,7 +1055,6 @@ private enum TypeLiteralTestCase { INT("int", SimpleType.INT), UINT("uint", SimpleType.UINT), STRING("string", SimpleType.STRING), - DYN("dyn", SimpleType.DYN), LIST("list", ListType.create(SimpleType.DYN)), MAP("map", MapType.create(SimpleType.DYN, SimpleType.DYN)), NULL("null_type", SimpleType.NULL_TYPE), From 941932c0f10ee29711cd156c962a8e601501e445 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 26 Feb 2026 09:09:59 -0800 Subject: [PATCH 094/100] Prevent planning createMap for unsupported keys PiperOrigin-RevId: 875751638 --- .../exceptions/CelInvalidArgumentException.java | 4 ++++ .../test/java/dev/cel/conformance/BUILD.bazel | 2 -- .../java/dev/cel/runtime/planner/BUILD.bazel | 1 + .../dev/cel/runtime/planner/EvalCreateMap.java | 17 +++++++++++++++-- .../cel/runtime/planner/ProgramPlannerTest.java | 11 +++++++++++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java index 41358bb79..6a2b4ab72 100644 --- a/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java +++ b/common/src/main/java/dev/cel/common/exceptions/CelInvalidArgumentException.java @@ -24,4 +24,8 @@ public final class CelInvalidArgumentException extends CelRuntimeException { public CelInvalidArgumentException(Throwable cause) { super(cause, CelErrorCode.INVALID_ARGUMENT); } + + public CelInvalidArgumentException(String message) { + super(message, CelErrorCode.INVALID_ARGUMENT); + } } diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index 3ac7ffcba..375cd8bfa 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -175,8 +175,6 @@ _TESTS_TO_SKIP_PLANNER = [ "timestamps/timestamp_range/sub_time_duration_under", # Skip until fixed. - "fields/qualified_identifier_resolution/map_key_float", - "fields/qualified_identifier_resolution/map_key_null", "fields/qualified_identifier_resolution/map_value_repeat_key_heterogeneous", "optionals/optionals/map_null_entry_no_such_key", "optionals/optionals/map_present_key_invalid_field", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index b827afed5..f7912cff2 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -334,6 +334,7 @@ java_library( ":localized_evaluation_exception", ":planned_interpretable", "//common/exceptions:duplicate_key", + "//common/exceptions:invalid_argument", "//runtime:evaluation_exception", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java index 1ab0f7e5b..c09c19987 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -17,8 +17,10 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; +import com.google.common.primitives.UnsignedLong; import com.google.errorprone.annotations.Immutable; import dev.cel.common.exceptions.CelDuplicateKeyException; +import dev.cel.common.exceptions.CelInvalidArgumentException; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.GlobalResolver; import java.util.HashSet; @@ -46,11 +48,22 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval HashSet keysSeen = Sets.newHashSetWithExpectedSize(keys.length); for (int i = 0; i < keys.length; i++) { - Object key = keys[i].eval(resolver, frame); + PlannedInterpretable keyInterpretable = keys[i]; + Object key = keyInterpretable.eval(resolver, frame); + if (!(key instanceof String + || key instanceof Long + || key instanceof UnsignedLong + || key instanceof Boolean)) { + throw new LocalizedEvaluationException( + new CelInvalidArgumentException("Unsupported key type: " + key), + keyInterpretable.exprId()); + } + Object val = values[i].eval(resolver, frame); if (!keysSeen.add(key)) { - throw new LocalizedEvaluationException(CelDuplicateKeyException.of(key), keys[i].exprId()); + throw new LocalizedEvaluationException( + CelDuplicateKeyException.of(key), keyInterpretable.exprId()); } if (isOptional[i]) { diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 7bdcaaac1..30b100bc2 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -390,6 +390,17 @@ public void plan_createMap_containsDuplicateKey_throws() throws Exception { .contains("evaluation error at :20: duplicate map key [true]"); } + @Test + public void plan_createMap_unsupportedKeyType_throws() throws Exception { + CelAbstractSyntaxTree ast = compile("{1.0: 'foo'}"); + Program program = PLANNER.plan(ast); + + CelEvaluationException e = assertThrows(CelEvaluationException.class, program::eval); + assertThat(e) + .hasMessageThat() + .contains("evaluation error at :1: Unsupported key type: 1.0"); + } + @Test public void plan_createStruct() throws Exception { CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{}"); From 3cebc1a61eef4f0858e99271f18458eb4b2aa43d Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 26 Feb 2026 13:36:58 -0800 Subject: [PATCH 095/100] Expose planner based runtimes through experimental factories PiperOrigin-RevId: 875869202 --- bundle/BUILD.bazel | 8 +++ .../src/main/java/dev/cel/bundle/BUILD.bazel | 18 +++++- .../cel/bundle/CelExperimentalFactory.java | 61 +++++++++++++++++++ .../src/main/java/dev/cel/bundle/CelImpl.java | 13 +--- .../test/java/dev/cel/extensions/BUILD.bazel | 5 +- .../extensions/CelOptionalLibraryTest.java | 19 +----- runtime/BUILD.bazel | 8 +++ .../src/main/java/dev/cel/runtime/BUILD.bazel | 13 ++++ .../CelRuntimeExperimentalFactory.java | 52 ++++++++++++++++ .../src/test/java/dev/cel/runtime/BUILD.bazel | 2 +- .../cel/runtime/PlannerInterpreterTest.java | 3 +- 11 files changed, 166 insertions(+), 36 deletions(-) create mode 100644 bundle/src/main/java/dev/cel/bundle/CelExperimentalFactory.java create mode 100644 runtime/src/main/java/dev/cel/runtime/CelRuntimeExperimentalFactory.java diff --git a/bundle/BUILD.bazel b/bundle/BUILD.bazel index 1eaf0bec8..11e0b8a6d 100644 --- a/bundle/BUILD.bazel +++ b/bundle/BUILD.bazel @@ -13,6 +13,14 @@ java_library( ], ) +java_library( + name = "cel_experimental_factory", + visibility = ["//:internal"], + exports = [ + "//bundle/src/main/java/dev/cel/bundle:cel_experimental_factory", + ], +) + java_library( name = "environment", exports = ["//bundle/src/main/java/dev/cel/bundle:environment"], diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel index 0a014ec73..822511e4c 100644 --- a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel @@ -57,6 +57,23 @@ java_library( ], ) +java_library( + name = "cel_experimental_factory", + srcs = ["CelExperimentalFactory.java"], + tags = [ + ], + deps = [ + ":cel", + ":cel_impl", + "//checker", + "//common:options", + "//common/annotations", + "//compiler", + "//parser", + "//runtime:runtime_planner_impl", + ], +) + java_library( name = "cel_impl", srcs = ["CelImpl.java"], @@ -73,7 +90,6 @@ java_library( "//common:compiler_common", "//common:container", "//common:options", - "//common/annotations", "//common/internal:env_visitor", "//common/internal:file_descriptor_converter", "//common/types:cel_proto_types", diff --git a/bundle/src/main/java/dev/cel/bundle/CelExperimentalFactory.java b/bundle/src/main/java/dev/cel/bundle/CelExperimentalFactory.java new file mode 100644 index 000000000..2275d1c56 --- /dev/null +++ b/bundle/src/main/java/dev/cel/bundle/CelExperimentalFactory.java @@ -0,0 +1,61 @@ +// 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.bundle; + +import dev.cel.checker.CelCheckerLegacyImpl; +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Beta; +import dev.cel.compiler.CelCompilerImpl; +import dev.cel.parser.CelParserImpl; +import dev.cel.runtime.CelRuntimeImpl; + +/** + * Experimental helper class to configure the entire CEL stack in a common interface, backed by the + * new {@code ProgramPlanner} architecture. + * + *

    All APIs and behaviors surfaced here are subject to change. + */ +@Beta +public final class CelExperimentalFactory { + + /** + * Creates a builder for configuring CEL for the parsing, optional type-checking, and evaluation + * of expressions using the Program Planner. + * + *

    The {@code ProgramPlanner} architecture provides key benefits over the legacy runtime: + * + *

      + *
    • Performance: Programs can be cached for improving evaluation speed. + *
    • Parsed-only expression evaluation: Unlike the traditional stack which required + * supplying type-checked expressions, this architecture handles both parsed-only and + * type-checked expressions. + *
    + */ + public static CelBuilder plannerCelBuilder() { + return CelImpl.newBuilder( + CelCompilerImpl.newBuilder( + CelParserImpl.newBuilder(), + CelCheckerLegacyImpl.newBuilder().setStandardEnvironmentEnabled(true)), + CelRuntimeImpl.newBuilder()) + // CEL-Internal-2 + .setOptions( + CelOptions.current() + .enableHeterogeneousNumericComparisons(true) + .enableTimestampEpoch(true) + .build()); + } + + private CelExperimentalFactory() {} +} diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java index 51fe2dc38..ae0ab2395 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java +++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java @@ -38,7 +38,6 @@ import dev.cel.common.CelSource; import dev.cel.common.CelValidationResult; import dev.cel.common.CelVarDecl; -import dev.cel.common.annotations.Internal; import dev.cel.common.internal.EnvVisitable; import dev.cel.common.internal.EnvVisitor; import dev.cel.common.internal.FileDescriptorSetConverter; @@ -65,14 +64,9 @@ * Implementation of the synchronous CEL stack. * *

    Note, the underlying {@link CelCompiler} and {@link CelRuntime} values are constructed lazily. - * - *

    CEL Library Internals. Do Not Use. Consumers should use {@code CelFactory} instead. - * - *

    TODO: Restrict visibility once factory is introduced */ @Immutable -@Internal -public final class CelImpl implements Cel, EnvVisitable { +final class CelImpl implements Cel, EnvVisitable { // The lazily constructed compiler and runtime values are memoized and guaranteed to be // constructed only once without side effects, thus making them effectively immutable. @@ -151,11 +145,8 @@ static CelImpl combine(CelCompiler compiler, CelRuntime runtime) { *

    By default, {@link CelOptions#DEFAULT} are enabled, as is the CEL standard environment. * *

    CEL Library Internals. Do Not Use. Consumers should use {@code CelFactory} instead. - * - *

    TODO: Restrict visibility once factory is introduced */ - @Internal - public static CelBuilder newBuilder( + static CelBuilder newBuilder( CelCompilerBuilder compilerBuilder, CelRuntimeBuilder celRuntimeBuilder) { return new CelImpl.Builder(compilerBuilder, celRuntimeBuilder); } diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel index b441c33cf..48915fd02 100644 --- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel +++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel @@ -10,8 +10,7 @@ java_library( deps = [ "//:java_truth", "//bundle:cel", - "//bundle:cel_impl", - "//checker", + "//bundle:cel_experimental_factory", "//common:cel_ast", "//common:compiler_common", "//common:container", @@ -32,7 +31,6 @@ java_library( "//extensions:sets", "//extensions:sets_function", "//extensions:strings", - "//parser", "//parser:macro", "//parser:unparser", "//runtime", @@ -40,7 +38,6 @@ java_library( "//runtime:interpreter_util", "//runtime:lite_runtime", "//runtime:lite_runtime_factory", - "//runtime:runtime_planner_impl", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto", "@cel_spec//proto/cel/expr/conformance/test:simple_java_proto", diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index 0f0c649f8..24e9d6d86 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -26,9 +26,8 @@ import com.google.testing.junit.testparameterinjector.TestParameters; import dev.cel.bundle.Cel; import dev.cel.bundle.CelBuilder; +import dev.cel.bundle.CelExperimentalFactory; import dev.cel.bundle.CelFactory; -import dev.cel.bundle.CelImpl; -import dev.cel.checker.CelCheckerLegacyImpl; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; import dev.cel.common.CelFunctionDecl; @@ -46,16 +45,13 @@ import dev.cel.common.values.CelByteString; import dev.cel.common.values.NullValue; import dev.cel.compiler.CelCompiler; -import dev.cel.compiler.CelCompilerImpl; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage; import dev.cel.parser.CelMacro; -import dev.cel.parser.CelParserImpl; import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelEvaluationException; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; -import dev.cel.runtime.CelRuntimeImpl; import dev.cel.runtime.InterpreterUtil; import java.time.Duration; import java.time.Instant; @@ -106,17 +102,6 @@ private enum ConstantTestCases { } } - private static CelBuilder plannerCelBuilder() { - // TODO: Replace with factory once available. - return CelImpl.newBuilder( - CelCompilerImpl.newBuilder( - CelParserImpl.newBuilder(), - CelCheckerLegacyImpl.newBuilder().setStandardEnvironmentEnabled(true)), - CelRuntimeImpl.newBuilder()) - // CEL-Internal-2 - .setOptions(CelOptions.current().build()); - } - private CelBuilder newCelBuilder() { return newCelBuilder(Integer.MAX_VALUE); } @@ -126,7 +111,7 @@ private CelBuilder newCelBuilder(int version) { switch (testMode) { case PLANNER_PARSE_ONLY: case PLANNER_CHECKED: - celBuilder = plannerCelBuilder(); + celBuilder = CelExperimentalFactory.plannerCelBuilder(); break; case LEGACY_CHECKED: celBuilder = CelFactory.standardCelBuilder(); diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel index 72ec02d12..55ee241a0 100644 --- a/runtime/BUILD.bazel +++ b/runtime/BUILD.bazel @@ -29,6 +29,14 @@ java_library( ], ) +java_library( + name = "runtime_experimental_factory", + visibility = ["//:internal"], + exports = [ + "//runtime/src/main/java/dev/cel/runtime:runtime_experimental_factory", + ], +) + java_library( name = "runtime_legacy_impl", visibility = ["//:internal"], diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 70568cd90..10dca9ece 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -912,6 +912,19 @@ java_library( ], ) +java_library( + name = "runtime_experimental_factory", + srcs = ["CelRuntimeExperimentalFactory.java"], + tags = [ + ], + deps = [ + ":runtime", + ":runtime_planner_impl", + "//common:options", + "//common/annotations", + ], +) + java_library( name = "runtime", srcs = RUNTIME_SOURCES, diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeExperimentalFactory.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeExperimentalFactory.java new file mode 100644 index 000000000..d0089e48d --- /dev/null +++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeExperimentalFactory.java @@ -0,0 +1,52 @@ +// 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.runtime; + +import dev.cel.common.CelOptions; +import dev.cel.common.annotations.Beta; + +/** + * Experimental helper class to construct new {@code CelRuntime} instances backed by the new {@code + * ProgramPlanner} architecture. + * + *

    All APIs and behaviors surfaced here are subject to change. + */ +@Beta +public final class CelRuntimeExperimentalFactory { + + /** + * Create a new builder for constructing a {@code CelRuntime} instance. + * + *

    The {@code ProgramPlanner} architecture provides key benefits over the legacy runtime: + * + *

      + *
    • Performance: Programs can be cached for improving evaluation speed. + *
    • Parsed-only expression evaluation: Unlike the traditional legacy runtime, which + * only supported evaluating type-checked expressions, this architecture handles both + * parsed-only and type-checked expressions. + *
    + */ + public static CelRuntimeBuilder plannerRuntimeBuilder() { + return CelRuntimeImpl.newBuilder() + // CEL-Internal-2 + .setOptions( + CelOptions.current() + .enableTimestampEpoch(true) + .enableHeterogeneousNumericComparisons(true) + .build()); + } + + private CelRuntimeExperimentalFactory() {} +} diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 569d7372d..8a0b1f9de 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -138,7 +138,7 @@ java_library( "//common/types:type_providers", "//extensions", "//runtime", - "//runtime:runtime_planner_impl", + "//runtime:runtime_experimental_factory", "//testing:base_interpreter_test", "@maven//:com_google_testparameterinjector_test_parameter_injector", "@maven//:junit_junit", diff --git a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java index 7518951c7..3254855c7 100644 --- a/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/PlannerInterpreterTest.java @@ -33,9 +33,8 @@ public class PlannerInterpreterTest extends BaseInterpreterTest { @Override protected CelRuntimeBuilder newBaseRuntimeBuilder(CelOptions celOptions) { - return CelRuntimeImpl.newBuilder() + return CelRuntimeExperimentalFactory.plannerRuntimeBuilder() .addLateBoundFunctions("record") - // CEL-Internal-2 .setOptions(celOptions) .addLibraries(CelExtensions.optional()) .addFileTypes(TEST_FILE_DESCRIPTORS); From d499ba8e36e974e59e4b36d74299d5ef56f1399f Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 26 Feb 2026 15:16:48 -0800 Subject: [PATCH 096/100] Prevent planning createMap for heterogeneous duplicate keys PiperOrigin-RevId: 875912898 --- .../test/java/dev/cel/conformance/BUILD.bazel | 1 - .../dev/cel/runtime/planner/EvalCreateMap.java | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index 375cd8bfa..c8cc06cb0 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -175,7 +175,6 @@ _TESTS_TO_SKIP_PLANNER = [ "timestamps/timestamp_range/sub_time_duration_under", # Skip until fixed. - "fields/qualified_identifier_resolution/map_value_repeat_key_heterogeneous", "optionals/optionals/map_null_entry_no_such_key", "optionals/optionals/map_present_key_invalid_field", "parse/receiver_function_names", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java index c09c19987..f6f73e842 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateMap.java @@ -59,13 +59,26 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval keyInterpretable.exprId()); } - Object val = values[i].eval(resolver, frame); + boolean isDuplicate = !keysSeen.add(key); + if (!isDuplicate) { + if (key instanceof Long) { + long longVal = (Long) key; + if (longVal >= 0) { + isDuplicate = keysSeen.contains(UnsignedLong.valueOf(longVal)); + } + } else if (key instanceof UnsignedLong) { + UnsignedLong ulongVal = (UnsignedLong) key; + isDuplicate = keysSeen.contains(ulongVal.longValue()); + } + } - if (!keysSeen.add(key)) { + if (isDuplicate) { throw new LocalizedEvaluationException( CelDuplicateKeyException.of(key), keyInterpretable.exprId()); } + Object val = values[i].eval(resolver, frame); + if (isOptional[i]) { if (!(val instanceof Optional)) { throw new IllegalArgumentException( From 5103b301547226801c41c158dea4ee751bfb5a7d Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Thu, 26 Feb 2026 16:15:47 -0800 Subject: [PATCH 097/100] Fix optionals to properly error on invalid qualification PiperOrigin-RevId: 875937520 --- .../test/java/dev/cel/conformance/BUILD.bazel | 2 -- .../cel/runtime/planner/StringQualifier.java | 32 +++++++++++++------ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index c8cc06cb0..717f7aaa0 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -175,8 +175,6 @@ _TESTS_TO_SKIP_PLANNER = [ "timestamps/timestamp_range/sub_time_duration_under", # Skip until fixed. - "optionals/optionals/map_null_entry_no_such_key", - "optionals/optionals/map_present_key_invalid_field", "parse/receiver_function_names", "proto2/extensions_get/package_scoped_test_all_types_ext", "proto2/extensions_get/package_scoped_repeated_test_all_types", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java index 4ceaa0e51..293ca5c7d 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/StringQualifier.java @@ -15,6 +15,7 @@ package dev.cel.runtime.planner; import dev.cel.common.exceptions.CelAttributeNotFoundException; +import dev.cel.common.values.OptionalValue; import dev.cel.common.values.SelectableValue; import java.util.Map; @@ -31,21 +32,34 @@ public String value() { @Override @SuppressWarnings("unchecked") // Qualifications on maps/structs must be a string public Object qualify(Object obj) { + if (obj instanceof OptionalValue) { + OptionalValue opt = (OptionalValue) obj; + if (!opt.isZeroValue()) { + Object inner = opt.value(); + if (!(inner instanceof SelectableValue) && !(inner instanceof Map)) { + throw CelAttributeNotFoundException.forFieldResolution(value); + } + } + } + if (obj instanceof SelectableValue) { return ((SelectableValue) obj).select(value); - } else if (obj instanceof Map) { - Map map = (Map) obj; - if (!map.containsKey(value)) { - throw CelAttributeNotFoundException.forMissingMapKey(value); - } + } + if (obj instanceof Map) { + Map map = (Map) obj; Object mapVal = map.get(value); - if (mapVal == null) { - throw CelAttributeNotFoundException.of( - String.format("Map value cannot be null for key: %s", value)); + if (mapVal != null) { + return mapVal; + } + + if (!map.containsKey(value)) { + throw CelAttributeNotFoundException.forMissingMapKey(value); } - return map.get(value); + + throw CelAttributeNotFoundException.of( + String.format("Map value cannot be null for key: %s", value)); } throw CelAttributeNotFoundException.forFieldResolution(value); From 9e1b5eeda3b7fb89b792eaee5d8d339585267676 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 27 Feb 2026 17:26:41 -0800 Subject: [PATCH 098/100] Fix CelValue adaptation for lists/maps PiperOrigin-RevId: 876477992 --- .../cel/common/values/CelValueConverter.java | 57 +++++++++++++++---- .../common/values/CelValueConverterTest.java | 5 +- .../values/ProtoCelValueConverterTest.java | 2 +- .../test/java/dev/cel/conformance/BUILD.bazel | 6 -- .../runtime/CelValueRuntimeTypeProvider.java | 19 +------ .../java/dev/cel/runtime/planner/BUILD.bazel | 2 - .../dev/cel/runtime/planner/EvalHelpers.java | 19 +++---- .../runtime/planner/NamespacedAttribute.java | 7 +-- .../runtime/planner/RelativeAttribute.java | 6 +- .../runtime/planner/ProgramPlannerTest.java | 29 ++++++++++ 10 files changed, 92 insertions(+), 60 deletions(-) diff --git a/common/src/main/java/dev/cel/common/values/CelValueConverter.java b/common/src/main/java/dev/cel/common/values/CelValueConverter.java index fda014f31..2af0a76cb 100644 --- a/common/src/main/java/dev/cel/common/values/CelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/CelValueConverter.java @@ -41,24 +41,39 @@ public static CelValueConverter getDefaultInstance() { return DEFAULT_INSTANCE; } - /** Adapts a {@link CelValue} to a plain old Java Object. */ - public Object unwrap(CelValue celValue) { - Preconditions.checkNotNull(celValue); + /** + * Unwraps the {@code value} into its plain old Java Object representation. + * + *

    The value may be a {@link CelValue}, a {@link Collection} or a {@link Map}. + */ + public Object maybeUnwrap(Object value) { + if (value instanceof CelValue) { + return unwrap((CelValue) value); + } - if (celValue instanceof OptionalValue) { - OptionalValue optionalValue = (OptionalValue) celValue; - if (optionalValue.isZeroValue()) { - return Optional.empty(); + if (value instanceof Collection) { + Collection collection = (Collection) value; + ImmutableList.Builder builder = + ImmutableList.builderWithExpectedSize(collection.size()); + for (Object element : collection) { + builder.add(maybeUnwrap(element)); } - return Optional.of(optionalValue.value()); + return builder.build(); } - if (celValue instanceof ErrorValue) { - return celValue; + if (value instanceof Map) { + Map map = (Map) value; + ImmutableMap.Builder builder = + ImmutableMap.builderWithExpectedSize(map.size()); + for (Map.Entry entry : map.entrySet()) { + builder.put(maybeUnwrap(entry.getKey()), maybeUnwrap(entry.getValue())); + } + + return builder.buildOrThrow(); } - return celValue.value(); + return value; } /** @@ -101,6 +116,26 @@ protected Object normalizePrimitive(Object value) { return value; } + /** Adapts a {@link CelValue} to a plain old Java Object. */ + private static Object unwrap(CelValue celValue) { + Preconditions.checkNotNull(celValue); + + if (celValue instanceof OptionalValue) { + OptionalValue optionalValue = (OptionalValue) celValue; + if (optionalValue.isZeroValue()) { + return Optional.empty(); + } + + return Optional.of(optionalValue.value()); + } + + if (celValue instanceof ErrorValue) { + return celValue; + } + + return celValue.value(); + } + private ImmutableList toListValue(Collection iterable) { Preconditions.checkNotNull(iterable); diff --git a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java index 308d7b510..ccb8e605f 100644 --- a/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/CelValueConverterTest.java @@ -37,7 +37,8 @@ public void toRuntimeValue_optionalValue() { @Test @SuppressWarnings("unchecked") // Test only public void unwrap_optionalValue() { - Optional result = (Optional) CEL_VALUE_CONVERTER.unwrap(OptionalValue.create(2L)); + Optional result = + (Optional) CEL_VALUE_CONVERTER.maybeUnwrap(OptionalValue.create(2L)); assertThat(result).isEqualTo(Optional.of(2L)); } @@ -45,7 +46,7 @@ public void unwrap_optionalValue() { @Test @SuppressWarnings("unchecked") // Test only public void unwrap_emptyOptionalValue() { - Optional result = (Optional) CEL_VALUE_CONVERTER.unwrap(OptionalValue.EMPTY); + Optional result = (Optional) CEL_VALUE_CONVERTER.maybeUnwrap(OptionalValue.EMPTY); assertThat(result).isEqualTo(Optional.empty()); } diff --git a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java index a517931c2..17c012db7 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -35,7 +35,7 @@ public class ProtoCelValueConverterTest { @Test public void unwrap_nullValue() { - NullValue nullValue = (NullValue) PROTO_CEL_VALUE_CONVERTER.unwrap(NullValue.NULL_VALUE); + NullValue nullValue = (NullValue) PROTO_CEL_VALUE_CONVERTER.maybeUnwrap(NullValue.NULL_VALUE); // Note: No conversion is attempted. We're using dev.cel.common.values.NullValue.NULL_VALUE as // the diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index 717f7aaa0..d6b2296e5 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -176,12 +176,6 @@ _TESTS_TO_SKIP_PLANNER = [ # Skip until fixed. "parse/receiver_function_names", - "proto2/extensions_get/package_scoped_test_all_types_ext", - "proto2/extensions_get/package_scoped_repeated_test_all_types", - "proto2/extensions_get/message_scoped_nested_ext", - "proto2/extensions_get/message_scoped_repeated_test_all_types", - "proto2_ext/get_ext/package_scoped_repeated_test_all_types", - "proto2_ext/get_ext/message_scoped_repeated_test_all_types", # TODO: Fix null assignment to a field "proto2/set_null/single_message", diff --git a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java index e071289ca..38365127c 100644 --- a/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java +++ b/runtime/src/main/java/dev/cel/runtime/CelValueRuntimeTypeProvider.java @@ -22,7 +22,6 @@ import dev.cel.common.exceptions.CelAttributeNotFoundException; import dev.cel.common.values.BaseProtoCelValueConverter; import dev.cel.common.values.BaseProtoMessageValueProvider; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueProvider; import dev.cel.common.values.CombinedCelValueProvider; import dev.cel.common.values.SelectableValue; @@ -64,7 +63,7 @@ static CelValueRuntimeTypeProvider newInstance(CelValueProvider valueProvider) { @Override public Object createMessage(String messageName, Map values) { - return maybeUnwrapCelValue( + return protoCelValueConverter.maybeUnwrap( valueProvider .newValue(messageName, values) .orElseThrow( @@ -87,7 +86,7 @@ public Object selectField(Object message, String fieldName) { SelectableValue selectableValue = getSelectableValueOrThrow(message, fieldName); Object value = selectableValue.select(fieldName); - return maybeUnwrapCelValue(value); + return protoCelValueConverter.maybeUnwrap(value); } @Override @@ -120,24 +119,12 @@ public Object adapt(String messageName, Object message) { } if (message instanceof MessageLite) { - return maybeUnwrapCelValue(protoCelValueConverter.toRuntimeValue(message)); + return protoCelValueConverter.maybeUnwrap(protoCelValueConverter.toRuntimeValue(message)); } return message; } - /** - * DefaultInterpreter cannot handle CelValue and instead expects plain Java objects. - * - *

    This will become unnecessary once we introduce a rewrite of a Cel runtime. - */ - private Object maybeUnwrapCelValue(Object object) { - if (object instanceof CelValue) { - return protoCelValueConverter.unwrap((CelValue) object); - } - return object; - } - private static void throwInvalidFieldSelection(String fieldName) { throw CelAttributeNotFoundException.forFieldResolution(fieldName); } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel index f7912cff2..6561e4e5c 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel @@ -128,7 +128,6 @@ java_library( "//common/types", "//common/types:type_providers", "//common/values", - "//common/values:cel_value", "//runtime:interpretable", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", @@ -380,7 +379,6 @@ java_library( "//common:error_codes", "//common/exceptions:runtime_exception", "//common/values", - "//common/values:cel_value", "//runtime:evaluation_exception", "//runtime:interpretable", "//runtime:resolved_overload", diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java index 08f4fa8a8..92d234acc 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java @@ -17,7 +17,6 @@ import com.google.common.base.Joiner; import dev.cel.common.CelErrorCode; import dev.cel.common.exceptions.CelRuntimeException; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.common.values.ErrorValue; import dev.cel.runtime.CelEvaluationException; @@ -56,23 +55,21 @@ static Object evalStrictly( } } - static Object dispatch(CelResolvedOverload overload, CelValueConverter valueConverter, Object[] args) throws CelEvaluationException { + static Object dispatch( + CelResolvedOverload overload, CelValueConverter valueConverter, Object[] args) + throws CelEvaluationException { try { Object result = overload.getDefinition().apply(args); - Object runtimeValue = valueConverter.toRuntimeValue(result); - if (runtimeValue instanceof CelValue) { - return valueConverter.unwrap((CelValue) runtimeValue); - } - - return runtimeValue; + return valueConverter.maybeUnwrap(valueConverter.toRuntimeValue(result)); } catch (CelRuntimeException e) { // Function dispatch failure that's already been handled -- just propagate. throw e; } catch (RuntimeException e) { // Unexpected function dispatch failure. - throw new IllegalArgumentException(String.format( - "Function '%s' failed with arg(s) '%s'", - overload.getOverloadId(), Joiner.on(", ").join(args)), + throw new IllegalArgumentException( + String.format( + "Function '%s' failed with arg(s) '%s'", + overload.getOverloadId(), Joiner.on(", ").join(args)), e); } } diff --git a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java index de1a90291..cc8ca1d97 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/NamespacedAttribute.java @@ -22,7 +22,6 @@ import dev.cel.common.types.EnumType; import dev.cel.common.types.SimpleType; import dev.cel.common.types.TypeType; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.GlobalResolver; import java.util.NoSuchElementException; @@ -148,11 +147,7 @@ private static Object applyQualifiers( obj = qualifier.qualify(obj); } - if (obj instanceof CelValue) { - obj = celValueConverter.unwrap((CelValue) obj); - } - - return obj; + return celValueConverter.maybeUnwrap(obj); } static NamespacedAttribute create( diff --git a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java index 54eb26f21..b3d83c390 100644 --- a/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java +++ b/runtime/src/main/java/dev/cel/runtime/planner/RelativeAttribute.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; -import dev.cel.common.values.CelValue; import dev.cel.common.values.CelValueConverter; import dev.cel.runtime.GlobalResolver; @@ -41,10 +40,7 @@ public Object resolve(GlobalResolver ctx, ExecutionFrame frame) { } // TODO: Handle unknowns - if (obj instanceof CelValue) { - obj = celValueConverter.unwrap((CelValue) obj); - } - return obj; + return celValueConverter.maybeUnwrap(obj); } @Override diff --git a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java index 30b100bc2..20b4e641a 100644 --- a/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java +++ b/runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java @@ -316,6 +316,35 @@ public void plan_ident_variable() throws Exception { assertThat(result).isEqualTo(1); } + @Test + public void plan_ident_variableWithStructInList() throws Exception { + CelAbstractSyntaxTree ast = compile("dyn_var"); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "dyn_var", ImmutableList.of(TestAllTypes.newBuilder().setSingleInt32(42).build()))); + + assertThat(result) + .isEqualTo(ImmutableList.of(TestAllTypes.newBuilder().setSingleInt32(42).build())); + } + + @Test + public void plan_ident_variableWithStructInMap() throws Exception { + CelAbstractSyntaxTree ast = compile("dyn_var"); + Program program = PLANNER.plan(ast); + + Object result = + program.eval( + ImmutableMap.of( + "dyn_var", + ImmutableMap.of("foo", TestAllTypes.newBuilder().setSingleInt32(42).build()))); + + assertThat(result) + .isEqualTo(ImmutableMap.of("foo", TestAllTypes.newBuilder().setSingleInt32(42).build())); + } + @Test public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception { CelAbstractSyntaxTree ast = compile(testCase.expression); From 3fd7b96241ce54155fcd515a3bbb58b3f83b5ed3 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Fri, 27 Feb 2026 18:50:10 -0800 Subject: [PATCH 099/100] Fix null assignment to fields PiperOrigin-RevId: 876500682 --- .../dev/cel/common/internal/ProtoAdapter.java | 33 +++++++++++------ .../common/values/ProtoCelValueConverter.java | 2 +- .../cel/common/internal/ProtoAdapterTest.java | 17 ++++++--- .../test/java/dev/cel/conformance/BUILD.bazel | 17 --------- .../dev/cel/conformance/ConformanceTest.java | 2 +- .../cel/runtime/CelLiteInterpreterTest.java | 5 +++ .../test/resources/nullAssignability.baseline | 35 +++++++++++++++++++ .../dev/cel/testing/BaseInterpreterTest.java | 25 +++++++++++++ 8 files changed, 103 insertions(+), 33 deletions(-) create mode 100644 runtime/src/test/resources/nullAssignability.baseline diff --git a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java index 962a9d2e9..b6648a5b8 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -204,8 +204,29 @@ public Optional adaptFieldToValue(FieldDescriptor fieldDescriptor, Objec @SuppressWarnings({"unchecked", "rawtypes"}) public Optional adaptValueToFieldType( FieldDescriptor fieldDescriptor, Object fieldValue) { - if (isWrapperType(fieldDescriptor) && fieldValue.equals(NullValue.NULL_VALUE)) { - return Optional.empty(); + if (fieldValue instanceof NullValue) { + // `null` assignment to fields indicate that the field would not be set + // in a protobuf message (e.g: Message{msg_field: null} -> Message{}) + // + // We explicitly check below for invalid null assignments, such as repeated + // or map fields. (e.g: Message{repeated_field: null} -> Error) + if (fieldDescriptor.isMapField() + || fieldDescriptor.isRepeated() + || fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE + || WellKnownProto.JSON_STRUCT_VALUE + .typeName() + .equals(fieldDescriptor.getMessageType().getFullName()) + || WellKnownProto.JSON_LIST_VALUE + .typeName() + .equals(fieldDescriptor.getMessageType().getFullName())) { + throw new IllegalArgumentException("Unsupported field type"); + } + + String typeFullName = fieldDescriptor.getMessageType().getFullName(); + if (!WellKnownProto.ANY_VALUE.typeName().equals(typeFullName) + && !WellKnownProto.JSON_VALUE.typeName().equals(typeFullName)) { + return Optional.empty(); + } } if (fieldDescriptor.isMapField()) { Descriptor entryDescriptor = fieldDescriptor.getMessageType(); @@ -370,14 +391,6 @@ private static String typeName(Descriptor protoType) { return protoType.getFullName(); } - private static boolean isWrapperType(FieldDescriptor fieldDescriptor) { - if (fieldDescriptor.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { - return false; - } - String fieldTypeName = fieldDescriptor.getMessageType().getFullName(); - return WellKnownProto.isWrapperType(fieldTypeName); - } - private static int intCheckedCast(long value) { try { return Ints.checkedCast(value); diff --git a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java index c7b829e13..565c65438 100644 --- a/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/ProtoCelValueConverter.java @@ -67,7 +67,7 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel try { unpackedMessage = dynamicProto.unpack((Any) message); } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException( + throw new IllegalArgumentException( "Unpacking failed for message: " + message.getDescriptorForType().getFullName(), e); } return toRuntimeValue(unpackedMessage); diff --git a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java index 61c71e4a6..91e0e22db 100644 --- a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java +++ b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java @@ -150,10 +150,7 @@ public static List data() { @Test public void adaptValueToProto_bidirectionalConversion() { DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE); - ProtoAdapter protoAdapter = - new ProtoAdapter( - dynamicProto, - CelOptions.current().build()); + ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.current().build()); assertThat(protoAdapter.adaptValueToProto(value, proto.getDescriptorForType().getFullName())) .isEqualTo(proto); assertThat(protoAdapter.adaptProtoToValue(proto)).isEqualTo(value); @@ -181,6 +178,18 @@ public void adaptAnyValue_hermeticTypes_bidirectionalConversion() { @RunWith(JUnit4.class) public static class AsymmetricConversionTest { + + @Test + public void unpackAny_celNullValue() throws Exception { + ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT); + Any any = + (Any) + protoAdapter.adaptValueToProto( + dev.cel.common.values.NullValue.NULL_VALUE, "google.protobuf.Any"); + Object unpacked = protoAdapter.adaptProtoToValue(any); + assertThat(unpacked).isEqualTo(dev.cel.common.values.NullValue.NULL_VALUE); + } + @Test public void adaptValueToProto_asymmetricFloatConversion() { ProtoAdapter protoAdapter = new ProtoAdapter(DYNAMIC_PROTO, CelOptions.DEFAULT); diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index d6b2296e5..fb2b1a159 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -124,14 +124,6 @@ _TESTS_TO_SKIP_LEGACY = [ "string_ext/format", "string_ext/format_errors", - # TODO: Fix null assignment to a field - "proto2/set_null/single_message", - "proto2/set_null/single_duration", - "proto2/set_null/single_timestamp", - "proto3/set_null/single_message", - "proto3/set_null/single_duration", - "proto3/set_null/single_timestamp", - # Future features for CEL 1.0 # TODO: Strong typing support for enums, specified but not implemented. "enums/strong_proto2", @@ -162,7 +154,6 @@ _TESTS_TO_SKIP_PLANNER = [ "string_ext/format_errors", # TODO: Check behavior for go/cpp - "basic/functions/unbound", "basic/functions/unbound_is_runtime_error", # TODO: Ensure overflow occurs on conversions of double values which might not work properly on all platforms. @@ -177,14 +168,6 @@ _TESTS_TO_SKIP_PLANNER = [ # Skip until fixed. "parse/receiver_function_names", - # TODO: Fix null assignment to a field - "proto2/set_null/single_message", - "proto2/set_null/single_duration", - "proto2/set_null/single_timestamp", - "proto3/set_null/single_message", - "proto3/set_null/single_duration", - "proto3/set_null/single_timestamp", - # Type inference edgecases around null(able) assignability. # These type check, but resolve to a different type. # list(int), want list(wrapper(int)) diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java index 437e50fea..5a25fb9d9 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTest.java @@ -210,10 +210,10 @@ public void evaluate() throws Throwable { } CelRuntime runtime = getRuntime(test, usePlanner); - Program program = runtime.createProgram(response.getAst()); ExprValue result = null; CelEvaluationException error = null; try { + Program program = runtime.createProgram(response.getAst()); result = toExprValue(program.eval(getBindings(test)), response.getAst().getResultType()); } catch (CelEvaluationException e) { error = e; diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java index b3a1f2efa..1d1a316c0 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteInterpreterTest.java @@ -54,6 +54,11 @@ public void dynamicMessage_dynamicDescriptor() throws Exception { // All the tests below rely on message creation with fields populated. They are excluded for time // being until this support is added. + @Override + public void nullAssignability() throws Exception { + skipBaselineVerification(); + } + @Override public void wrappers() throws Exception { skipBaselineVerification(); diff --git a/runtime/src/test/resources/nullAssignability.baseline b/runtime/src/test/resources/nullAssignability.baseline new file mode 100644 index 000000000..47b9c7a0d --- /dev/null +++ b/runtime/src/test/resources/nullAssignability.baseline @@ -0,0 +1,35 @@ +Source: TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null +=====> +bindings: {} +result: true + +Source: TestAllTypes{}.single_int64_wrapper == null +=====> +bindings: {} +result: true + +Source: has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper) +=====> +bindings: {} +result: false + +Source: TestAllTypes{single_value: null}.single_value == null +=====> +bindings: {} +result: true + +Source: has(TestAllTypes{single_value: null}.single_value) +=====> +bindings: {} +result: true + +Source: TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0) +=====> +bindings: {} +result: true + +Source: has(TestAllTypes{single_timestamp: null}.single_timestamp) +=====> +bindings: {} +result: false + diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index bc67e8218..144ada5a8 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -2122,6 +2122,31 @@ public void wrappers() throws Exception { runTest(); } + @Test + public void nullAssignability() throws Exception { + setContainer(CelContainer.ofName(TestAllTypes.getDescriptor().getFile().getPackage())); + source = "TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper == null"; + runTest(); + + source = "TestAllTypes{}.single_int64_wrapper == null"; + runTest(); + + source = "has(TestAllTypes{single_int64_wrapper: null}.single_int64_wrapper)"; + runTest(); + + source = "TestAllTypes{single_value: null}.single_value == null"; + runTest(); + + source = "has(TestAllTypes{single_value: null}.single_value)"; + runTest(); + + source = "TestAllTypes{single_timestamp: null}.single_timestamp == timestamp(0)"; + runTest(); + + source = "has(TestAllTypes{single_timestamp: null}.single_timestamp)"; + runTest(); + } + @Test public void longComprehension() { ImmutableList l = LongStream.range(0L, 1000L).boxed().collect(toImmutableList()); From a881ed4f02f74bd418a08046886a89b1aed9517b Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 2 Mar 2026 09:55:25 -0800 Subject: [PATCH 100/100] Prepare 0.12.0 Release PiperOrigin-RevId: 877448588 --- MODULE.bazel | 2 +- README.md | 4 ++-- publish/BUILD.bazel | 3 +++ publish/cel_version.bzl | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index fd35e41a2..0b67c825c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -46,7 +46,7 @@ TRUTH_VERSION = "1.4.4" PROTOBUF_JAVA_VERSION = "4.33.5" -CEL_VERSION = "0.12.0-SNAPSHOT" +CEL_VERSION = "0.12.0" # Compile only artifacts [ diff --git a/README.md b/README.md index f46a1f8c6..40bd9deac 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,14 @@ CEL-Java is available in Maven Central Repository. [Download the JARs here][8] o dev.cel cel - 0.11.1 + 0.12.0 ``` **Gradle** ```gradle -implementation 'dev.cel:cel:0.11.1' +implementation 'dev.cel:cel:0.12.0' ``` Then run this example: diff --git a/publish/BUILD.bazel b/publish/BUILD.bazel index cb13a70b5..d905edc4b 100644 --- a/publish/BUILD.bazel +++ b/publish/BUILD.bazel @@ -21,6 +21,7 @@ COMMON_TARGETS = [ "//common/src/main/java/dev/cel/common/internal:file_descriptor_converter", "//common/src/main/java/dev/cel/common/internal:safe_string_formatter", "//common/src/main/java/dev/cel/common/types:cel_types", + "//common/src/main/java/dev/cel/common/types:message_type_provider", "//common/src/main/java/dev/cel/common/values", "//common/src/main/java/dev/cel/common/values:cel_value", ] @@ -31,6 +32,7 @@ RUNTIME_TARGETS = [ "//runtime/src/main/java/dev/cel/runtime:base", "//runtime/src/main/java/dev/cel/runtime:interpreter", "//runtime/src/main/java/dev/cel/runtime:late_function_binding", + "//runtime/src/main/java/dev/cel/runtime:runtime_experimental_factory", "//runtime/src/main/java/dev/cel/runtime:runtime_factory", "//runtime/src/main/java/dev/cel/runtime:runtime_helpers", "//runtime/src/main/java/dev/cel/runtime:runtime_legacy_impl", @@ -123,6 +125,7 @@ EXTENSION_TARGETS = [ # keep sorted BUNDLE_TARGETS = [ "//bundle/src/main/java/dev/cel/bundle:cel", + "//bundle/src/main/java/dev/cel/bundle:cel_experimental_factory", "//bundle/src/main/java/dev/cel/bundle:environment", "//bundle/src/main/java/dev/cel/bundle:environment_yaml_parser", ] diff --git a/publish/cel_version.bzl b/publish/cel_version.bzl index ea793eee5..b40addd73 100644 --- a/publish/cel_version.bzl +++ b/publish/cel_version.bzl @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. """Maven artifact version for CEL.""" -CEL_VERSION = "0.12.0-SNAPSHOT" +CEL_VERSION = "0.12.0"