diff --git a/MODULE.bazel b/MODULE.bazel index de3cbdace..8e0e79068 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -35,11 +35,11 @@ use_repo(switched_rules, "com_google_googleapis_imports") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") -GUAVA_VERSION = "33.4.8" +GUAVA_VERSION = "33.5.0" TRUTH_VERSION = "1.4.4" -PROTOBUF_JAVA_VERSION = "4.32.0" +PROTOBUF_JAVA_VERSION = "4.33.0" # Compile only artifacts [ @@ -51,7 +51,7 @@ PROTOBUF_JAVA_VERSION = "4.32.0" ) for group, artifact, version in [coord.split(":") for coord in [ "com.google.code.findbugs:annotations:3.0.1", - "com.google.errorprone:error_prone_annotations:2.41.0", + "com.google.errorprone:error_prone_annotations:2.42.0", ]] ] @@ -114,7 +114,7 @@ maven.install( maven.install( name = "maven_conformance", - artifacts = ["dev.cel:cel:0.11.0"], + artifacts = ["dev.cel:cel:0.11.1"], repositories = [ "https://maven.google.com", "https://repo1.maven.org/maven2", diff --git a/README.md b/README.md index 61ac687b9..f46a1f8c6 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.0 + 0.11.1 ``` **Gradle** ```gradle -implementation 'dev.cel:cel:0.11.0' +implementation 'dev.cel:cel:0.11.1' ``` Then run this example: diff --git a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel index 35b4679f7..50b78c63b 100644 --- a/bundle/src/main/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/main/java/dev/cel/bundle/BUILD.bazel @@ -25,6 +25,7 @@ java_library( "//checker:checker_builder", "//checker:checker_legacy_environment", "//checker:proto_type_mask", + "//checker:standard_decl", "//common:cel_ast", "//common:cel_source", "//common:compiler_common", @@ -42,6 +43,7 @@ java_library( "//parser:parser_builder", "//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", diff --git a/bundle/src/main/java/dev/cel/bundle/CelBuilder.java b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java index 9580dc555..1dadaeb39 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelBuilder.java +++ b/bundle/src/main/java/dev/cel/bundle/CelBuilder.java @@ -22,6 +22,7 @@ import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; +import dev.cel.checker.CelStandardDeclarations; import dev.cel.checker.ProtoTypeMask; import dev.cel.checker.TypeProvider; import dev.cel.common.CelContainer; @@ -36,6 +37,7 @@ import dev.cel.parser.CelStandardMacro; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntimeLibrary; +import dev.cel.runtime.CelStandardFunctions; import java.util.function.Function; /** Interface for building an instance of Cel. */ @@ -84,7 +86,7 @@ public interface CelBuilder { /** Retrieves the currently configured {@link CelContainer} in the builder. */ CelContainer container(); - /* + /** * Set the {@link CelContainer} to use as the namespace for resolving CEL expression variables and * functions. */ @@ -299,6 +301,24 @@ public interface CelBuilder { @CanIgnoreReturnValue CelBuilder addRuntimeLibraries(Iterable libraries); + /** + * Override the standard declarations for the type-checker. This can be used to subset the + * standard environment to only expose the desired declarations to the type-checker. {@link + * #setStandardEnvironmentEnabled(boolean)} must be set to false for this to take effect. + */ + @CanIgnoreReturnValue + CelBuilder setStandardDeclarations(CelStandardDeclarations standardDeclarations); + + /** + * Override the standard functions for the runtime. This can be used to subset the standard + * environment to only expose the desired function overloads to the runtime. + * + *

{@link #setStandardEnvironmentEnabled(boolean)} must be set to false for this to take + * effect. + */ + @CanIgnoreReturnValue + CelBuilder setStandardFunctions(CelStandardFunctions standardFunctions); + /** * Sets a proto ExtensionRegistry to assist with unpacking Any messages containing a proto2 extension field. diff --git a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java index 3bdc7c894..9f33032a8 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java +++ b/bundle/src/main/java/dev/cel/bundle/CelEnvironment.java @@ -70,7 +70,8 @@ public abstract class CelEnvironment { "optional", CanonicalCelExtension.OPTIONAL, "protos", CanonicalCelExtension.PROTOS, "sets", CanonicalCelExtension.SETS, - "strings", CanonicalCelExtension.STRINGS); + "strings", CanonicalCelExtension.STRINGS, + "comprehensions", CanonicalCelExtension.COMPREHENSIONS); /** Environment source in textual format (ex: textproto, YAML). */ public abstract Optional source(); @@ -687,8 +688,8 @@ enum CanonicalCelExtension { BINDINGS((options, version) -> CelExtensions.bindings()), PROTOS((options, version) -> CelExtensions.protos()), ENCODERS( - (options, version) -> CelExtensions.encoders(), - (options, version) -> CelExtensions.encoders()), + (options, version) -> CelExtensions.encoders(options), + (options, version) -> CelExtensions.encoders(options)), MATH( (options, version) -> CelExtensions.math(options, version), (options, version) -> CelExtensions.math(options, version)), @@ -701,7 +702,10 @@ enum CanonicalCelExtension { SETS( (options, version) -> CelExtensions.sets(options), (options, version) -> CelExtensions.sets(options)), - LISTS((options, version) -> CelExtensions.lists(), (options, version) -> CelExtensions.lists()); + LISTS((options, version) -> CelExtensions.lists(), (options, version) -> CelExtensions.lists()), + COMPREHENSIONS( + (options, version) -> CelExtensions.comprehensions(), + (options, version) -> CelExtensions.comprehensions()); @SuppressWarnings("ImmutableEnumChecker") private final CompilerExtensionProvider compilerExtensionProvider; diff --git a/bundle/src/main/java/dev/cel/bundle/CelImpl.java b/bundle/src/main/java/dev/cel/bundle/CelImpl.java index 795e202e3..bc92cca7a 100644 --- a/bundle/src/main/java/dev/cel/bundle/CelImpl.java +++ b/bundle/src/main/java/dev/cel/bundle/CelImpl.java @@ -28,6 +28,7 @@ import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.Message; import dev.cel.checker.CelCheckerBuilder; +import dev.cel.checker.CelStandardDeclarations; import dev.cel.checker.ProtoTypeMask; import dev.cel.checker.TypeProvider; import dev.cel.common.CelAbstractSyntaxTree; @@ -54,6 +55,7 @@ import dev.cel.runtime.CelRuntime; import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.CelRuntimeLibrary; +import dev.cel.runtime.CelStandardFunctions; import java.util.Arrays; import java.util.function.Function; @@ -382,6 +384,20 @@ public CelBuilder addRuntimeLibraries(Iterable libraries) { return this; } + @Override + public CelBuilder setStandardDeclarations(CelStandardDeclarations standardDeclarations) { + checkNotNull(standardDeclarations); + compilerBuilder.setStandardDeclarations(standardDeclarations); + return this; + } + + @Override + public CelBuilder setStandardFunctions(CelStandardFunctions standardFunctions) { + checkNotNull(standardFunctions); + runtimeBuilder.setStandardFunctions(standardFunctions); + return this; + } + @Override public CelBuilder setExtensionRegistry(ExtensionRegistry extensionRegistry) { checkNotNull(extensionRegistry); diff --git a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel index a4c6f513d..26cbe392d 100644 --- a/bundle/src/test/java/dev/cel/bundle/BUILD.bazel +++ b/bundle/src/test/java/dev/cel/bundle/BUILD.bazel @@ -25,6 +25,7 @@ java_library( "//checker:checker_legacy_environment", "//checker:proto_type_mask", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_source", "//common:compiler_common", "//common:container", diff --git a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java index bafa20ea6..56a4a241c 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelEnvironmentTest.java @@ -60,7 +60,8 @@ public void extend_allExtensions() throws Exception { ExtensionConfig.latest("optional"), ExtensionConfig.latest("protos"), ExtensionConfig.latest("sets"), - ExtensionConfig.latest("strings")); + ExtensionConfig.latest("strings"), + ExtensionConfig.latest("comprehensions")); CelEnvironment environment = CelEnvironment.newBuilder().addExtensions(extensionConfigs).build(); @@ -100,9 +101,7 @@ public void extensionVersion_specific() throws Exception { @Test public void extensionVersion_latest() throws Exception { CelEnvironment environment = - CelEnvironment.newBuilder() - .addExtensions(ExtensionConfig.latest("math")) - .build(); + CelEnvironment.newBuilder().addExtensions(ExtensionConfig.latest("math")).build(); Cel cel = environment.extend(CelFactory.standardCelBuilder().build(), CelOptions.DEFAULT); CelAbstractSyntaxTree ast = cel.compile("math.sqrt(4)").getAst(); @@ -240,10 +239,10 @@ public void stdlibSubset_functionsIncluded() throws Exception { LibrarySubset.newBuilder() .setDisabled(false) .setIncludedFunctions( - ImmutableSet.of( - FunctionSelector.create("_==_", ImmutableSet.of()), - FunctionSelector.create("_!=_", ImmutableSet.of()), - FunctionSelector.create("_&&_", ImmutableSet.of()))) + ImmutableSet.of( + FunctionSelector.create("_==_", ImmutableSet.of()), + FunctionSelector.create("_!=_", ImmutableSet.of()), + FunctionSelector.create("_&&_", ImmutableSet.of()))) .build()) .build(); @@ -287,8 +286,7 @@ public void stdlibSubset_functionsExcluded() throws Exception { LibrarySubset.newBuilder() .setDisabled(false) .setExcludedFunctions( - ImmutableSet.of( - FunctionSelector.create("_+_", ImmutableSet.of()))) + ImmutableSet.of(FunctionSelector.create("_+_", ImmutableSet.of()))) .build()) .build(); diff --git a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java index 2a702538a..2d337e4cf 100644 --- a/bundle/src/test/java/dev/cel/bundle/CelImplTest.java +++ b/bundle/src/test/java/dev/cel/bundle/CelImplTest.java @@ -43,14 +43,18 @@ import com.google.protobuf.ByteString; import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.DescriptorProtos.FileDescriptorSet; +import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.Duration; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.Empty; +import com.google.protobuf.ExtensionRegistry; import com.google.protobuf.FieldMask; import com.google.protobuf.Message; import com.google.protobuf.Struct; import com.google.protobuf.TextFormat; import com.google.protobuf.Timestamp; +import com.google.protobuf.TypeRegistry; import com.google.protobuf.WrappersProto; import com.google.rpc.context.AttributeContext; import com.google.testing.junit.testparameterinjector.TestParameter; @@ -62,6 +66,7 @@ import dev.cel.checker.TypeProvider; import dev.cel.common.CelAbstractSyntaxTree; import dev.cel.common.CelContainer; +import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelErrorCode; import dev.cel.common.CelIssue; import dev.cel.common.CelOptions; @@ -72,7 +77,6 @@ import dev.cel.common.CelVarDecl; import dev.cel.common.ast.CelExpr; import dev.cel.common.ast.CelExpr.CelList; -import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.testing.RepeatedTestProvider; import dev.cel.common.types.CelKind; import dev.cel.common.types.CelProtoMessageTypes; @@ -91,6 +95,7 @@ import dev.cel.compiler.CelCompilerFactory; import dev.cel.compiler.CelCompilerImpl; import dev.cel.expr.conformance.proto2.Proto2ExtensionScopedMessage; +import dev.cel.expr.conformance.proto2.TestAllTypesExtensions; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.parser.CelParserImpl; import dev.cel.parser.CelStandardMacro; @@ -108,6 +113,7 @@ import dev.cel.runtime.CelVariableResolver; import dev.cel.runtime.UnknownContext; import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum; +import java.time.Instant; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -196,13 +202,11 @@ public void build_badFileDescriptorSet() { IllegalArgumentException.class, () -> standardCelBuilderWithMacros() - .setContainer(CelContainer.ofName("google.rpc.context.AttributeContext")) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto2")) .addFileTypes( FileDescriptorSet.newBuilder() - .addFile(AttributeContext.getDescriptor().getFile().toProto()) + .addFile(TestAllTypesExtensions.getDescriptor().getFile().toProto()) .build()) - .setProtoResultType( - CelProtoTypes.createMessage("google.rpc.context.AttributeContext.Resource")) .build()); assertThat(e).hasMessageThat().contains("file descriptor set with unresolved proto file"); } @@ -813,6 +817,7 @@ 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")) @@ -820,20 +825,22 @@ public void program_duplicateTypeDescriptor() throws Exception { .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); + + assertThat(program.eval()).isEqualTo(Instant.ofEpochSecond(12)); } @Test 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) .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); + assertThat(program.eval()).isEqualTo(Instant.ofEpochSecond(12)); } @Test @@ -955,6 +962,7 @@ 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")) @@ -962,7 +970,7 @@ public void program_typeProvider() throws Exception { .build(); CelRuntime.Program program = cel.createProgram(cel.compile("protobuf.Timestamp{seconds: 12}").getAst()); - assertThat(program.eval()).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(12)); + assertThat(program.eval()).isEqualTo(Instant.ofEpochSecond(12)); } @Test @@ -2107,6 +2115,75 @@ public void program_evaluateCanonicalTypesToNativeTypesDisabled_producesProtoVal assertThat(result).containsExactly(com.google.protobuf.NullValue.NULL_VALUE, expectedNestedMap); } + @Test + public void program_evaluateCanonicalTypesToNativeTypesDisabled_producesBytesProto() + throws Exception { + Cel cel = + standardCelBuilderWithMacros() + .addMessageTypes(TestAllTypes.getDescriptor()) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .setOptions(CelOptions.current().evaluateCanonicalTypesToNativeValues(false).build()) + .build(); + CelAbstractSyntaxTree ast = + cel.compile("TestAllTypes{single_bytes: bytes('abc')}.single_bytes").getAst(); + + ByteString result = (ByteString) cel.createProgram(ast).eval(); + + assertThat(result).isEqualTo(ByteString.copyFromUtf8("abc")); + } + + @Test + public void program_fdsContainsWktDependency_descriptorInstancesMatch() throws Exception { + // Force serialization of the descriptor to get a unique instance + FileDescriptorProto proto = TestAllTypes.getDescriptor().getFile().toProto(); + FileDescriptorSet fds = FileDescriptorSet.newBuilder().addFile(proto).build(); + ImmutableSet fileDescriptors = + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fds); + ImmutableSet descriptors = + CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(fileDescriptors) + .messageTypeDescriptors(); + Descriptor testAllTypesDescriptor = + descriptors.stream() + .filter(x -> x.getFullName().equals(TestAllTypes.getDescriptor().getFullName())) + .findAny() + .get(); + + // Parse text proto using this fds + TypeRegistry typeRegistry = TypeRegistry.newBuilder().add(descriptors).build(); + TestAllTypes.Builder testAllTypesBuilder = TestAllTypes.newBuilder(); + TextFormat.Parser textFormatParser = + TextFormat.Parser.newBuilder().setTypeRegistry(typeRegistry).build(); + String textProto = + "single_timestamp {\n" // + + " seconds: 100\n" // + + "}"; + textFormatParser.merge(textProto, testAllTypesBuilder); + TestAllTypes testAllTypesFromTextProto = testAllTypesBuilder.build(); + DynamicMessage dynamicMessage = + DynamicMessage.parseFrom( + testAllTypesDescriptor, + testAllTypesFromTextProto.toByteArray(), + ExtensionRegistry.getEmptyRegistry()); + // Setup CEL environment with the same descriptors obtained from FDS + Cel cel = + standardCelBuilderWithMacros() + .addMessageTypes(descriptors) + .setOptions( + CelOptions.current() + .evaluateCanonicalTypesToNativeValues(true) + .enableTimestampEpoch(true) + .build()) + .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .build(); + CelAbstractSyntaxTree ast = + cel.compile("TestAllTypes{single_timestamp: timestamp(100)}").getAst(); + + DynamicMessage evalResult = (DynamicMessage) cel.createProgram(ast).eval(); + + // This should strictly equal regardless of where the descriptors came from for WKTs + assertThat(evalResult).isEqualTo(dynamicMessage); + } + @Test public void toBuilder_isImmutable() { CelBuilder celBuilder = CelFactory.standardCelBuilder(); diff --git a/checker/src/main/java/dev/cel/checker/BUILD.bazel b/checker/src/main/java/dev/cel/checker/BUILD.bazel index 80f6e1a8a..5479934c4 100644 --- a/checker/src/main/java/dev/cel/checker/BUILD.bazel +++ b/checker/src/main/java/dev/cel/checker/BUILD.bazel @@ -73,7 +73,7 @@ java_library( ":type_provider_legacy_impl", "//:auto_value", "//common:cel_ast", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common:cel_source", "//common:compiler_common", "//common:container", diff --git a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java index 3865430ec..94b7ad313 100644 --- a/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java +++ b/checker/src/main/java/dev/cel/checker/CelStandardDeclarations.java @@ -1488,7 +1488,7 @@ public CelFunctionDecl functionDecl() { return celFunctionDecl; } - String functionName() { + public String functionName() { return functionName; } diff --git a/checker/src/main/java/dev/cel/checker/ExprChecker.java b/checker/src/main/java/dev/cel/checker/ExprChecker.java index c2a2929b1..d4dbf7994 100644 --- a/checker/src/main/java/dev/cel/checker/ExprChecker.java +++ b/checker/src/main/java/dev/cel/checker/ExprChecker.java @@ -362,6 +362,13 @@ private CelExpr visit(CelExpr expr, CelExpr.CelStruct struct) { 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(); + } + env.setRef(expr, CelReference.newBuilder().setName(decl.name()).build()); CelType type = decl.type(); if (type.kind() != CelKind.ERROR) { diff --git a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java index c019604d5..ec1c31840 100644 --- a/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java +++ b/checker/src/main/java/dev/cel/checker/ProtoTypeMask.java @@ -75,12 +75,12 @@ public ProtoTypeMask withFieldsAsVariableDeclarations() { * treated as variable identifiers bound to the protobuf field name and its associated field type. * *

A {@code FieldMask} contains one or more {@code paths} which contain identifier characters - * that have been dot delimited, e.g.resource.name, request.auth.claims. Here are a few things to + * that have been dot delimited, e.g. resource.name, request.auth.claims. Here are a few things to * keep in mind: * *

@@ -99,7 +99,7 @@ public static ProtoTypeMask of(String typeName, FieldMask fieldMask) { * Construct a new {@code ProtoTypeMask} which exposes all fields in the given {@code typeName} * for use within CEL expressions. * - *

The {@code typeName} should be a fully-qualified path, e.g., {@code + *

The {@code typeName} should be a fully-qualified path, e.g. {@code * "google.rpc.context.AttributeContext"}. * *

All top-level fields in the given {@code typeName} should be treated as variable identifiers @@ -113,7 +113,7 @@ public static ProtoTypeMask ofAllFields(String fullyQualifiedTypeName) { * Construct a new {@code ProtoTypeMask} which hides all fields in the given {@code typeName} for * use within CEL expressions. * - *

The {@code typeName} should be a fully-qualified path, e.g., {@code + *

The {@code typeName} should be a fully-qualified path, e.g. {@code * "google.rpc.context.AttributeContext"}. */ public static ProtoTypeMask ofAllFieldsHidden(String fullyQualifiedTypeName) { diff --git a/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java b/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java index f4b389552..d9e311ca2 100644 --- a/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java +++ b/checker/src/test/java/dev/cel/checker/CelProtoExprVisitorTest.java @@ -175,7 +175,9 @@ public void visitSelect() throws Exception { .isEqualTo( VisitedReference.newBuilder() .setCreateStruct( - Expr.CreateStruct.newBuilder().setMessageName("TestAllTypes").build()) + Expr.CreateStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .setSelect( Expr.Select.newBuilder() .setOperand( @@ -183,7 +185,7 @@ public void visitSelect() throws Exception { .setId(1) .setStructExpr( Expr.CreateStruct.newBuilder() - .setMessageName("TestAllTypes") + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") .build())) .setField("single_int64") .build()) @@ -227,7 +229,9 @@ public void visitCreateStruct() throws Exception { .isEqualTo( VisitedReference.newBuilder() .setCreateStruct( - Expr.CreateStruct.newBuilder().setMessageName("TestAllTypes").build()) + Expr.CreateStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .build()); } diff --git a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java index 360a8c64a..c3b26785a 100644 --- a/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java +++ b/checker/src/test/java/dev/cel/checker/ExprCheckerTest.java @@ -73,8 +73,9 @@ private void runTest() throws Exception { CelAbstractSyntaxTree ast = prepareTest( Arrays.asList( - TestAllTypes.getDescriptor(), - dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor())); + StandaloneGlobalEnum.getDescriptor().getFile(), + TestAllTypes.getDescriptor().getFile(), + dev.cel.expr.conformance.proto2.TestAllTypes.getDescriptor().getFile())); if (ast != null) { testOutput() .println( @@ -239,6 +240,45 @@ public void messageFieldSelect() throws Exception { runTest(); } + @Test + public void containers() throws Exception { + container = + CelContainer.newBuilder() + .setName("dev.cel.testing.testdata.proto3.StandaloneGlobalEnum") + .addAlias("p3_alias", "cel.expr.conformance.proto3") + .addAlias("foo_bar_alias", "foo.bar") + .addAlias("foo_bar_baz_alias", "foo.bar.baz") + .addAbbreviations("cel.expr.conformance.proto2", "cel.expr.conformance.proto3") + .build(); + source = "p3_alias.TestAllTypes{}"; + runTest(); + + source = "proto2.TestAllTypes{}"; + runTest(); + + source = "proto3.TestAllTypes{}"; + runTest(); + + source = "SGAR"; // From StandaloneGlobalEnum + runTest(); + + declareVariable("foo.bar", SimpleType.STRING); + declareFunction( + "baz", + memberOverload( + "foo_bar_baz_overload", ImmutableList.of(SimpleType.STRING), SimpleType.DYN)); + // Member call of "baz()" on "foo.bar" identifier + source = "foo_bar_alias.baz()"; + runTest(); + + declareFunction( + "foo.bar.baz.qux", + globalOverload("foo_bar_baz_qux_overload", ImmutableList.of(), SimpleType.DYN)); + // Global call of "foo.bar.baz.qux" as a fully qualified name + source = "foo_bar_baz_alias.qux()"; + runTest(); + } + @Test public void messageCreationError() throws Exception { declareVariable("x", SimpleType.INT); @@ -851,17 +891,16 @@ public void twoVarComprehensions_incorrectIterVars() throws Exception { public void twoVarComprehensions_duplicateIterVars() throws Exception { CelType messageType = StructTypeReference.create("cel.expr.conformance.proto3.TestAllTypes"); declareVariable("x", messageType); - source = - "x.repeated_int64.exists(i, i, i < v)"; + source = "x.repeated_int64.exists(i, i, i < v)"; runTest(); } - + @Test public void twoVarComprehensions_incorrectNumberOfArgs() throws Exception { CelType messageType = StructTypeReference.create("cel.expr.conformance.proto3.TestAllTypes"); declareVariable("x", messageType); source = - "[1, 2, 3, 4].exists_one(i, v, i < v, v)" + "[1, 2, 3, 4].exists_one(i, v, i < v, v)" + "&& x.map_string_string.transformList(i, i < v) " + "&& [1, 2, 3].transformList(i, v, i > 0 && x < 3, (i * v) + v) == [4]"; runTest(); diff --git a/checker/src/test/resources/aggregateMessage.baseline b/checker/src/test/resources/aggregateMessage.baseline index 7bace8021..eb138b0e8 100644 --- a/checker/src/test/resources/aggregateMessage.baseline +++ b/checker/src/test/resources/aggregateMessage.baseline @@ -1,7 +1,6 @@ Source: TestAllTypes{single_int32: 1, single_int64: 2} =====> -TestAllTypes{ +cel.expr.conformance.proto3.TestAllTypes{ single_int32:1~int, single_int64:2~int -}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes - +}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes \ No newline at end of file diff --git a/checker/src/test/resources/containers.baseline b/checker/src/test/resources/containers.baseline new file mode 100644 index 000000000..cdf26eb63 --- /dev/null +++ b/checker/src/test/resources/containers.baseline @@ -0,0 +1,38 @@ +Source: p3_alias.TestAllTypes{} +=====> +cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes + +Source: proto2.TestAllTypes{} +=====> +cel.expr.conformance.proto2.TestAllTypes{}~cel.expr.conformance.proto2.TestAllTypes^cel.expr.conformance.proto2.TestAllTypes + +Source: proto3.TestAllTypes{} +=====> +cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes + +Source: SGAR +=====> +dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR~int^dev.cel.testing.testdata.proto3.StandaloneGlobalEnum.SGAR + +Source: foo_bar_alias.baz() +declare foo.bar { + value string +} +declare baz { + function foo_bar_baz_overload string.() -> dyn +} +=====> +foo.bar~string^foo.bar.baz()~dyn^foo_bar_baz_overload + +Source: foo_bar_baz_alias.qux() +declare foo.bar { + value string +} +declare baz { + function foo_bar_baz_overload string.() -> dyn +} +declare foo.bar.baz.qux { + function foo_bar_baz_qux_overload () -> dyn +} +=====> +foo.bar.baz.qux()~dyn^foo_bar_baz_qux_overload \ No newline at end of file diff --git a/checker/src/test/resources/nestedEnums.baseline b/checker/src/test/resources/nestedEnums.baseline index 211fbf4c0..f05594997 100644 --- a/checker/src/test/resources/nestedEnums.baseline +++ b/checker/src/test/resources/nestedEnums.baseline @@ -30,8 +30,8 @@ declare single_nested_enum { } =====> _==_( - TestAllTypes{ + cel.expr.conformance.proto3.TestAllTypes{ single_nested_enum:cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR~int^cel.expr.conformance.proto3.TestAllTypes.NestedEnum.BAR }~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes.single_nested_enum~int, 1~int -)~bool^equals +)~bool^equals \ No newline at end of file diff --git a/checker/src/test/resources/nullableMessage.baseline b/checker/src/test/resources/nullableMessage.baseline index 6b5c13a68..54f8e56d5 100644 --- a/checker/src/test/resources/nullableMessage.baseline +++ b/checker/src/test/resources/nullableMessage.baseline @@ -16,10 +16,10 @@ declare x { _||_( _==_( null~null, - TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes + cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes )~bool^equals, _==_( - TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes, + cel.expr.conformance.proto3.TestAllTypes{}~cel.expr.conformance.proto3.TestAllTypes^cel.expr.conformance.proto3.TestAllTypes, null~null )~bool^equals -)~bool^logical_or +)~bool^logical_or \ No newline at end of file diff --git a/checker/src/test/resources/optionals.baseline b/checker/src/test/resources/optionals.baseline index a86fb1fc7..be50e44ba 100644 --- a/checker/src/test/resources/optionals.baseline +++ b/checker/src/test/resources/optionals.baseline @@ -72,7 +72,7 @@ Source: {?'key': {'a': 'b'}.?value}.key Source: TestAllTypes{?single_int32: {}.?i} =====> -TestAllTypes{ +cel.expr.conformance.proto3.TestAllTypes{ ?single_int32:_?._( {}~map(dyn, int), "i" @@ -118,4 +118,4 @@ declare b { { ?"str"~string:a~optional_type(string)^a, 2~int:3~int -}~map(dyn, dyn) +}~map(dyn, dyn) \ No newline at end of file diff --git a/common/BUILD.bazel b/common/BUILD.bazel index 77a336dbf..f86ea04f9 100644 --- a/common/BUILD.bazel +++ b/common/BUILD.bazel @@ -106,5 +106,18 @@ java_library( java_library( name = "cel_descriptors", + visibility = ["//:internal"], exports = ["//common/src/main/java/dev/cel/common:cel_descriptors"], ) + +java_library( + name = "cel_descriptor_util", + 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 a39bb9365..c69f26b3e 100644 --- a/common/internal/BUILD.bazel +++ b/common/internal/BUILD.bazel @@ -137,3 +137,13 @@ cel_android_library( name = "proto_time_utils_android", exports = ["//common/src/main/java/dev/cel/common/internal:proto_time_utils_android"], ) + +java_library( + name = "date_time_helpers", + exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers"], +) + +cel_android_library( + name = "date_time_helpers_android", + exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"], +) diff --git a/common/src/main/java/dev/cel/common/BUILD.bazel b/common/src/main/java/dev/cel/common/BUILD.bazel index 255ae5d8d..ce6963206 100644 --- a/common/src/main/java/dev/cel/common/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/BUILD.bazel @@ -36,18 +36,31 @@ PROTO_V1ALPHA1_AST_SOURCE = [ ] java_library( - name = "cel_descriptors", + name = "cel_descriptor_util", srcs = [ "CelDescriptorUtil.java", - "CelDescriptors.java", ], tags = [ ], deps = [ - "//:auto_value", + ":cel_descriptors", "//common/annotations", "//common/internal:file_descriptor_converter", "//common/types:cel_types", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +java_library( + name = "cel_descriptors", + srcs = [ + "CelDescriptors.java", + ], + tags = [ + ], + deps = [ + "//:auto_value", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -205,6 +218,7 @@ java_library( tags = [ ], deps = [ + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", diff --git a/common/src/main/java/dev/cel/common/CelContainer.java b/common/src/main/java/dev/cel/common/CelContainer.java index 6d9fcd612..ccb1715ba 100644 --- a/common/src/main/java/dev/cel/common/CelContainer.java +++ b/common/src/main/java/dev/cel/common/CelContainer.java @@ -148,6 +148,19 @@ public Builder addAbbreviations(ImmutableSet qualifiedNames) { * useful for remapping poorly chosen protobuf message / package names. * *

Note: all the rules that apply to abbreviations also apply to aliasing. + * + *

Note: It is also possible to alias a top-level package or a name that does not contain a + * period. When resolving an identifier, CEL checks for variables and functions before + * attempting to expand aliases for type resolution. Therefore, if an expression consists solely + * of an identifier that matches both an alias and a declared variable (e.g., {@code + * short_alias}), the variable will take precedence and the compilation will succeed. The alias + * expansion will only be used when the alias is a prefix to a longer name (e.g., {@code + * short_alias.TestRequest}) or if no variable with the same name exists, in which case using + * the alias as a standalone identifier will likely result in a compilation error. + * + * @param alias Simple name to be expanded. Must be a valid identifier. + * @param qualifiedName The fully qualified name to expand to. This may be a simple name (e.g. a + * package name) but it must be a valid identifier. */ @CanIgnoreReturnValue public Builder addAlias(String alias, String qualifiedName) { @@ -172,12 +185,6 @@ private void validateAliasOrThrow(AliasKind kind, String qualifiedName, String a String.format("qualified name must not begin with a leading '.': %s", qualifiedName)); } - int index = qualifiedName.lastIndexOf("."); - if (index <= 0 || index == qualifiedName.length() - 1) { - throw new IllegalArgumentException( - String.format("%s must refer to a valid qualified name: %s", kind, qualifiedName)); - } - String aliasRef = aliases.get(alias); if (aliasRef != null) { throw new IllegalArgumentException( diff --git a/common/src/main/java/dev/cel/common/CelDescriptorUtil.java b/common/src/main/java/dev/cel/common/CelDescriptorUtil.java index db1f22120..7695e96d8 100644 --- a/common/src/main/java/dev/cel/common/CelDescriptorUtil.java +++ b/common/src/main/java/dev/cel/common/CelDescriptorUtil.java @@ -166,10 +166,12 @@ private static void collectMessageTypeDescriptors( if (visited.contains(messageName)) { return; } + if (!descriptor.getOptions().getMapEntry()) { visited.add(messageName); celDescriptors.addMessageTypeDescriptors(descriptor); } + if (CelTypes.getWellKnownCelType(messageName).isPresent()) { return; } @@ -234,6 +236,7 @@ private static void copyToFileDescriptorSet( if (visited.contains(fd.getFullName())) { return; } + visited.add(fd.getFullName()); for (FileDescriptor dep : fd.getDependencies()) { copyToFileDescriptorSet(visited, dep, files); diff --git a/common/src/main/java/dev/cel/common/CelOptions.java b/common/src/main/java/dev/cel/common/CelOptions.java index 23654235d..3cbfb62b1 100644 --- a/common/src/main/java/dev/cel/common/CelOptions.java +++ b/common/src/main/java/dev/cel/common/CelOptions.java @@ -462,6 +462,8 @@ public abstract static class Builder { * com.google.protobuf.ByteString}. *

  • CEL null: {@code dev.cel.common.values.NullValue} instead of {@code * com.google.protobuf.NullValue}. + *
  • Timestamp: {@code java.time.Instant} instead of {@code com.google.protobuf.Timestamp}. + *
  • Duration: {@code java.time.Duration} instead of {@code com.google.protobuf.Duration}. * */ public abstract Builder evaluateCanonicalTypesToNativeValues(boolean value); diff --git a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java index e0afb8c4e..e74d52f7a 100644 --- a/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java +++ b/common/src/main/java/dev/cel/common/CelProtoJsonAdapter.java @@ -26,8 +26,10 @@ import com.google.protobuf.Struct; import com.google.protobuf.Timestamp; import com.google.protobuf.Value; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -118,6 +120,14 @@ public static Value adaptValueToJsonValue(Object value) { String duration = ProtoTimeUtils.toString((Duration) value); return json.setStringValue(duration).build(); } + if (value instanceof Instant) { + // Instant's toString follows RFC 3339 + return json.setStringValue(value.toString()).build(); + } + if (value instanceof java.time.Duration) { + String duration = DateTimeHelpers.toString((java.time.Duration) value); + return json.setStringValue(duration).build(); + } if (value instanceof FieldMask) { String fieldMaskStr = toJsonString((FieldMask) value); return json.setStringValue(fieldMaskStr).build(); diff --git a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java index d3940405e..e0fabc400 100644 --- a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java +++ b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java @@ -1034,6 +1034,10 @@ public static CelMutableExpr ofMap(long id, CelMutableMap mutableMap) { return new CelMutableExpr(id, mutableMap); } + public static CelMutableExpr ofComprehension(CelMutableComprehension mutableComprehension) { + return ofComprehension(0, mutableComprehension); + } + public static CelMutableExpr ofComprehension( long id, CelMutableComprehension mutableComprehension) { return new CelMutableExpr(id, mutableComprehension); 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 c508649a3..690b1cc75 100644 --- a/common/src/main/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/internal/BUILD.bazel @@ -195,9 +195,11 @@ java_library( deps = [ ":well_known_proto", "//common:error_codes", + "//common:options", "//common:proto_json_adapter", "//common:runtime_exception", "//common/annotations", + "//common/internal:proto_time_utils", "//common/values", "//common/values:cel_byte_string", "@maven//:com_google_errorprone_error_prone_annotations", @@ -230,7 +232,9 @@ java_library( tags = [ ], deps = [ + ":cel_descriptor_pools", "//:auto_value", + "//common/internal:well_known_proto", "@maven//:com_google_errorprone_error_prone_annotations", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", @@ -429,3 +433,31 @@ cel_android_library( "@maven_android//:com_google_protobuf_protobuf_javalite", ], ) + +java_library( + name = "date_time_helpers", + srcs = ["DateTimeHelpers.java"], + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_java", + ], +) + +cel_android_library( + name = "date_time_helpers_android", + srcs = ["DateTimeHelpers.java"], + tags = [ + ], + deps = [ + "//common:error_codes", + "//common:runtime_exception", + "//common/annotations", + "@maven_android//:com_google_guava_guava", + "@maven_android//:com_google_protobuf_protobuf_javalite", + ], +) diff --git a/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java b/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java index daec1a464..4d498d526 100644 --- a/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java +++ b/common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java @@ -81,7 +81,8 @@ public static int compareUintInt(UnsignedLong ul, long l) { public static boolean numericEquals(Number x, Number y) { if (x instanceof Double) { if (y instanceof Double) { - return !(Double.isNaN((Double) x) || Double.isNaN((Double) y)) && x.equals(y); + return !(Double.isNaN((Double) x) || Double.isNaN((Double) y)) + && x.doubleValue() == y.doubleValue(); } if (y instanceof Long) { return compareDoubleInt((Double) x, (Long) y) == 0; @@ -115,10 +116,7 @@ public static boolean numericEquals(Number x, Number y) { return false; } - - /** - * Compare two numeric values of any type (double, int, uint). - */ + /** Compare two numeric values of any type (double, int, uint). */ public static int numericCompare(Number x, Number y) { if (x instanceof Double) { if (y instanceof Double) { diff --git a/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java new file mode 100644 index 000000000..9abe39b4b --- /dev/null +++ b/common/src/main/java/dev/cel/common/internal/DateTimeHelpers.java @@ -0,0 +1,261 @@ +// 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 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 java.time.DateTimeException; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Locale; + +/** Collection of utility methods for CEL datetime handlings. */ +@Internal +@SuppressWarnings("JavaInstantGetSecondsGetNano") // Intended within CEL. +public final class DateTimeHelpers { + public static final String UTC = "UTC"; + + // Timestamp for "0001-01-01T00:00:00Z" + private 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; + private static final int NANOS_PER_SECOND = 1000000000; + + /** + * Constructs a new {@link LocalDateTime} instance + * + * @param ts Timestamp protobuf object + * @param tz Timezone based on the CEL specification. This is either the canonical name from tz + * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: + * + * + * @return If an Invalid timezone is supplied. + */ + public static LocalDateTime newLocalDateTime(Timestamp ts, String tz) { + return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()) + .atZone(timeZone(tz)) + .toLocalDateTime(); + } + + /** + * Constructs a new {@link LocalDateTime} instance from a Java Instant. + * + * @param instant Instant object + * @param tz Timezone based on the CEL specification. This is either the canonical name from tz + * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: + * + * + * @return A new {@link LocalDateTime} instance. + */ + public static LocalDateTime newLocalDateTime(Instant instant, String tz) { + return instant.atZone(timeZone(tz)).toLocalDateTime(); + } + + /** + * Parse from RFC 3339 date string to {@link java.time.Instant}. + * + *

    Example of accepted format: "1972-01-01T10:00:20.021-05:00" + */ + public static Instant parse(String text) { + OffsetDateTime offsetDateTime = OffsetDateTime.parse(text); + Instant instant = offsetDateTime.toInstant(); + checkValid(instant); + + return instant; + } + + /** Adds a duration to an instant. */ + public static Instant add(Instant ts, Duration dur) { + Instant newInstant = ts.plus(dur); + checkValid(newInstant); + + return newInstant; + } + + /** Adds two durations */ + public static Duration add(Duration d1, Duration d2) { + Duration newDuration = d1.plus(d2); + checkValid(newDuration); + + return newDuration; + } + + /** Subtracts a duration to an instant. */ + public static Instant subtract(Instant ts, Duration dur) { + Instant newInstant = ts.minus(dur); + checkValid(newInstant); + + return newInstant; + } + + /** Subtract a duration from another. */ + public static Duration subtract(Duration d1, Duration d2) { + Duration newDuration = d1.minus(d2); + checkValid(newDuration); + + return newDuration; + } + + /** + * Formats a {@link Duration} into a minimal seconds-based representation. + * + *

    Note: follows {@code ProtoTimeUtils#toString(Duration)} implementation + */ + public static String toString(Duration duration) { + if (duration.isZero()) { + return "0s"; + } + + long totalNanos = duration.toNanos(); + StringBuilder sb = new StringBuilder(); + + if (totalNanos < 0) { + sb.append('-'); + totalNanos = -totalNanos; + } + + long seconds = totalNanos / 1_000_000_000; + int nanos = (int) (totalNanos % 1_000_000_000); + + sb.append(seconds); + + // Follows ProtoTimeUtils.toString(Duration) implementation + if (nanos > 0) { + sb.append('.'); + if (nanos % 1_000_000 == 0) { + // Millisecond precision (3 digits) + int millis = nanos / 1_000_000; + sb.append(String.format(Locale.US, "%03d", millis)); + } else if (nanos % 1_000 == 0) { + // Microsecond precision (6 digits) + int micros = nanos / 1_000; + sb.append(String.format(Locale.US, "%06d", micros)); + } else { + // Nanosecond precision (9 digits) + sb.append(String.format(Locale.US, "%09d", nanos)); + } + } + + sb.append('s'); + return sb.toString(); + } + + /** + * Get the DateTimeZone Instance. + * + * @param tz the ID of the datetime zone + * @return the ZoneId object + */ + private static ZoneId timeZone(String tz) { + try { + return ZoneId.of(tz); + } catch (DateTimeException e) { + // If timezone is not a string name (for example, 'US/Central'), it should be a numerical + // offset from UTC in the format [+/-]HH:MM. + try { + int ind = tz.indexOf(":"); + if (ind == -1) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + + int hourOffset = Integer.parseInt(tz.substring(0, ind)); + int minOffset = Integer.parseInt(tz.substring(ind + 1)); + // Ensures that the offset are properly formatted in [+/-]HH:MM to conform with + // ZoneOffset's format requirements. + // Example: "-9:30" -> "-09:30" and "9:30" -> "+09:30" + String formattedOffset = + ((hourOffset < 0) ? "-" : "+") + + String.format(Locale.US, "%02d:%02d", Math.abs(hourOffset), minOffset); + + return ZoneId.of(formattedOffset); + + } catch (DateTimeException e2) { + throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); + } + } + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Timestamp} is not valid. */ + private static void checkValid(Instant instant) { + long seconds = instant.getEpochSecond(); + + if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Timestamp is not valid. " + + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]. " + + "Nanos (%s) must be in range [0, +999,999,999].", + seconds, instant.getNano())); + } + } + + /** Throws an {@link IllegalArgumentException} if the given {@link Duration} is not valid. */ + private static void checkValid(Duration duration) { + long seconds = duration.getSeconds(); + int nanos = duration.getNano(); + if (!isDurationValid(seconds, nanos)) { + throw new IllegalArgumentException( + Strings.lenientFormat( + "Duration is not valid. " + + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. " + + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " + + "Nanos must have the same sign as seconds", + seconds, nanos)); + } + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@link Duration}. The {@code + * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos} + * value must be in the range [-999,999,999, +999,999,999]. + * + *

    Note: Durations less than one second are represented with a 0 {@code seconds} field + * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero + * value for the {@code nanos} field must be of the same sign as the {@code seconds} field. + */ + private static boolean isDurationValid(long seconds, int nanos) { + if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { + return false; + } + if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { + return false; + } + if (seconds < 0 || nanos < 0) { + if (seconds > 0 || nanos > 0) { + return false; + } + } + return true; + } + + private DateTimeHelpers() {} +} diff --git a/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java b/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java index df1907e42..a4614a2cb 100644 --- a/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java +++ b/common/src/main/java/dev/cel/common/internal/DefaultDescriptorPool.java @@ -119,6 +119,10 @@ public static DefaultDescriptorPool create( extensionRegistry); } + public static Descriptor getWellKnownProtoDescriptor(WellKnownProto wellKnownProto) { + return WELL_KNOWN_PROTO_TO_DESCRIPTORS.get(wellKnownProto); + } + @Override public Optional findDescriptor(String name) { return Optional.ofNullable(descriptorMap.get(name)); diff --git a/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java b/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java index c6fd4a0da..5bde34097 100644 --- a/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java +++ b/common/src/main/java/dev/cel/common/internal/FileDescriptorSetConverter.java @@ -15,6 +15,7 @@ package dev.cel.common.internal; import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CheckReturnValue; @@ -76,7 +77,15 @@ private static FileDescriptor readDescriptor( // Read dependencies first, they are needed to create the logical descriptor from the proto. List deps = new ArrayList<>(); for (String dep : fileProto.getDependencyList()) { - deps.add(readDescriptor(dep, descriptorProtos, descriptors)); + ImmutableCollection wktProtos = WellKnownProto.getByPathName(dep); + if (wktProtos.isEmpty()) { + deps.add(readDescriptor(dep, descriptorProtos, descriptors)); + } else { + // Ensure the generated message's descriptor is used as a dependency for WKTs to avoid + // issues with descriptor instance mismatch. + WellKnownProto wellKnownProto = wktProtos.iterator().next(); + deps.add(DefaultDescriptorPool.getWellKnownProtoDescriptor(wellKnownProto).getFile()); + } } // Create the file descriptor, cache, and return. try { 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 3461106ae..cd4be2f5e 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoAdapter.java @@ -125,7 +125,7 @@ public final class ProtoAdapter { public ProtoAdapter(DynamicProto dynamicProto, CelOptions celOptions) { this.dynamicProto = checkNotNull(dynamicProto); - this.protoLiteAdapter = new ProtoLiteAdapter(celOptions.enableUnsignedLongs()); + this.protoLiteAdapter = new ProtoLiteAdapter(celOptions); this.celOptions = celOptions; } 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 eb8c42862..e05d92f68 100644 --- a/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java +++ b/common/src/main/java/dev/cel/common/internal/ProtoLiteAdapter.java @@ -43,10 +43,12 @@ 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.values.CelByteString; +import java.time.Instant; import java.util.Map; import java.util.Map.Entry; @@ -63,7 +65,7 @@ @Immutable public final class ProtoLiteAdapter { - private final boolean enableUnsignedLongs; + private final CelOptions celOptions; @SuppressWarnings("unchecked") public MessageLite adaptValueToWellKnownProto(Object value, WellKnownProto wellKnownProto) { @@ -94,9 +96,9 @@ public MessageLite adaptValueToWellKnownProto(Object value, WellKnownProto wellK case UINT64_VALUE: return adaptValueToUint64(value); case DURATION: - return (Duration) value; + return adaptValueToProtoDuration(value); case TIMESTAMP: - return (Timestamp) value; + return adaptValueToProtoTimestamp(value); case EMPTY: case FIELD_MASK: // These two WKTs are typically used in context of JSON conversions, in which they are @@ -114,7 +116,6 @@ public Any adaptValueToAny(Object value, String typeName) { return packAnyMessage((MessageLite) value, typeName); } - // if (value instanceof NullValue) { if (value instanceof dev.cel.common.values.NullValue) { return packAnyMessage( Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(), WellKnownProto.JSON_VALUE); @@ -140,6 +141,10 @@ public Any adaptValueToAny(Object value, String typeName) { wellKnownProto = WellKnownProto.JSON_LIST_VALUE; } else if (value instanceof Map) { wellKnownProto = WellKnownProto.JSON_STRUCT_VALUE; + } else if (value instanceof Instant) { + wellKnownProto = WellKnownProto.TIMESTAMP; + } else if (value instanceof java.time.Duration) { + wellKnownProto = WellKnownProto.DURATION; } else { throw new IllegalArgumentException("Unsupported value conversion to any: " + value); } @@ -175,16 +180,26 @@ public Object adaptWellKnownProtoToValue( case STRING_VALUE: return ((StringValue) proto).getValue(); case UINT32_VALUE: - if (enableUnsignedLongs) { + if (celOptions.enableUnsignedLongs()) { return UnsignedLong.fromLongBits( Integer.toUnsignedLong(((UInt32Value) proto).getValue())); } return (long) ((UInt32Value) proto).getValue(); case UINT64_VALUE: - if (enableUnsignedLongs) { + if (celOptions.enableUnsignedLongs()) { return UnsignedLong.fromLongBits(((UInt64Value) proto).getValue()); } return ((UInt64Value) proto).getValue(); + case TIMESTAMP: + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return ProtoTimeUtils.toJavaInstant((Timestamp) proto); + } + return proto; + case DURATION: + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return ProtoTimeUtils.toJavaDuration((Duration) proto); + } + return proto; default: return proto; } @@ -324,7 +339,23 @@ private static Any packAnyMessage(MessageLite msg, String typeUrl) { .build(); } - public ProtoLiteAdapter(boolean enableUnsignedLongs) { - this.enableUnsignedLongs = enableUnsignedLongs; + private Timestamp adaptValueToProtoTimestamp(Object value) { + if (!celOptions.evaluateCanonicalTypesToNativeValues()) { + return (Timestamp) value; + } + + return ProtoTimeUtils.toProtoTimestamp((Instant) value); + } + + private Duration adaptValueToProtoDuration(Object value) { + if (!celOptions.evaluateCanonicalTypesToNativeValues()) { + return (Duration) value; + } + + return ProtoTimeUtils.toProtoDuration((java.time.Duration) value); + } + + public ProtoLiteAdapter(CelOptions celOptions) { + this.celOptions = celOptions; } } diff --git a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java index 78041e3be..21ee8053e 100644 --- a/common/src/main/java/dev/cel/common/internal/WellKnownProto.java +++ b/common/src/main/java/dev/cel/common/internal/WellKnownProto.java @@ -17,7 +17,9 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.Arrays.stream; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; import com.google.protobuf.Any; import com.google.protobuf.BoolValue; import com.google.protobuf.BytesValue; @@ -46,28 +48,71 @@ */ @Internal public enum WellKnownProto { - ANY_VALUE("google.protobuf.Any", Any.class), - DURATION("google.protobuf.Duration", Duration.class), - JSON_LIST_VALUE("google.protobuf.ListValue", ListValue.class), - JSON_STRUCT_VALUE("google.protobuf.Struct", Struct.class), - JSON_VALUE("google.protobuf.Value", Value.class), - TIMESTAMP("google.protobuf.Timestamp", Timestamp.class), + ANY_VALUE("google.protobuf.Any", "google/protobuf/any.proto", Any.class), + DURATION("google.protobuf.Duration", "google/protobuf/duration.proto", Duration.class), + JSON_LIST_VALUE("google.protobuf.ListValue", "google/protobuf/struct.proto", ListValue.class), + JSON_STRUCT_VALUE("google.protobuf.Struct", "google/protobuf/struct.proto", Struct.class), + JSON_VALUE("google.protobuf.Value", "google/protobuf/struct.proto", Value.class), + TIMESTAMP("google.protobuf.Timestamp", "google/protobuf/timestamp.proto", Timestamp.class), // Wrapper types - FLOAT_VALUE("google.protobuf.FloatValue", FloatValue.class, /* isWrapperType= */ true), - INT32_VALUE("google.protobuf.Int32Value", Int32Value.class, /* isWrapperType= */ true), - INT64_VALUE("google.protobuf.Int64Value", Int64Value.class, /* isWrapperType= */ true), - STRING_VALUE("google.protobuf.StringValue", StringValue.class, /* isWrapperType= */ true), - BOOL_VALUE("google.protobuf.BoolValue", BoolValue.class, /* isWrapperType= */ true), - BYTES_VALUE("google.protobuf.BytesValue", BytesValue.class, /* isWrapperType= */ true), - DOUBLE_VALUE("google.protobuf.DoubleValue", DoubleValue.class, /* isWrapperType= */ true), - UINT32_VALUE("google.protobuf.UInt32Value", UInt32Value.class, /* isWrapperType= */ true), - UINT64_VALUE("google.protobuf.UInt64Value", UInt64Value.class, /* isWrapperType= */ true), + FLOAT_VALUE( + "google.protobuf.FloatValue", + "google/protobuf/wrappers.proto", + FloatValue.class, + /* isWrapperType= */ true), + INT32_VALUE( + "google.protobuf.Int32Value", + "google/protobuf/wrappers.proto", + Int32Value.class, + /* isWrapperType= */ true), + INT64_VALUE( + "google.protobuf.Int64Value", + "google/protobuf/wrappers.proto", + Int64Value.class, + /* isWrapperType= */ true), + STRING_VALUE( + "google.protobuf.StringValue", + "google/protobuf/wrappers.proto", + StringValue.class, + /* isWrapperType= */ true), + BOOL_VALUE( + "google.protobuf.BoolValue", + "google/protobuf/wrappers.proto", + BoolValue.class, + /* isWrapperType= */ true), + BYTES_VALUE( + "google.protobuf.BytesValue", + "google/protobuf/wrappers.proto", + BytesValue.class, + /* isWrapperType= */ true), + DOUBLE_VALUE( + "google.protobuf.DoubleValue", + "google/protobuf/wrappers.proto", + DoubleValue.class, + /* isWrapperType= */ true), + UINT32_VALUE( + "google.protobuf.UInt32Value", + "google/protobuf/wrappers.proto", + UInt32Value.class, + /* isWrapperType= */ true), + UINT64_VALUE( + "google.protobuf.UInt64Value", + "google/protobuf/wrappers.proto", + UInt64Value.class, + /* isWrapperType= */ true), // These aren't explicitly called out as wrapper types in the spec, but behave like one, because // they are still converted into an equivalent primitive type. - EMPTY("google.protobuf.Empty", Empty.class, /* isWrapperType= */ true), - FIELD_MASK("google.protobuf.FieldMask", FieldMask.class, /* isWrapperType= */ true), - ; + EMPTY( + "google.protobuf.Empty", + "google/protobuf/empty.proto", + Empty.class, + /* isWrapperType= */ true), + FIELD_MASK( + "google.protobuf.FieldMask", + "google/protobuf/field_mask.proto", + FieldMask.class, + /* isWrapperType= */ true); private static final ImmutableMap TYPE_NAME_TO_WELL_KNOWN_PROTO_MAP = stream(WellKnownProto.values()) @@ -78,10 +123,27 @@ public enum WellKnownProto { stream(WellKnownProto.values()) .collect(toImmutableMap(WellKnownProto::messageClass, Function.identity())); + private static final ImmutableMultimap PATH_NAME_TO_WELL_KNOWN_PROTO_MAP = + initPathNameMap(); + + private static ImmutableMultimap initPathNameMap() { + ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); + for (WellKnownProto proto : values()) { + builder.put(proto.pathName(), proto); + } + return builder.build(); + } + private final String wellKnownProtoTypeName; + private final String pathName; private final Class clazz; private final boolean isWrapperType; + /** Gets the full proto path name (ex: google/protobuf/any.proto) */ + public String pathName() { + return pathName; + } + /** Gets the fully qualified prototype name (ex: google.protobuf.FloatValue) */ public String typeName() { return wellKnownProtoTypeName; @@ -92,6 +154,14 @@ public Class messageClass() { return clazz; } + /** + * Returns the well known proto given the full proto path (example: + * google/protobuf/timestamp.proto) + */ + public static ImmutableCollection getByPathName(String typeName) { + return PATH_NAME_TO_WELL_KNOWN_PROTO_MAP.get(typeName); + } + public static Optional getByTypeName(String typeName) { return Optional.ofNullable(TYPE_NAME_TO_WELL_KNOWN_PROTO_MAP.get(typeName)); } @@ -112,12 +182,14 @@ public boolean isWrapperType() { return isWrapperType; } - WellKnownProto(String wellKnownProtoTypeName, Class clazz) { - this(wellKnownProtoTypeName, clazz, /* isWrapperType= */ false); + WellKnownProto(String wellKnownProtoTypeName, String pathName, Class clazz) { + this(wellKnownProtoTypeName, pathName, clazz, /* isWrapperType= */ false); } - WellKnownProto(String wellKnownProtoFullName, Class clazz, boolean isWrapperType) { + WellKnownProto( + String wellKnownProtoFullName, String pathName, Class clazz, boolean isWrapperType) { this.wellKnownProtoTypeName = wellKnownProtoFullName; + this.pathName = pathName; this.clazz = clazz; this.isWrapperType = isWrapperType; } 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 ccb72f9e5..579fd31c5 100644 --- a/common/src/main/java/dev/cel/common/types/BUILD.bazel +++ b/common/src/main/java/dev/cel/common/types/BUILD.bazel @@ -174,6 +174,7 @@ java_library( ":type_providers", ":types", "//:auto_value", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common/internal:file_descriptor_converter", "@maven//:com_google_errorprone_error_prone_annotations", 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 38741f555..450b273c2 100644 --- a/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java +++ b/common/src/main/java/dev/cel/common/values/BaseProtoCelValueConverter.java @@ -53,23 +53,6 @@ public abstract class BaseProtoCelValueConverter extends CelValueConverter { public abstract CelValue fromProtoMessageToCelValue(MessageLite msg); - /** - * Adapts a {@link CelValue} to a native Java object. The CelValue is adapted into protobuf object - * when an equivalent exists. - */ - @Override - public Object fromCelValueToJavaObject(CelValue celValue) { - Preconditions.checkNotNull(celValue); - - if (celValue instanceof TimestampValue) { - return ProtoTimeUtils.toProtoTimestamp(((TimestampValue) celValue).value()); - } else if (celValue instanceof DurationValue) { - return ProtoTimeUtils.toProtoDuration(((DurationValue) celValue).value()); - } - - return super.fromCelValueToJavaObject(celValue); - } - /** {@inheritDoc} Protobuf semantics take precedence for conversion. */ @Override public CelValue fromJavaObjectToCelValue(Object value) { diff --git a/common/src/test/java/dev/cel/common/BUILD.bazel b/common/src/test/java/dev/cel/common/BUILD.bazel index c2ce1efdb..1ff6deb3b 100644 --- a/common/src/test/java/dev/cel/common/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/BUILD.bazel @@ -12,6 +12,7 @@ java_library( deps = [ "//:java_truth", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:cel_source", "//common:compiler_common", diff --git a/common/src/test/java/dev/cel/common/CelContainerTest.java b/common/src/test/java/dev/cel/common/CelContainerTest.java index f116039c8..cd7b73576 100644 --- a/common/src/test/java/dev/cel/common/CelContainerTest.java +++ b/common/src/test/java/dev/cel/common/CelContainerTest.java @@ -121,10 +121,6 @@ public void containerBuilder_addAliasError_throws(@TestParameter AliasingErrorTe private enum AliasingErrorTestCase { BAD_QUALIFIED_NAME( - "foo", - "invalid_qualified_name", - "alias must refer to a valid qualified name: invalid_qualified_name"), - BAD_QUALIFIED_NAME_2( "foo", ".bad.name", "qualified name must not begin with a leading '.': .bad.name"), BAD_ALIAS_NAME_1( "bad.alias", "b.c", "alias must be non-empty and simple (not qualified): alias=bad.alias"), diff --git a/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java b/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java index df4fc35fb..3184f5624 100644 --- a/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java +++ b/common/src/test/java/dev/cel/common/CelDescriptorUtilTest.java @@ -16,7 +16,6 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; 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.ImmutableSet; @@ -183,11 +182,16 @@ public void getFileDescriptorsFromFileDescriptorSet_incompleteFileSet() { .addFile(Timestamp.getDescriptor().getFile().toProto()) .addFile(TestAllTypes.getDescriptor().getFile().toProto()) .build(); - IllegalArgumentException e = - assertThrows( - IllegalArgumentException.class, - () -> CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fds)); - assertThat(e).hasMessageThat().contains("google/protobuf/any.proto"); + + ImmutableSet fileDescriptors = + CelDescriptorUtil.getFileDescriptorsFromFileDescriptorSet(fds); + + assertThat(fileDescriptors.stream().map(FileDescriptor::getName).collect(toImmutableSet())) + .containsExactly( + TestAllTypes.getDescriptor().getFile().getName(), + Duration.getDescriptor().getFile().getName(), + Struct.getDescriptor().getFile().getName(), + Timestamp.getDescriptor().getFile().getName()); } @Test diff --git a/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java b/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java index 56b386ba3..116222d68 100644 --- a/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelExprFormatterTest.java @@ -191,7 +191,7 @@ public void struct() throws Exception { assertThat(formattedExpr) .isEqualTo( "STRUCT [1] {\n" - + " name: TestAllTypes\n" + + " name: cel.expr.conformance.proto3.TestAllTypes\n" + " entries: {\n" + " ENTRY [2] {\n" + " field_key: single_int64\n" 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 1610acb52..7e75c9610 100644 --- a/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelExprVisitorTest.java @@ -206,14 +206,19 @@ public void visitSelect() throws Exception { assertThat(visited) .isEqualTo( VisitedReference.newBuilder() - .setStruct(CelStruct.newBuilder().setMessageName("TestAllTypes").build()) + .setStruct( + CelStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .setSelect( CelSelect.newBuilder() .setOperand( CelExpr.newBuilder() .setId(1) .setStruct( - CelStruct.newBuilder().setMessageName("TestAllTypes").build()) + CelStruct.newBuilder() + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") + .build()) .build()) .setField("single_int64") .build()) @@ -268,7 +273,7 @@ public void visitStruct_fieldkey() throws Exception { .setFieldKey("single_int64") .setValue(CelExpr.ofConstant(3, longConstant)) .build()) - .setMessageName("TestAllTypes") + .setMessageName("cel.expr.conformance.proto3.TestAllTypes") .build()) .build()); } diff --git a/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java b/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java index 2307b6219..a44c30bb1 100644 --- a/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java @@ -600,6 +600,35 @@ public void mutableMap_deepCopy() { .containsExactlyElementsIn(deepCopiedExpr.map().entries()); } + @Test + public void ofComprehension() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofComprehension( + CelMutableComprehension.create( + "iterVar", + CelMutableExpr.ofList( + CelMutableList.create(CelMutableExpr.ofConstant(CelConstant.ofValue(true)))), + "accuVar", + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofIdent("__result__"))); + + assertThat(mutableExpr.id()).isEqualTo(0L); + assertThat(mutableExpr.comprehension()) + .isEqualTo( + CelMutableComprehension.create( + "iterVar", + "", + CelMutableExpr.ofList( + CelMutableList.create(CelMutableExpr.ofConstant(CelConstant.ofValue(true)))), + "accuVar", + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofIdent("__result__"))); + } + @Test public void ofComprehension_withId() { CelMutableExpr mutableExpr = 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 d46607118..295c48a6e 100644 --- a/common/src/test/java/dev/cel/common/internal/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/internal/BUILD.bazel @@ -13,6 +13,7 @@ java_library( deps = [ "//:auto_value", "//:java_truth", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:options", "//common/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 3172258f4..0ce37ac1d 100644 --- a/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java +++ b/common/src/test/java/dev/cel/common/internal/ProtoAdapterTest.java @@ -41,6 +41,7 @@ import com.google.type.Expr; import dev.cel.common.CelOptions; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -88,11 +89,10 @@ public static List data() { {1.5D, Any.pack(DoubleValue.of(1.5D))}, {1.5D, Value.newBuilder().setNumberValue(1.5D).build()}, { - Duration.newBuilder().setSeconds(123).build(), - Duration.newBuilder().setSeconds(123).build(), + java.time.Duration.ofSeconds(123), Duration.newBuilder().setSeconds(123).build(), }, { - Duration.newBuilder().setSeconds(123).build(), + java.time.Duration.ofSeconds(123), Any.pack(Duration.newBuilder().setSeconds(123).build()), }, {1L, Int64Value.of(1L)}, @@ -132,12 +132,10 @@ public static List data() { .build(), }, { - Timestamp.newBuilder().setSeconds(123).build(), - Timestamp.newBuilder().setSeconds(123).build(), + Instant.ofEpochSecond(123), Timestamp.newBuilder().setSeconds(123).build(), }, { - Timestamp.newBuilder().setSeconds(123).build(), - Any.pack(Timestamp.newBuilder().setSeconds(123).build()), + Instant.ofEpochSecond(123), Any.pack(Timestamp.newBuilder().setSeconds(123).build()), }, {UnsignedLong.valueOf(1L), UInt64Value.of(1L)}, {UnsignedLong.valueOf(1L), Any.pack(UInt64Value.of(1L))}, @@ -152,7 +150,10 @@ public static List data() { @Test public void adaptValueToProto_bidirectionalConversion() { DynamicProto dynamicProto = DynamicProto.create(DefaultMessageFactory.INSTANCE); - ProtoAdapter protoAdapter = new ProtoAdapter(dynamicProto, CelOptions.DEFAULT); + ProtoAdapter protoAdapter = + new ProtoAdapter( + dynamicProto, + CelOptions.current().evaluateCanonicalTypesToNativeValues(true).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/internal/WellKnownProtoTest.java b/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java index bb75a341a..c12411d95 100644 --- a/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java +++ b/common/src/test/java/dev/cel/common/internal/WellKnownProtoTest.java @@ -16,7 +16,24 @@ import static com.google.common.truth.Truth.assertThat; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import com.google.protobuf.BoolValue; +import com.google.protobuf.BytesValue; +import com.google.protobuf.DoubleValue; +import com.google.protobuf.Duration; +import com.google.protobuf.Empty; +import com.google.protobuf.FieldMask; import com.google.protobuf.FloatValue; +import com.google.protobuf.Int32Value; +import com.google.protobuf.Int64Value; +import com.google.protobuf.ListValue; +import com.google.protobuf.StringValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Timestamp; +import com.google.protobuf.UInt32Value; +import com.google.protobuf.UInt64Value; +import com.google.protobuf.Value; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameters; import java.util.List; @@ -63,4 +80,67 @@ public void getByClass_success() { public void getByClass_unknownClass_returnsEmpty() { assertThat(WellKnownProto.getByClass(List.class)).isEmpty(); } + + @Test + public void getByPathName_singular() { + assertThat(WellKnownProto.getByPathName(Any.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.ANY_VALUE); + assertThat(WellKnownProto.getByPathName(Duration.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.DURATION); + assertThat(WellKnownProto.getByPathName(Timestamp.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.TIMESTAMP); + assertThat(WellKnownProto.getByPathName(Empty.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.EMPTY); + assertThat(WellKnownProto.getByPathName(FieldMask.getDescriptor().getFile().getName())) + .containsExactly(WellKnownProto.FIELD_MASK); + } + + @Test + public void getByPathName_json() { + ImmutableList expectedWellKnownProtos = + ImmutableList.of( + WellKnownProto.JSON_STRUCT_VALUE, + WellKnownProto.JSON_VALUE, + WellKnownProto.JSON_LIST_VALUE); + assertThat(WellKnownProto.getByPathName(Struct.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(ListValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + } + + @Test + public void getByPathName_wrappers() { + ImmutableList expectedWellKnownProtos = + ImmutableList.of( + WellKnownProto.FLOAT_VALUE, + WellKnownProto.DOUBLE_VALUE, + WellKnownProto.INT32_VALUE, + WellKnownProto.INT64_VALUE, + WellKnownProto.UINT32_VALUE, + WellKnownProto.UINT64_VALUE, + WellKnownProto.BOOL_VALUE, + WellKnownProto.STRING_VALUE, + WellKnownProto.BYTES_VALUE); + + assertThat(WellKnownProto.getByPathName(FloatValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(DoubleValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(Int32Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(Int64Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(UInt32Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(UInt64Value.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(BoolValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(StringValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + assertThat(WellKnownProto.getByPathName(BytesValue.getDescriptor().getFile().getName())) + .containsExactlyElementsIn(expectedWellKnownProtos); + } } 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 11bf79f27..ed58a93cc 100644 --- a/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java +++ b/common/src/test/java/dev/cel/common/navigation/CelNavigableExprVisitorTest.java @@ -595,7 +595,7 @@ public void messageConstruction_allNodesReturned() throws Exception { constExpr, CelExpr.ofStruct( 1, - "TestAllTypes", + "cel.expr.conformance.proto3.TestAllTypes", ImmutableList.of(CelExpr.ofStructEntry(2, "single_int64", constExpr, false)))); } @@ -621,7 +621,7 @@ public void messageConstruction_filterStruct_allNodesReturned() throws Exception .isEqualTo( CelExpr.ofStruct( 1, - "TestAllTypes", + "cel.expr.conformance.proto3.TestAllTypes", ImmutableList.of( CelExpr.ofStructEntry( 2, "single_int64", CelExpr.ofConstant(3, CelConstant.ofValue(1)), false)))); 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 9d9132cbb..131344e1a 100644 --- a/common/src/test/java/dev/cel/common/values/BUILD.bazel +++ b/common/src/test/java/dev/cel/common/values/BUILD.bazel @@ -11,7 +11,7 @@ java_library( "//:java_truth", "//bundle:cel", "//common:cel_ast", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common:options", "//common:runtime_exception", "//common/internal:cel_descriptor_pools", @@ -21,7 +21,6 @@ java_library( "//common/internal:dynamic_proto", "//common/internal:proto_message_factory", "//common/internal:proto_time_utils", - "//common/internal:well_known_proto", "//common/types", "//common/types:type_providers", "//common/values", 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 b7e72c6b8..234411ce7 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoCelValueConverterTest.java @@ -16,12 +16,10 @@ import static com.google.common.truth.Truth.assertThat; -import com.google.protobuf.Duration; -import com.google.protobuf.Timestamp; import dev.cel.common.internal.DefaultDescriptorPool; import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; -import dev.cel.common.internal.ProtoTimeUtils; +import java.time.Duration; import java.time.Instant; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,13 +33,13 @@ public class ProtoCelValueConverterTest { DefaultDescriptorPool.INSTANCE, DynamicProto.create(DefaultMessageFactory.INSTANCE)); @Test - public void fromCelValueToJavaObject_returnsTimestampValue() { - Timestamp timestamp = - (Timestamp) + public void fromCelValueToJavaObject_returnsInstantValue() { + Instant timestamp = + (Instant) PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( TimestampValue.create(Instant.ofEpochSecond(50))); - assertThat(timestamp).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); + assertThat(timestamp).isEqualTo(Instant.ofEpochSecond(50)); } @Test @@ -49,9 +47,9 @@ public void fromCelValueToJavaObject_returnsDurationValue() { Duration duration = (Duration) PROTO_CEL_VALUE_CONVERTER.fromCelValueToJavaObject( - DurationValue.create(java.time.Duration.ofSeconds(10))); + DurationValue.create(Duration.ofSeconds(10))); - assertThat(duration).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(10)); + assertThat(duration).isEqualTo(Duration.ofSeconds(10)); } @Test 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 891a7d0e2..f9bee1af1 100644 --- a/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java +++ b/common/src/test/java/dev/cel/common/values/ProtoMessageValueProviderTest.java @@ -28,7 +28,6 @@ import dev.cel.common.internal.DefaultMessageFactory; import dev.cel.common.internal.DynamicProto; import dev.cel.common.internal.ProtoMessageFactory; -import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.expr.conformance.proto2.TestAllTypes; import dev.cel.expr.conformance.proto2.TestAllTypesExtensions; import java.time.Duration; @@ -66,7 +65,8 @@ public void newValue_createEmptyProtoMessage() { @Test public void newValue_createProtoMessage_fieldsPopulated() { ProtoMessageValueProvider protoMessageValueProvider = - ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO); + ProtoMessageValueProvider.newInstance( + CelOptions.current().evaluateCanonicalTypesToNativeValues(true).build(), DYNAMIC_PROTO); ProtoMessageValue protoMessageValue = (ProtoMessageValue) @@ -89,9 +89,9 @@ public void newValue_createProtoMessage_fieldsPopulated() { "single_string", "hello", "single_timestamp", - ProtoTimeUtils.fromSecondsToTimestamp(50), + Instant.ofEpochSecond(50), "single_duration", - ProtoTimeUtils.fromSecondsToDuration(100))) + Duration.ofSeconds(100))) .get(); assertThat(protoMessageValue.isZeroValue()).isFalse(); diff --git a/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel b/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel index aa10b1324..92375ec8f 100644 --- a/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel +++ b/compiler/src/main/java/dev/cel/compiler/tools/BUILD.bazel @@ -19,7 +19,7 @@ java_binary( "//bundle:environment_exception", "//bundle:environment_yaml_parser", "//common:cel_ast", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common:options", "//common:proto_ast", "//compiler", diff --git a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel index 69755324d..7283e2977 100644 --- a/conformance/src/test/java/dev/cel/conformance/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/BUILD.bazel @@ -49,7 +49,6 @@ java_library( tags = ["conformance_maven"], deps = MAVEN_JAR_DEPS + [ "//:java_truth", - "//parser:parser_factory", # TODO: Remove next OSS release "//testing:expr_value_utils", "@cel_spec//proto/cel/expr:expr_java_proto", "@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto", diff --git a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java index 50fb0e086..89598ed3e 100644 --- a/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java +++ b/conformance/src/test/java/dev/cel/conformance/ConformanceTestRunner.java @@ -108,7 +108,7 @@ protected List getChildren() { @Override protected Description describeChild(ConformanceTest child) { return Description.createTestDescription( - ConformanceTest.class, child.getName(), ConformanceTest.class.getAnnotations()); + ConformanceTests.class, child.getName(), ConformanceTest.class.getAnnotations()); } @Override diff --git a/extensions/src/main/java/dev/cel/extensions/CelExtensions.java b/extensions/src/main/java/dev/cel/extensions/CelExtensions.java index 5d70f8dfc..2d14ed118 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelExtensions.java +++ b/extensions/src/main/java/dev/cel/extensions/CelExtensions.java @@ -338,7 +338,9 @@ public static ImmutableSet getAllFunctionNames() { stream(CelListsExtensions.Function.values()) .map(CelListsExtensions.Function::getFunction), stream(CelRegexExtensions.Function.values()) - .map(CelRegexExtensions.Function::getFunction)) + .map(CelRegexExtensions.Function::getFunction), + stream(CelComprehensionsExtensions.Function.values()) + .map(CelComprehensionsExtensions.Function::getFunction)) .collect(toImmutableSet()); } diff --git a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java index a384b09e2..6ed4d1491 100644 --- a/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java +++ b/extensions/src/main/java/dev/cel/extensions/CelOptionalLibrary.java @@ -50,6 +50,7 @@ import dev.cel.runtime.CelInternalRuntimeLibrary; import dev.cel.runtime.CelRuntimeBuilder; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Collection; import java.util.List; import java.util.Map; @@ -394,6 +395,10 @@ private static boolean isZeroValue(Object val) { } else if (val instanceof NullValue) { // A null value always represents an absent value return true; + } else if (val instanceof Instant) { + return val.equals(Instant.EPOCH); + } else if (val instanceof java.time.Duration) { + return val.equals(java.time.Duration.ZERO); } // Unknown. Assume that it is non-zero. diff --git a/extensions/src/main/java/dev/cel/extensions/README.md b/extensions/src/main/java/dev/cel/extensions/README.md index a5b65168c..10c5217e8 100644 --- a/extensions/src/main/java/dev/cel/extensions/README.md +++ b/extensions/src/main/java/dev/cel/extensions/README.md @@ -912,7 +912,8 @@ regex.extractAll('id:123, id:456', 'assa') == [] regex.extractAll('testuser@testdomain', '(.*)@([^.]*)') \\ Runtime Error multiple capture group ``` -## TwoVarComprehensions + +## Comprehensions TwoVarComprehensions introduces support for two-variable comprehensions. diff --git a/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java b/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java index db126f6ee..61922f70f 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelExtensionsTest.java @@ -187,6 +187,7 @@ public void getAllFunctionNames() { "lists.@sortByAssociatedKeys", "regex.replace", "regex.extract", - "regex.extractAll"); + "regex.extractAll", + "cel.@mapInsert"); } } diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java index a806037da..a51fbedbf 100644 --- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java +++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java @@ -34,7 +34,6 @@ import dev.cel.common.CelOverloadDecl; import dev.cel.common.CelValidationException; import dev.cel.common.CelVarDecl; -import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -52,6 +51,8 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelRuntime; import dev.cel.runtime.InterpreterUtil; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.Optional; @@ -70,16 +71,12 @@ private enum ConstantTestCases { UINT("5u", "0u", SimpleType.UINT, UnsignedLong.valueOf(5)), BOOL("true", "false", SimpleType.BOOL, true), BYTES("b'abc'", "b''", SimpleType.BYTES, CelByteString.copyFromUtf8("abc")), - DURATION( - "duration('180s')", - "duration('0s')", - SimpleType.DURATION, - ProtoTimeUtils.fromSecondsToDuration(180)), + DURATION("duration('180s')", "duration('0s')", SimpleType.DURATION, Duration.ofMinutes(3)), TIMESTAMP( "timestamp(1685552643)", "timestamp(0)", SimpleType.TIMESTAMP, - ProtoTimeUtils.fromSecondsToTimestamp(1685552643)); + Instant.ofEpochSecond(1685552643)); private final String sourceWithNonZeroValue; private final String sourceWithZeroValue; diff --git a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java index 00c5c2e3a..10ea36e48 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java +++ b/optimizer/src/main/java/dev/cel/optimizer/AstMutator.java @@ -60,7 +60,7 @@ public final class AstMutator { private final long iterationLimit; /** - * Returns a new instance of a AST mutator with the iteration limit set. + * Returns a new instance of an AST mutator with the iteration limit set. * *

    Mutation is performed by walking the existing AST until the expression node to replace is * found, then the new subtree is walked to complete the mutation. Visiting of each node @@ -203,22 +203,22 @@ public CelMutableAst renumberIdsConsecutively(CelMutableAst mutableAst) { * @param newIterVarPrefix Prefix to use for new iteration variable identifier name. For example, * providing @c will produce @c0:0, @c0:1, @c1:0, @c2:0... as new names. * @param newAccuVarPrefix Prefix to use for new accumulation variable identifier name. - * @param incrementSerially If true, indices for the mangled variables are incremented serially - * per occurrence regardless of their nesting level or its types. */ public MangledComprehensionAst mangleComprehensionIdentifierNames( CelMutableAst ast, String newIterVarPrefix, - String newAccuVarPrefix, - boolean incrementSerially) { + String newIterVar2Prefix, + String newAccuVarPrefix) { CelNavigableMutableAst navigableMutableAst = CelNavigableMutableAst.fromAst(ast); Predicate comprehensionIdentifierPredicate = x -> true; comprehensionIdentifierPredicate = comprehensionIdentifierPredicate .and(node -> node.getKind().equals(Kind.COMPREHENSION)) - .and(node -> !node.expr().comprehension().iterVar().startsWith(newIterVarPrefix)) - .and(node -> !node.expr().comprehension().accuVar().startsWith(newAccuVarPrefix)); - + .and(node -> !node.expr().comprehension().iterVar().startsWith(newIterVarPrefix + ":")) + .and(node -> !node.expr().comprehension().accuVar().startsWith(newAccuVarPrefix + ":")) + .and( + node -> + !node.expr().comprehension().iterVar2().startsWith(newIterVar2Prefix + ":")); LinkedHashMap comprehensionsToMangle = navigableMutableAst .getRoot() @@ -231,13 +231,17 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( // Ensure the iter_var or the comprehension result is actually referenced in the // loop_step. If it's not, we can skip mangling. String iterVar = node.expr().comprehension().iterVar(); + String iterVar2 = node.expr().comprehension().iterVar2(); String result = node.expr().comprehension().result().ident().name(); return CelNavigableMutableExpr.fromExpr(node.expr().comprehension().loopStep()) .allNodes() .filter(subNode -> subNode.getKind().equals(Kind.IDENT)) .map(subNode -> subNode.expr().ident()) .anyMatch( - ident -> ident.name().contains(iterVar) || ident.name().contains(result)); + ident -> + ident.name().contains(iterVar) + || ident.name().contains(iterVar2) + || ident.name().contains(result)); }) .collect( Collectors.toMap( @@ -245,6 +249,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( v -> { CelMutableComprehension comprehension = v.expr().comprehension(); String iterVar = comprehension.iterVar(); + String iterVar2 = comprehension.iterVar2(); // Identifiers to mangle could be the iteration variable, comprehension // result or both, but at least one has to exist. // As an example, [1,2].map(i, 3) would result in optional.empty for iteration @@ -258,6 +263,16 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( && loopStepNode.expr().ident().name().equals(iterVar)) .map(CelNavigableMutableExpr::id) .findAny(); + Optional iterVar2Id = + CelNavigableMutableExpr.fromExpr(comprehension.loopStep()) + .allNodes() + .filter( + loopStepNode -> + !iterVar2.isEmpty() + && loopStepNode.getKind().equals(Kind.IDENT) + && loopStepNode.expr().ident().name().equals(iterVar2)) + .map(CelNavigableMutableExpr::id) + .findAny(); Optional iterVarType = iterVarId.map( id -> @@ -269,6 +284,17 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( "Checked type not present for iteration" + " variable: " + iterVarId))); + Optional iterVar2Type = + iterVar2Id.map( + id -> + navigableMutableAst + .getType(id) + .orElseThrow( + () -> + new NoSuchElementException( + "Checked type not present for iteration" + + " variable: " + + iterVar2Id))); CelType resultType = navigableMutableAst .getType(comprehension.result().id()) @@ -278,7 +304,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( "Result type was not present for the comprehension ID: " + comprehension.result().id())); - return MangledComprehensionType.of(iterVarType, resultType); + return MangledComprehensionType.of(iterVarType, iterVar2Type, resultType); }, (x, y) -> { throw new IllegalStateException( @@ -301,38 +327,25 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( MangledComprehensionType comprehensionEntryType = comprehensionEntry.getValue(); CelMutableExpr comprehensionExpr = comprehensionNode.expr(); - MangledComprehensionName mangledComprehensionName; - if (incrementSerially) { - // In case of applying CSE via cascaded cel.binds, not only is mangling based on level/types - // meaningless (because all comprehensions are nested anyways, thus all indices would be - // uinque), - // it can lead to an erroneous result due to extracting a common subexpr with accu_var at - // the wrong scope. - // Example: "[1].exists(k, k > 1) && [2].exists(l, l > 1). The loop step for both branches - // are identical, but shouldn't be extracted. - String mangledIterVarName = newIterVarPrefix + ":" + iterCount; - String mangledResultName = newAccuVarPrefix + ":" + iterCount; - mangledComprehensionName = - MangledComprehensionName.of(mangledIterVarName, mangledResultName); - mangledIdentNamesToType.put(mangledComprehensionName, comprehensionEntry.getValue()); - } else { - mangledComprehensionName = - getMangledComprehensionName( - newIterVarPrefix, - newAccuVarPrefix, - comprehensionNode, - comprehensionLevelToType, - comprehensionEntryType); - } + MangledComprehensionName mangledComprehensionName = + getMangledComprehensionName( + newIterVarPrefix, + newIterVar2Prefix, + newAccuVarPrefix, + comprehensionNode, + comprehensionLevelToType, + comprehensionEntryType); mangledIdentNamesToType.put(mangledComprehensionName, comprehensionEntryType); String iterVar = comprehensionExpr.comprehension().iterVar(); + String iterVar2 = comprehensionExpr.comprehension().iterVar2(); String accuVar = comprehensionExpr.comprehension().accuVar(); mutatedComprehensionExpr = mangleIdentsInComprehensionExpr( mutatedComprehensionExpr, comprehensionExpr, iterVar, + iterVar2, accuVar, mangledComprehensionName); // Repeat the mangling process for the macro source. @@ -341,6 +354,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( newSource, mutatedComprehensionExpr, iterVar, + iterVar2, mangledComprehensionName, comprehensionExpr.id()); iterCount++; @@ -360,6 +374,7 @@ public MangledComprehensionAst mangleComprehensionIdentifierNames( private static MangledComprehensionName getMangledComprehensionName( String newIterVarPrefix, + String newIterVar2Prefix, String newResultPrefix, CelNavigableMutableExpr comprehensionNode, Table comprehensionLevelToType, @@ -377,7 +392,11 @@ private static MangledComprehensionName getMangledComprehensionName( newIterVarPrefix + ":" + comprehensionNestingLevel + ":" + uniqueTypeIdx; String mangledResultName = newResultPrefix + ":" + comprehensionNestingLevel + ":" + uniqueTypeIdx; - mangledComprehensionName = MangledComprehensionName.of(mangledIterVarName, mangledResultName); + String mangledIterVar2Name = + newIterVar2Prefix + ":" + comprehensionNestingLevel + ":" + uniqueTypeIdx; + + mangledComprehensionName = + MangledComprehensionName.of(mangledIterVarName, mangledIterVar2Name, mangledResultName); comprehensionLevelToType.put( comprehensionNestingLevel, comprehensionEntryType, mangledComprehensionName); } @@ -530,6 +549,7 @@ private CelMutableExpr mangleIdentsInComprehensionExpr( CelMutableExpr root, CelMutableExpr comprehensionExpr, String originalIterVar, + String originalIterVar2, String originalAccuVar, MangledComprehensionName mangledComprehensionName) { CelMutableComprehension comprehension = comprehensionExpr.comprehension(); @@ -538,11 +558,18 @@ private CelMutableExpr mangleIdentsInComprehensionExpr( replaceIdentName(comprehensionExpr, originalAccuVar, mangledComprehensionName.resultName()); comprehension.setIterVar(mangledComprehensionName.iterVarName()); + // Most standard macros set accu_var as __result__, but not all (ex: cel.bind). if (comprehension.accuVar().equals(originalAccuVar)) { comprehension.setAccuVar(mangledComprehensionName.resultName()); } + if (!originalIterVar2.isEmpty()) { + comprehension.setIterVar2(mangledComprehensionName.iterVar2Name()); + replaceIdentName( + comprehension.loopStep(), originalIterVar2, mangledComprehensionName.iterVar2Name()); + } + return mutateExpr(NO_OP_ID_GENERATOR, root, comprehensionExpr, comprehensionExpr.id()); } @@ -581,6 +608,7 @@ private CelMutableSource mangleIdentsInMacroSource( CelMutableSource sourceBuilder, CelMutableExpr mutatedComprehensionExpr, String originalIterVar, + String originalIterVar2, MangledComprehensionName mangledComprehensionName, long originalComprehensionId) { if (!sourceBuilder.getMacroCalls().containsKey(originalComprehensionId)) { @@ -604,7 +632,6 @@ private CelMutableSource mangleIdentsInMacroSource( // macro call expression. CelMutableExpr identToMangle = macroExpr.call().args().get(0); if (identToMangle.ident().name().equals(originalIterVar)) { - // if (identToMangle.identOrDefault().name().equals(originalIterVar)) { macroExpr = mutateExpr( NO_OP_ID_GENERATOR, @@ -612,6 +639,18 @@ private CelMutableSource mangleIdentsInMacroSource( CelMutableExpr.ofIdent(mangledComprehensionName.iterVarName()), identToMangle.id()); } + if (!originalIterVar2.isEmpty()) { + // Similarly by convention, iter_var2 is always the second argument of the macro call. + identToMangle = macroExpr.call().args().get(1); + if (identToMangle.ident().name().equals(originalIterVar2)) { + macroExpr = + mutateExpr( + NO_OP_ID_GENERATOR, + macroExpr, + CelMutableExpr.ofIdent(mangledComprehensionName.iterVar2Name()), + identToMangle.id()); + } + } newSource.addMacroCalls(originalComprehensionId, macroExpr); @@ -815,7 +854,7 @@ private static void unwrapListArgumentsInMacroCallExpr( newMacroCall.addArgs( existingMacroCall.args().get(0)); // iter_var is first argument of the call by convention - CelMutableList extraneousList = null; + CelMutableList extraneousList; if (loopStepArgs.size() == 2) { extraneousList = loopStepArgs.get(1).list(); } else { @@ -895,14 +934,22 @@ private static MangledComprehensionAst of( @AutoValue public abstract static class MangledComprehensionType { - /** Type of iter_var */ + /** + * Type of iter_var. Empty if iter_var is not referenced in the expression anywhere (ex: "i" in + * "[1].exists(i, true)" + */ public abstract Optional iterVarType(); + /** Type of iter_var2. */ + public abstract Optional iterVar2Type(); + /** Type of comprehension result */ public abstract CelType resultType(); - private static MangledComprehensionType of(Optional iterVarType, CelType resultType) { - return new AutoValue_AstMutator_MangledComprehensionType(iterVarType, resultType); + private static MangledComprehensionType of( + Optional iterVarType, Optional iterVarType2, CelType resultType) { + return new AutoValue_AstMutator_MangledComprehensionType( + iterVarType, iterVarType2, resultType); } } @@ -916,11 +963,16 @@ public abstract static class MangledComprehensionName { /** Mangled name for iter_var */ public abstract String iterVarName(); + /** Mangled name for iter_var2 */ + public abstract String iterVar2Name(); + /** Mangled name for comprehension result */ public abstract String resultName(); - private static MangledComprehensionName of(String iterVarName, String resultName) { - return new AutoValue_AstMutator_MangledComprehensionName(iterVarName, resultName); + private static MangledComprehensionName of( + String iterVarName, String iterVar2Name, String resultName) { + return new AutoValue_AstMutator_MangledComprehensionName( + iterVarName, iterVar2Name, resultName); } } } 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 7728c0ac8..6057b3105 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/BUILD.bazel @@ -19,13 +19,16 @@ java_library( ":default_optimizer_constants", "//:auto_value", "//bundle:cel", + "//checker:standard_decl", "//common:cel_ast", "//common:cel_source", "//common:compiler_common", "//common:mutable_ast", "//common/ast", "//common/ast:mutable_expr", + "//common/internal:date_time_helpers", "//common/navigation:mutable_navigation", + "//common/types", "//extensions:optional_library", "//optimizer:ast_optimizer", "//optimizer:mutable_ast", 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 e46ee2e98..294589af2 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizer.java @@ -16,6 +16,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.MoreCollectors.onlyElement; +import static dev.cel.checker.CelStandardDeclarations.StandardFunction.DURATION; +import static dev.cel.checker.CelStandardDeclarations.StandardFunction.TIMESTAMP; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; @@ -36,14 +38,18 @@ import dev.cel.common.ast.CelMutableExpr.CelMutableMap; import dev.cel.common.ast.CelMutableExpr.CelMutableStruct; import dev.cel.common.ast.CelMutableExprConverter; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.navigation.CelNavigableMutableAst; import dev.cel.common.navigation.CelNavigableMutableExpr; +import dev.cel.common.types.SimpleType; import dev.cel.extensions.CelOptionalLibrary.Function; 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; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -88,6 +94,9 @@ private static CelMutableExpr newOptionalNoneExpr() { @Override public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) throws CelOptimizationException { + // Override the environment's expected type to generally allow all subtrees to be folded. + Cel optimizerEnv = cel.toCelBuilder().setResultType(SimpleType.DYN).build(); + CelMutableAst mutableAst = CelMutableAst.fromCelAst(ast); int iterCount = 0; boolean continueFolding = true; @@ -112,7 +121,7 @@ public OptimizationResult optimize(CelAbstractSyntaxTree ast, Cel cel) mutatedResult = maybePruneBranches(mutableAst, foldableExpr.expr()); if (!mutatedResult.isPresent()) { // Evaluate the call then fold - mutatedResult = maybeFold(cel, mutableAst, foldableExpr); + mutatedResult = maybeFold(optimizerEnv, mutableAst, foldableExpr); } if (!mutatedResult.isPresent()) { @@ -138,6 +147,14 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) { return false; } + // Timestamps/durations in CEL are calls, but they are effectively treated as literals. + // Expressions like timestamp(123) cannot be folded directly, but arithmetics involving + // timestamps can be optimized. + // Ex: timestamp(123) - timestamp(100) = duration("23s") + if (isCallTimestampOrDuration(navigableExpr.expr().call())) { + return false; + } + CelMutableCall mutableCall = navigableExpr.expr().call(); String functionName = mutableCall.function(); @@ -163,6 +180,16 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) { && cond.constant().getKind().equals(CelConstant.Kind.BOOLEAN_VALUE); } + if (functionName.equals(Operator.EQUALS.getFunction()) + || functionName.equals(Operator.NOT_EQUALS.getFunction())) { + if (mutableCall.args().stream() + .anyMatch(node -> isExprConstantOfKind(node, CelConstant.Kind.BOOLEAN_VALUE)) + || mutableCall.args().stream() + .allMatch(node -> node.getKind().equals(Kind.CONSTANT))) { + return true; + } + } + if (functionName.equals(Operator.IN.getFunction())) { return canFoldInOperator(navigableExpr); } @@ -183,14 +210,14 @@ private boolean canFold(CelNavigableMutableExpr navigableExpr) { private boolean containsFoldableFunctionOnly(CelNavigableMutableExpr navigableExpr) { return navigableExpr .allNodes() - .allMatch( - node -> { - if (node.getKind().equals(Kind.CALL)) { - return foldableFunctions.contains(node.expr().call().function()); - } - - return true; - }); + .filter(node -> node.getKind().equals(Kind.CALL)) + .map(node -> node.expr().call()) + .allMatch(call -> foldableFunctions.contains(call.function())); + } + + private static boolean isCallTimestampOrDuration(CelMutableCall call) { + return call.function().equals(TIMESTAMP.functionName()) + || call.function().equals(DURATION.functionName()); } private static boolean canFoldInOperator(CelNavigableMutableExpr navigableExpr) { @@ -304,6 +331,22 @@ private Optional maybeAdaptEvaluatedResult(Object result) { } return Optional.of(CelMutableExpr.ofMap(CelMutableMap.create(mapEntries))); + } else if (result instanceof Duration) { + String durationStrArg = DateTimeHelpers.toString((Duration) result); + CelMutableCall durationCall = + CelMutableCall.create( + DURATION.functionName(), + CelMutableExpr.ofConstant(CelConstant.ofValue(durationStrArg))); + + return Optional.of(CelMutableExpr.ofCall(durationCall)); + } else if (result instanceof Instant) { + String timestampStrArg = result.toString(); + CelMutableCall timestampCall = + CelMutableCall.create( + TIMESTAMP.functionName(), + CelMutableExpr.ofConstant(CelConstant.ofValue(timestampStrArg))); + + return Optional.of(CelMutableExpr.ofCall(timestampCall)); } // Evaluated result cannot be folded (e.g: unknowns) @@ -389,6 +432,38 @@ private Optional maybePruneBranches( } } } + } else if (function.equals(Operator.EQUALS.getFunction()) + || function.equals(Operator.NOT_EQUALS.getFunction())) { + CelMutableExpr lhs = call.args().get(0); + CelMutableExpr rhs = call.args().get(1); + boolean lhsIsBoolean = isExprConstantOfKind(lhs, CelConstant.Kind.BOOLEAN_VALUE); + boolean rhsIsBoolean = isExprConstantOfKind(rhs, CelConstant.Kind.BOOLEAN_VALUE); + boolean invertCondition = function.equals(Operator.NOT_EQUALS.getFunction()); + Optional replacementExpr = Optional.empty(); + + if (lhs.getKind().equals(Kind.CONSTANT) && rhs.getKind().equals(Kind.CONSTANT)) { + // If both args are const, don't prune any branches and let maybeFold method evaluate this + // subExpr + return Optional.empty(); + } else if (lhsIsBoolean) { + boolean cond = invertCondition != lhs.constant().booleanValue(); + replacementExpr = + Optional.of( + cond + ? rhs + : CelMutableExpr.ofCall( + CelMutableCall.create(Operator.LOGICAL_NOT.getFunction(), rhs))); + } else if (rhsIsBoolean) { + boolean cond = invertCondition != rhs.constant().booleanValue(); + replacementExpr = + Optional.of( + cond + ? lhs + : CelMutableExpr.ofCall( + CelMutableCall.create(Operator.LOGICAL_NOT.getFunction(), lhs))); + } + + return replacementExpr.map(node -> astMutator.replaceSubtree(mutableAst, node, expr.id())); } return Optional.empty(); @@ -659,6 +734,10 @@ public static Builder newBuilder() { ConstantFoldingOptions() {} } + private static boolean isExprConstantOfKind(CelMutableExpr expr, CelConstant.Kind constantKind) { + return expr.getKind().equals(Kind.CONSTANT) && expr.constant().getKind().equals(constantKind); + } + private ConstantFoldingOptimizer(ConstantFoldingOptions constantFoldingOptions) { this.constantFoldingOptions = constantFoldingOptions; this.astMutator = AstMutator.newInstance(constantFoldingOptions.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 a39ff813a..eceb0bbe1 100644 --- a/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java +++ b/optimizer/src/main/java/dev/cel/optimizer/optimizers/SubexpressionOptimizer.java @@ -91,6 +91,7 @@ public class SubexpressionOptimizer implements CelAstOptimizer { 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"; @@ -136,8 +137,8 @@ private OptimizationResult optimizeUsingCelBlock(CelAbstractSyntaxTree ast, Cel astMutator.mangleComprehensionIdentifierNames( astToModify, MANGLED_COMPREHENSION_ITER_VAR_PREFIX, - MANGLED_COMPREHENSION_ACCU_VAR_PREFIX, - /* incrementSerially= */ false); + MANGLED_COMPREHENSION_ITER_VAR2_PREFIX, + MANGLED_COMPREHENSION_ACCU_VAR_PREFIX); astToModify = mangledComprehensionAst.mutableAst(); CelMutableSource sourceToModify = astToModify.source(); @@ -197,6 +198,12 @@ private OptimizationResult optimizeUsingCelBlock(CelAbstractSyntaxTree ast, Cel iterVarType -> newVarDecls.add( CelVarDecl.newVarDeclaration(name.iterVarName(), iterVarType))); + type.iterVar2Type() + .ifPresent( + iterVar2Type -> + newVarDecls.add( + CelVarDecl.newVarDeclaration(name.iterVar2Name(), iterVar2Type))); + newVarDecls.add(CelVarDecl.newVarDeclaration(name.resultName(), type.resultType())); }); @@ -446,16 +453,16 @@ private boolean containsComprehensionIdentInSubexpr(CelNavigableMutableExpr navE navExpr .allNodes() .filter( - node -> - node.getKind().equals(Kind.IDENT) - && (node.expr() - .ident() - .name() - .startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) - || node.expr() - .ident() - .name() - .startsWith(MANGLED_COMPREHENSION_ACCU_VAR_PREFIX))) + node -> { + if (!node.getKind().equals(Kind.IDENT)) { + return false; + } + + String identName = node.expr().ident().name(); + return identName.startsWith(MANGLED_COMPREHENSION_ITER_VAR_PREFIX) + || identName.startsWith(MANGLED_COMPREHENSION_ITER_VAR2_PREFIX) + || identName.startsWith(MANGLED_COMPREHENSION_ACCU_VAR_PREFIX); + }) .collect(toImmutableList()); if (comprehensionIdents.isEmpty()) { diff --git a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java index 37641fac3..e03f95aa4 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/AstMutatorTest.java @@ -60,8 +60,9 @@ public class AstMutatorTest { .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setOptions(CelOptions.current().populateMacroCalls(true).build()) .addMessageTypes(TestAllTypes.getDescriptor()) - .addCompilerLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) - .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .addCompilerLibraries( + CelOptionalLibrary.INSTANCE, CelExtensions.bindings(), CelExtensions.comprehensions()) + .addRuntimeLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.comprehensions()) .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())) .addVar("x", SimpleType.INT) @@ -583,7 +584,7 @@ public void struct_replaceValue() throws Exception { CelMutableAst result = AST_MUTATOR.replaceSubtree(mutableAst, newExpr, 3); assertThat(CEL_UNPARSER.unparse(result.toParsedAst())) - .isEqualTo("TestAllTypes{single_int64: 5}"); + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes{single_int64: 5}"); } @Test @@ -666,14 +667,14 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); assertThat(mangledAst.getExpr().toString()) .isEqualTo( "COMPREHENSION [13] {\n" - + " iter_var: @it:0\n" + + " iter_var: @it:0:0\n" + " iter_range: {\n" + " LIST [1] {\n" + " elements: {\n" @@ -681,7 +682,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:0\n" + + " accu_var: @ac:0:0\n" + " accu_init: {\n" + " CONSTANT [6] { value: false }\n" + " }\n" @@ -693,7 +694,7 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " function: !_\n" + " args: {\n" + " IDENT [7] {\n" - + " name: @ac:0\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + " }\n" @@ -705,21 +706,87 @@ public void mangleComprehensionVariable_singleMacro() throws Exception { + " function: _||_\n" + " args: {\n" + " IDENT [10] {\n" - + " name: @ac:0\n" + + " name: @ac:0:0\n" + " }\n" + " IDENT [5] {\n" - + " name: @it:0\n" + + " name: @it:0:0\n" + " }\n" + " }\n" + " }\n" + " }\n" + " result: {\n" + " IDENT [12] {\n" - + " name: @ac:0\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + "}"); - assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@it:0, @it:0)"); + assertThat(CEL_UNPARSER.unparse(mangledAst)).isEqualTo("[false].exists(@it:0:0, @it:0:0)"); + assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(false); + assertConsistentMacroCalls(ast); + } + + @Test + public void mangleComprehensionVariable_withTwoIterVars_singleMacro() throws Exception { + CelAbstractSyntaxTree ast = CEL.compile("[false].exists(i, v, v)").getAst(); + + CelAbstractSyntaxTree mangledAst = + AST_MUTATOR + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") + .mutableAst() + .toParsedAst(); + + assertThat(mangledAst.getExpr().toString()) + .isEqualTo( + "COMPREHENSION [14] {\n" + + " iter_var: @it:0:0\n" + + " iter_var2: @it2:0:0\n" + + " iter_range: {\n" + + " LIST [1] {\n" + + " elements: {\n" + + " CONSTANT [2] { value: false }\n" + + " }\n" + + " }\n" + + " }\n" + + " accu_var: @ac:0:0\n" + + " accu_init: {\n" + + " CONSTANT [7] { value: false }\n" + + " }\n" + + " loop_condition: {\n" + + " CALL [10] {\n" + + " function: @not_strictly_false\n" + + " args: {\n" + + " CALL [9] {\n" + + " function: !_\n" + + " args: {\n" + + " IDENT [8] {\n" + + " name: @ac:0:0\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " loop_step: {\n" + + " CALL [12] {\n" + + " function: _||_\n" + + " args: {\n" + + " IDENT [11] {\n" + + " name: @ac:0:0\n" + + " }\n" + + " IDENT [6] {\n" + + " name: @it2:0:0\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " result: {\n" + + " IDENT [13] {\n" + + " name: @ac:0:0\n" + + " }\n" + + " }\n" + + "}"); + assertThat(CEL_UNPARSER.unparse(mangledAst)) + .isEqualTo("[false].exists(@it:0:0, @it2:0:0, @it2:0:0)"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(false); assertConsistentMacroCalls(ast); } @@ -734,7 +801,7 @@ public void mangleComprehensionVariable_adjacentMacros_sameIterVarTypes() throws CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", false) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -746,6 +813,31 @@ public void mangleComprehensionVariable_adjacentMacros_sameIterVarTypes() throws assertConsistentMacroCalls(ast); } + @Test + public void mangleComprehensionVariable_withTwoIterVars_adjacentMacros_sameIterVarTypes() + throws Exception { + CelAbstractSyntaxTree ast = + CEL.compile( + // LHS & RHS are same expressions, just different var names + "[1, 2].transformMap(i, v, [1,2].transformMap(i, v, i)) == " + + "[1, 2].transformMap(x, y, [1,2].transformMap(x, y, x))") + .getAst(); + + CelAbstractSyntaxTree mangledAst = + AST_MUTATOR + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") + .mutableAst() + .toParsedAst(); + + 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))"); + assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval()).isEqualTo(true); + assertConsistentMacroCalls(ast); + } + @Test public void mangleComprehensionVariable_adjacentMacros_differentIterVarTypes() throws Exception { CelAbstractSyntaxTree ast = @@ -756,7 +848,7 @@ public void mangleComprehensionVariable_adjacentMacros_differentIterVarTypes() t CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", false) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -780,7 +872,7 @@ public void mangleComprehensionVariable_macroSourceDisabled_macroCallMapIsEmpty( CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); @@ -793,14 +885,14 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); assertThat(mangledAst.getExpr().toString()) .isEqualTo( "COMPREHENSION [27] {\n" - + " iter_var: @it:1\n" + + " iter_var: @it:0:0\n" + " iter_range: {\n" + " LIST [1] {\n" + " elements: {\n" @@ -810,7 +902,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:1\n" + + " accu_var: @ac:0:0\n" + " accu_init: {\n" + " CONSTANT [20] { value: false }\n" + " }\n" @@ -822,7 +914,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: !_\n" + " args: {\n" + " IDENT [21] {\n" - + " name: @ac:1\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + " }\n" @@ -834,20 +926,20 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: _||_\n" + " args: {\n" + " IDENT [24] {\n" - + " name: @ac:1\n" + + " name: @ac:0:0\n" + " }\n" + " COMPREHENSION [19] {\n" - + " iter_var: @it:0\n" + + " iter_var: @it:1:0\n" + " iter_range: {\n" + " LIST [5] {\n" + " elements: {\n" + " IDENT [6] {\n" - + " name: @it:1\n" + + " name: @it:0:0\n" + " }\n" + " }\n" + " }\n" + " }\n" - + " accu_var: @ac:0\n" + + " accu_var: @ac:1:0\n" + " accu_init: {\n" + " CONSTANT [12] { value: false }\n" + " }\n" @@ -859,7 +951,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: !_\n" + " args: {\n" + " IDENT [13] {\n" - + " name: @ac:0\n" + + " name: @ac:1:0\n" + " }\n" + " }\n" + " }\n" @@ -871,13 +963,13 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " function: _||_\n" + " args: {\n" + " IDENT [16] {\n" - + " name: @ac:0\n" + + " name: @ac:1:0\n" + " }\n" + " CALL [10] {\n" + " function: _==_\n" + " args: {\n" + " IDENT [9] {\n" - + " name: @it:0\n" + + " name: @it:1:0\n" + " }\n" + " CONSTANT [11] { value: 1 }\n" + " }\n" @@ -887,7 +979,7 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " result: {\n" + " IDENT [18] {\n" - + " name: @ac:0\n" + + " name: @ac:1:0\n" + " }\n" + " }\n" + " }\n" @@ -896,13 +988,12 @@ public void mangleComprehensionVariable_nestedMacroWithShadowedVariables() throw + " }\n" + " result: {\n" + " IDENT [26] {\n" - + " name: @ac:1\n" + + " name: @ac:0:0\n" + " }\n" + " }\n" + "}"); - assertThat(CEL_UNPARSER.unparse(mangledAst)) - .isEqualTo("[x].exists(@it:1, [@it:1].exists(@it:0, @it:0 == 1))"); + .isEqualTo("[x].exists(@it:0:0, [@it:0:0].exists(@it:1:0, @it:1:0 == 1))"); assertThat(CEL.createProgram(CEL.check(mangledAst).getAst()).eval(ImmutableMap.of("x", 1))) .isEqualTo(true); assertConsistentMacroCalls(ast); @@ -914,7 +1005,7 @@ public void mangleComprehensionVariable_hasMacro_noOp() throws Exception { CelAbstractSyntaxTree mangledAst = AST_MUTATOR - .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@ac", true) + .mangleComprehensionIdentifierNames(CelMutableAst.fromCelAst(ast), "@it", "@it2", "@ac") .mutableAst() .toParsedAst(); 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 d5d204834..821a31d69 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/ConstantFoldingOptimizerTest.java @@ -46,6 +46,11 @@ @RunWith(TestParameterInjector.class) public class ConstantFoldingOptimizerTest { + private static final CelOptions CEL_OPTIONS = + CelOptions.current() + .enableTimestampEpoch(true) + .evaluateCanonicalTypesToNativeValues(true) + .build(); private static final Cel CEL = CelFactory.standardCelBuilder() .addVar("x", SimpleType.DYN) @@ -60,19 +65,20 @@ public class ConstantFoldingOptimizerTest { CelFunctionBinding.from("get_true_overload", ImmutableList.of(), unused -> true)) .addMessageTypes(TestAllTypes.getDescriptor()) .setContainer(CelContainer.ofName("cel.expr.conformance.proto3")) + .setOptions(CEL_OPTIONS) .addCompilerLibraries( CelExtensions.bindings(), CelOptionalLibrary.INSTANCE, - CelExtensions.math(CelOptions.DEFAULT), + CelExtensions.math(CEL_OPTIONS), CelExtensions.strings(), - CelExtensions.sets(CelOptions.DEFAULT), - CelExtensions.encoders()) + CelExtensions.sets(CEL_OPTIONS), + CelExtensions.encoders(CEL_OPTIONS)) .addRuntimeLibraries( CelOptionalLibrary.INSTANCE, - CelExtensions.math(CelOptions.DEFAULT), + CelExtensions.math(CEL_OPTIONS), CelExtensions.strings(), - CelExtensions.sets(CelOptions.DEFAULT), - CelExtensions.encoders()) + CelExtensions.sets(CEL_OPTIONS), + CelExtensions.encoders(CEL_OPTIONS)) .build(); private static final CelOptimizer CEL_OPTIMIZER = @@ -153,18 +159,18 @@ public class ConstantFoldingOptimizerTest { + " optional.of(1), ?y: optional.none()}'}") @TestParameters( "{source: 'TestAllTypes{single_int64: 1 + 2 + 3 + x}', " - + " expected: 'TestAllTypes{single_int64: 6 + x}'}") + + " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_int64: 6 + x}'}") @TestParameters( "{source: 'TestAllTypes{?single_int64: optional.ofNonZeroValue(1)}', " - + " expected: 'TestAllTypes{single_int64: 1}'}") + + " expected: 'cel.expr.conformance.proto3.TestAllTypes{single_int64: 1}'}") @TestParameters( "{source: 'TestAllTypes{?single_int64: optional.ofNonZeroValue(0)}', " - + " expected: 'TestAllTypes{}'}") + + " expected: 'cel.expr.conformance.proto3.TestAllTypes{}'}") @TestParameters( "{source: 'TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32:" + " optional.of(4), ?single_uint64: optional.ofNonZeroValue(x)}', expected:" - + " 'TestAllTypes{single_int64: 1, single_int32: 4, ?single_uint64:" - + " optional.ofNonZeroValue(x)}'}") + + " 'cel.expr.conformance.proto3.TestAllTypes{single_int64: 1, single_int32: 4," + + " ?single_uint64: optional.ofNonZeroValue(x)}'}") @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\")'}") @@ -189,6 +195,37 @@ public class ConstantFoldingOptimizerTest { @TestParameters("{source: 'sets.contains([1], [1])', expected: 'true'}") @TestParameters( "{source: 'cel.bind(r0, [1, 2, 3], cel.bind(r1, 1 in r0, r1))', expected: 'true'}") + @TestParameters("{source: 'x == true', expected: 'x'}") + @TestParameters("{source: 'true == x', expected: 'x'}") + @TestParameters("{source: 'x == false', expected: '!x'}") + @TestParameters("{source: 'false == x', expected: '!x'}") + @TestParameters("{source: 'true == false', expected: 'false'}") + @TestParameters("{source: 'true == true', expected: 'true'}") + @TestParameters("{source: 'false == true', expected: 'false'}") + @TestParameters("{source: 'false == false', expected: 'true'}") + @TestParameters("{source: '10 == 42', expected: 'false'}") + @TestParameters("{source: '42 == 42', expected: 'true'}") + @TestParameters("{source: 'x != true', expected: '!x'}") + @TestParameters("{source: 'true != x', expected: '!x'}") + @TestParameters("{source: 'x != false', expected: 'x'}") + @TestParameters("{source: 'false != x', expected: 'x'}") + @TestParameters("{source: 'true != false', expected: 'true'}") + @TestParameters("{source: 'true != true', expected: 'false'}") + @TestParameters("{source: 'false != true', expected: 'true'}") + @TestParameters("{source: 'false != false', expected: 'false'}") + @TestParameters("{source: '10 != 42', expected: 'true'}") + @TestParameters("{source: '42 != 42', expected: 'false'}") + @TestParameters("{source: '[\"foo\",\"bar\"] == [\"foo\",\"bar\"]', expected: 'true'}") + @TestParameters("{source: '[\"bar\",\"foo\"] == [\"foo\",\"bar\"]', expected: 'false'}") + @TestParameters("{source: 'duration(\"1h\") - duration(\"60m\")', expected: 'duration(\"0s\")'}") + @TestParameters( + "{source: 'duration(\"2h23m42s12ms42us92ns\") + duration(\"129481231298125ns\")', expected:" + + " 'duration(\"138103.243340217s\")'}") + @TestParameters( + "{source: 'timestamp(900000) - timestamp(100)', expected: 'duration(\"899900s\")'}") + @TestParameters( + "{source: 'timestamp(\"2000-01-01T00:02:03.2123Z\") + duration(\"25h2m32s42ms53us29ns\")'," + + " expected: 'timestamp(\"2000-01-02T01:04:35.254353029Z\")'}") // TODO: Support folding lists with mixed types. This requires mutable lists. // @TestParameters("{source: 'dyn([1]) + [1.0]'}") public void constantFold_success(String source, String expected) throws Exception { @@ -213,6 +250,10 @@ public void constantFold_success(String source, String expected) throws Exceptio @TestParameters( "{source: '[1, 2, 3].map(i, [1, 2, 3].map(j, i * j).filter(k, k % 2 == x))', " + "expected: '[1, 2, 3].map(i, [1, 2, 3].map(j, i * j).filter(k, k % 2 == x))'}") + @TestParameters("{source: '[1, 2, 3, 4].all(i, v, i < v)', expected: 'true'}") + @TestParameters( + "{source: '[1 + 1, 2 + 2, 3 + 3].all(i, v, i < 5 && v < x)', " + + "expected: '[2, 4, 6].all(i, v, i < 5 && v < x)'}") @TestParameters( "{source: '[{}, {\"a\": 1}, {\"b\": 2}].filter(m, has(m.a))', expected: '[{\"a\": 1}]'}") @TestParameters( @@ -241,8 +282,9 @@ public void constantFold_macros_macroCallMetadataPopulated(String source, String .addMessageTypes(TestAllTypes.getDescriptor()) .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setOptions(CelOptions.current().populateMacroCalls(true).build()) - .addCompilerLibraries(CelExtensions.bindings(), CelOptionalLibrary.INSTANCE) - .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .addCompilerLibraries( + CelExtensions.bindings(), CelExtensions.optional(), CelExtensions.comprehensions()) + .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) .build(); CelOptimizer celOptimizer = CelOptimizerFactory.standardCelOptimizerBuilder(cel) @@ -316,9 +358,15 @@ public void constantFold_macros_withoutMacroCallMetadata(String source) throws E @TestParameters("{source: 'optional.none()'}") @TestParameters("{source: '[optional.none()]'}") @TestParameters("{source: '[?x.?y]'}") - @TestParameters("{source: 'TestAllTypes{single_int32: x, repeated_int32: [1, 2, 3]}'}") + @TestParameters( + "{source: 'cel.expr.conformance.proto3.TestAllTypes{" + + "single_int32: x, repeated_int32: [1, 2, 3]}'}") @TestParameters("{source: 'get_true() == get_true()'}") @TestParameters("{source: 'get_true() == true'}") + @TestParameters("{source: 'x == x'}") + @TestParameters("{source: 'x == 42'}") + @TestParameters("{source: 'timestamp(100)'}") + @TestParameters("{source: 'duration(\"1h\")'}") public void constantFold_noOp(String source) throws Exception { CelAbstractSyntaxTree ast = CEL.compile(source).getAst(); @@ -342,6 +390,20 @@ public void constantFold_addFoldableFunction_success() throws Exception { assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("true"); } + @Test + public void constantFold_withExpectedResultTypeSet_success() throws Exception { + Cel cel = CelFactory.standardCelBuilder().setResultType(SimpleType.STRING).build(); + CelOptimizer optimizer = + CelOptimizerFactory.standardCelOptimizerBuilder(cel) + .addAstOptimizers(ConstantFoldingOptimizer.getInstance()) + .build(); + CelAbstractSyntaxTree ast = cel.compile("string(!true)").getAst(); + + CelAbstractSyntaxTree optimizedAst = optimizer.optimize(ast); + + assertThat(CEL_UNPARSER.unparse(optimizedAst)).isEqualTo("\"false\""); + } + @Test public void constantFold_withMacroCallPopulated_comprehensionsAreReplacedWithNotSet() throws Exception { 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 0b8a1e3f3..93f7778ec 100644 --- a/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java +++ b/optimizer/src/test/java/dev/cel/optimizer/optimizers/SubexpressionOptimizerBaselineTest.java @@ -35,7 +35,6 @@ import dev.cel.expr.conformance.proto3.NestedTestAllTypes; import dev.cel.expr.conformance.proto3.TestAllTypes; import dev.cel.extensions.CelExtensions; -import dev.cel.extensions.CelOptionalLibrary; import dev.cel.optimizer.CelOptimizer; import dev.cel.optimizer.CelOptimizerFactory; import dev.cel.optimizer.optimizers.SubexpressionOptimizer.SubexpressionOptimizerOptions; @@ -97,15 +96,13 @@ public void allOptimizers_producesSameEvaluationResult( throws Exception { skipBaselineVerification(); CelAbstractSyntaxTree ast = CEL.compile(cseTestCase.source).getAst(); - Object expectedEvalResult = - CEL.createProgram(ast) - .eval(ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "opt_x", Optional.of(5L))); + ImmutableMap inputMap = + ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "y", 6L, "opt_x", Optional.of(5L)); + Object expectedEvalResult = CEL.createProgram(ast).eval(inputMap); CelAbstractSyntaxTree optimizedAst = cseTestOptimizer.cseOptimizer.optimize(ast); - Object optimizedEvalResult = - CEL.createProgram(optimizedAst) - .eval(ImmutableMap.of("msg", TEST_ALL_TYPES_INPUT, "x", 5L, "opt_x", Optional.of(5L))); + Object optimizedEvalResult = CEL.createProgram(optimizedAst).eval(inputMap); assertThat(optimizedEvalResult).isEqualTo(expectedEvalResult); } @@ -119,7 +116,13 @@ public void subexpression_unparsed() throws Exception { boolean resultPrinted = false; for (CseTestOptimizer cseTestOptimizer : CseTestOptimizer.values()) { String optimizerName = cseTestOptimizer.name(); - CelAbstractSyntaxTree optimizedAst = cseTestOptimizer.cseOptimizer.optimize(ast); + CelAbstractSyntaxTree optimizedAst; + try { + optimizedAst = cseTestOptimizer.cseOptimizer.optimize(ast); + } catch (Exception e) { + testOutput().printf("[%s]: Optimization Error: %s", optimizerName, e); + continue; + } if (!resultPrinted) { Object optimizedEvalResult = CEL.createProgram(optimizedAst) @@ -264,8 +267,9 @@ private static CelBuilder newCelBuilder() { .setStandardMacros(CelStandardMacro.STANDARD_MACROS) .setOptions( CelOptions.current().enableTimestampEpoch(true).populateMacroCalls(true).build()) - .addCompilerLibraries(CelOptionalLibrary.INSTANCE, CelExtensions.bindings()) - .addRuntimeLibraries(CelOptionalLibrary.INSTANCE) + .addCompilerLibraries( + CelExtensions.optional(), CelExtensions.bindings(), CelExtensions.comprehensions()) + .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) .addFunctionDeclarations( CelFunctionDecl.newFunctionDeclaration( "pure_custom_func", @@ -279,6 +283,7 @@ private static CelBuilder newCelBuilder() { CelFunctionBinding.from("non_pure_custom_func_overload", Long.class, val -> val), CelFunctionBinding.from("pure_custom_func_overload", Long.class, val -> val)) .addVar("x", SimpleType.DYN) + .addVar("y", SimpleType.DYN) .addVar("opt_x", OptionalType.create(SimpleType.DYN)) .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); } @@ -399,16 +404,40 @@ private enum CseTestCase { MULTIPLE_MACROS_3( "[1].exists(i, i > 0) && [1].exists(j, j > 0) && [1].exists(k, k > 1) && [2].exists(l, l >" + " 1)"), + MULTIPLE_MACROS_COMP_V2_1( + // Note that all of these have different iteration variables, but they are still logically + // the same. + "size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + " + + "size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) " + + " == 4"), + MULTIPLE_MACROS_COMP_V2_2( + "[1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && " + + "[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_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]]"), + NESTED_MACROS_COMP_V2_2( + "[1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]]"), ADJACENT_NESTED_MACROS( "[1,2,3].map(i, [1, 2, 3].map(i, i + 1)) == [1,2,3].map(j, [1, 2, 3].map(j, j + 1))"), + ADJACENT_NESTED_MACROS_COMP_V2( + "[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))"), INCLUSION_LIST("1 in [1,2,3] && 2 in [1,2,3] && 3 in [3, [1,2,3]] && 1 in [1,2,3]"), INCLUSION_MAP("2 in {'a': 1, 2: {true: false}, 3: {true: false}}"), MACRO_ITER_VAR_NOT_REFERENCED( "[1,2].map(i, [1, 2].map(i, [3,4])) == [[[3, 4], [3, 4]], [[3, 4], [3, 4]]]"), + MACRO_ITER_VAR2_NOT_REFERENCED( + "[1,2].transformList(i, v, [1, 2].transformList(i, v, [3,4])) == " + + "[[[3, 4], [3, 4]], [[3, 4], [3, 4]]]"), MACRO_SHADOWED_VARIABLE("[x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3"), MACRO_SHADOWED_VARIABLE_2("[\"foo\", \"bar\"].map(x, [x + x, x + x]).map(x, [x + x, x + x])"), + MACRO_SHADOWED_VARIABLE_COMP_V2_1( + "[x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3"), + MACRO_SHADOWED_VARIABLE_COMP_V2_2( + "[\"foo\", \"bar\"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y])"), PRESENCE_TEST("has({'a': true}.a) && {'a':true}['a']"), PRESENCE_TEST_2("has({'a': true}.a) && has({'a': true}.a)"), PRESENCE_TEST_WITH_TERNARY( diff --git a/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline b/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline index 0d3f25bcf..cf6fc3e5f 100644 --- a/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/constfold_before_subexpression_unparsed.baseline @@ -298,6 +298,36 @@ Result: false [BLOCK_RECURSION_DEPTH_8]: false [BLOCK_RECURSION_DEPTH_9]: false +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +Result: false +[BLOCK_COMMON_SUBEXPR_ONLY]: false +[BLOCK_RECURSION_DEPTH_1]: false +[BLOCK_RECURSION_DEPTH_2]: false +[BLOCK_RECURSION_DEPTH_3]: false +[BLOCK_RECURSION_DEPTH_4]: false +[BLOCK_RECURSION_DEPTH_5]: false +[BLOCK_RECURSION_DEPTH_6]: false +[BLOCK_RECURSION_DEPTH_7]: false +[BLOCK_RECURSION_DEPTH_8]: false +[BLOCK_RECURSION_DEPTH_9]: false + 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]] =====> @@ -328,6 +358,36 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: true [BLOCK_RECURSION_DEPTH_9]: true +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]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + +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]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + 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)) =====> @@ -343,6 +403,21 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: true [BLOCK_RECURSION_DEPTH_9]: true +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]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + 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] =====> @@ -388,6 +463,21 @@ Result: true [BLOCK_RECURSION_DEPTH_8]: true [BLOCK_RECURSION_DEPTH_9]: true +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]: true +[BLOCK_RECURSION_DEPTH_1]: true +[BLOCK_RECURSION_DEPTH_2]: true +[BLOCK_RECURSION_DEPTH_3]: true +[BLOCK_RECURSION_DEPTH_4]: true +[BLOCK_RECURSION_DEPTH_5]: true +[BLOCK_RECURSION_DEPTH_6]: true +[BLOCK_RECURSION_DEPTH_7]: true +[BLOCK_RECURSION_DEPTH_8]: true +[BLOCK_RECURSION_DEPTH_9]: true + Test case: MACRO_SHADOWED_VARIABLE Source: [x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3 =====> @@ -418,6 +508,36 @@ Result: [[[foofoo, foofoo, foofoo, foofoo], [foofoo, foofoo, foofoo, foofoo]], [ [BLOCK_RECURSION_DEPTH_8]: cel.@block([["foofoo", "foofoo", "foofoo", "foofoo"], ["barbar", "barbar", "barbar", "barbar"]], [[@index0, @index0], [@index1, @index1]]) [BLOCK_RECURSION_DEPTH_9]: cel.@block([["foofoo", "foofoo", "foofoo", "foofoo"], ["barbar", "barbar", "barbar", "barbar"]], [[@index0, @index0], [@index1, @index1]]) +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +Result: CelUnknownSet{attributes=[], unknownExprIds=[6]} +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([x - y - 1, @index0 > 3], [@index1 ? @index0 : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([x - y, @index0 - 1, @index1 > 3, @index2 ? @index1 : 5, [@index3]], @index4.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index2) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([x - y - 1, @index0 > 3, [@index1 ? @index0 : 5]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([x - y - 1 > 3, @index0 ? (x - y - 1) : 5, [@index1]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([x - y - 1 > 3, [@index0 ? (x - y - 1) : 5]], @index1.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) + +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +Result: {0=[0, [0, foofoo, 0, foofoo]], 1=[2, [2, barbar, 2, barbar]]} +[BLOCK_COMMON_SUBEXPR_ONLY]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[0, "foofoo", 0, "foofoo"], [0, @index0], [2, "barbar", 2, "barbar"], [2, @index2]], {0: @index1, 1: @index3}) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[0, [0, "foofoo", 0, "foofoo"]], [2, [2, "barbar", 2, "barbar"]]], {0: @index0, 1: @index1}) +[BLOCK_RECURSION_DEPTH_3]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_4]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_5]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_6]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_7]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_8]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} +[BLOCK_RECURSION_DEPTH_9]: {0: [0, [0, "foofoo", 0, "foofoo"]], 1: [2, [2, "barbar", 2, "barbar"]]} + Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> 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 43782477c..103fcac54 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 @@ -1647,6 +1647,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + CALL [20] { + function: size + args: { + LIST [21] { + elements: { + IDENT [22] { + name: @index0 + } + } + } + } + } + COMPREHENSION [23] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [24] { + elements: { + CONSTANT [25] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [26] { value: false } + } + loop_condition: { + CALL [27] { + function: @not_strictly_false + args: { + CALL [28] { + function: !_ + args: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [30] { + function: _||_ + args: { + IDENT [31] { + name: @ac:0:0 + } + CALL [32] { + function: _&&_ + args: { + CALL [33] { + function: _>_ + args: { + IDENT [34] { + name: @it:0:0 + } + CONSTANT [35] { value: 1 } + } + } + CALL [36] { + function: _>=_ + args: { + IDENT [37] { + name: @it2:0:0 + } + CONSTANT [38] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [39] { + name: @ac:0:0 + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index2 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index1 + } + IDENT [48] { + name: @index1 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1732,7 +2174,269 @@ CALL [1] { IDENT [27] { name: @it:1:0 } - CONSTANT [28] { value: 1 } + CONSTANT [28] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + LIST [31] { + elements: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + } + } + CALL [11] { + function: _==_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [13] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [14] { + elements: { + } + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @ac:0:0 + } + LIST [18] { + elements: { + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_var2: @it2: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: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @it:1:0 + } + IDENT [29] { + name: @it2:1:0 + } + } + } + CONSTANT [30] { value: 1 } } } } @@ -1741,7 +2445,7 @@ CALL [1] { } } result: { - IDENT [29] { + IDENT [31] { name: @ac:1:0 } } @@ -1752,20 +2456,20 @@ CALL [1] { } } result: { - IDENT [30] { + IDENT [32] { name: @ac:0:0 } } } - LIST [31] { + LIST [33] { elements: { - IDENT [32] { + IDENT [34] { name: @index1 } - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } } @@ -1774,14 +2478,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1792,82 +2497,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1878,21 +2599,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2012,6 +2733,125 @@ CALL [1] { } } } +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)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + COMPREHENSION [7] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:0:0 + } + IDENT [13] { + name: @it:0:0 + } + COMPREHENSION [14] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [15] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [16] { + + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: cel.@mapInsert + args: { + IDENT [19] { + name: @ac:1:0 + } + IDENT [20] { + name: @it:1:0 + } + CALL [21] { + function: _+_ + args: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + IDENT [24] { + name: @it2:1:0 + } + } + } + CONSTANT [25] { value: 1 } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [27] { + name: @ac:0:0 + } + } + } + } + } + CALL [28] { + function: _==_ + args: { + IDENT [29] { + name: @index1 + } + IDENT [30] { + name: @index1 + } + } + } + } +} 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] =====> @@ -2117,24 +2957,150 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 3 } + CONSTANT [8] { value: 4 } + } + } + LIST [9] { + elements: { + IDENT [10] { + name: @index1 + } + IDENT [11] { + name: @index1 + } + } + } + } + } + CALL [12] { + function: _==_ + args: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:0:0 + } + LIST [19] { + elements: { + COMPREHENSION [20] { + iter_var: @it:1:0 + iter_range: { + IDENT [21] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [22] { + elements: { + } + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @ac:1:0 + } + LIST [26] { + elements: { + IDENT [27] { + name: @index1 + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [30] { + elements: { + IDENT [31] { + name: @index2 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [32] { + name: @index2 } } } @@ -2142,8 +3108,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2179,6 +3145,7 @@ CALL [1] { args: { COMPREHENSION [13] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [14] { name: @index0 @@ -2205,6 +3172,7 @@ CALL [1] { elements: { COMPREHENSION [20] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [21] { name: @index0 @@ -2505,6 +3473,258 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _-_ + args: { + CALL [4] { + function: _-_ + args: { + IDENT [5] { + name: x + } + IDENT [6] { + name: y + } + } + } + CONSTANT [7] { value: 1 } + } + } + CALL [8] { + function: _>_ + args: { + IDENT [9] { + name: @index0 + } + CONSTANT [10] { value: 3 } + } + } + } + } + CALL [11] { + function: _||_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [13] { + elements: { + CALL [14] { + function: _?_:_ + args: { + IDENT [15] { + name: @index1 + } + IDENT [16] { + name: @index0 + } + CONSTANT [17] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [18] { value: false } + } + loop_condition: { + CALL [19] { + function: @not_strictly_false + args: { + CALL [20] { + function: !_ + args: { + IDENT [21] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [22] { + function: _||_ + args: { + IDENT [23] { + name: @ac:0:0 + } + CALL [24] { + function: _>_ + args: { + CALL [25] { + function: _-_ + args: { + CALL [26] { + function: _-_ + args: { + IDENT [27] { + name: @it:0:0 + } + IDENT [28] { + name: @it2:0:0 + } + } + } + CONSTANT [29] { value: 1 } + } + } + CONSTANT [30] { value: 3 } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + IDENT [32] { + name: @index1 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3022,7 +4242,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 5bce4d49a..134e7effd 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 @@ -2220,6 +2220,650 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + CALL [8] { + function: _+_ + args: { + CALL [9] { + function: _+_ + args: { + CALL [10] { + function: _+_ + args: { + CALL [11] { + function: size + args: { + LIST [12] { + elements: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [15] { value: false } + } + loop_condition: { + CALL [16] { + function: @not_strictly_false + args: { + CALL [17] { + function: !_ + args: { + IDENT [18] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [19] { + function: _||_ + args: { + IDENT [20] { + name: @ac:0:0 + } + CALL [21] { + function: _&&_ + args: { + CALL [22] { + function: _>_ + args: { + IDENT [23] { + name: @it:0:0 + } + CONSTANT [24] { value: 0 } + } + } + CALL [25] { + function: _>=_ + args: { + IDENT [26] { + name: @it2:0:0 + } + CONSTANT [27] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:0:0 + } + } + } + } + } + } + } + CALL [29] { + function: size + args: { + LIST [30] { + elements: { + COMPREHENSION [31] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [32] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [33] { value: false } + } + loop_condition: { + CALL [34] { + function: @not_strictly_false + args: { + CALL [35] { + function: !_ + args: { + IDENT [36] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [37] { + function: _||_ + args: { + IDENT [38] { + name: @ac:0:0 + } + CALL [39] { + function: _&&_ + args: { + CALL [40] { + function: _>_ + args: { + IDENT [41] { + name: @it:0:0 + } + CONSTANT [42] { value: 0 } + } + } + CALL [43] { + function: _>=_ + args: { + IDENT [44] { + name: @it2:0:0 + } + CONSTANT [45] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [46] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [47] { + function: size + args: { + LIST [48] { + elements: { + COMPREHENSION [49] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [50] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [51] { value: false } + } + loop_condition: { + CALL [52] { + function: @not_strictly_false + args: { + CALL [53] { + function: !_ + args: { + IDENT [54] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [55] { + function: _||_ + args: { + IDENT [56] { + name: @ac:0:0 + } + CALL [57] { + function: _&&_ + args: { + CALL [58] { + function: _>_ + args: { + IDENT [59] { + name: @it:0:0 + } + CONSTANT [60] { value: 1 } + } + } + CALL [61] { + function: _>=_ + args: { + IDENT [62] { + name: @it2:0:0 + } + CONSTANT [63] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [64] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [65] { + function: size + args: { + LIST [66] { + elements: { + COMPREHENSION [67] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [68] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [69] { value: false } + } + loop_condition: { + CALL [70] { + function: @not_strictly_false + args: { + CALL [71] { + function: !_ + args: { + IDENT [72] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [73] { + function: _||_ + args: { + IDENT [74] { + name: @ac:0:0 + } + CALL [75] { + function: _&&_ + args: { + CALL [76] { + function: _>_ + args: { + IDENT [77] { + name: @it:0:0 + } + CONSTANT [78] { value: 1 } + } + } + CALL [79] { + function: _>=_ + args: { + IDENT [80] { + name: @it2:0:0 + } + CONSTANT [81] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [82] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CONSTANT [83] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _&&_ + args: { + CALL [8] { + function: _&&_ + args: { + COMPREHENSION [9] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [10] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [11] { value: false } + } + loop_condition: { + CALL [12] { + function: @not_strictly_false + args: { + CALL [13] { + function: !_ + args: { + IDENT [14] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [15] { + function: _||_ + args: { + IDENT [16] { + name: @ac:0:0 + } + CALL [17] { + function: _&&_ + args: { + CALL [18] { + function: _>_ + args: { + IDENT [19] { + name: @it:0:0 + } + CONSTANT [20] { value: 0 } + } + } + CALL [21] { + function: _>_ + args: { + IDENT [22] { + name: @it2:0:0 + } + CONSTANT [23] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [24] { + name: @ac:0:0 + } + } + } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [27] { value: false } + } + loop_condition: { + CALL [28] { + function: @not_strictly_false + args: { + CALL [29] { + function: !_ + args: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [31] { + function: _||_ + args: { + IDENT [32] { + name: @ac:0:0 + } + CALL [33] { + function: _&&_ + args: { + CALL [34] { + function: _>_ + args: { + IDENT [35] { + name: @it:0:0 + } + CONSTANT [36] { value: 0 } + } + } + CALL [37] { + function: _>_ + args: { + IDENT [38] { + name: @it2:0:0 + } + CONSTANT [39] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [40] { + name: @ac:0:0 + } + } + } + } + } + CALL [41] { + function: _&&_ + args: { + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [43] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [44] { value: false } + } + loop_condition: { + CALL [45] { + function: @not_strictly_false + args: { + CALL [46] { + function: !_ + args: { + IDENT [47] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [48] { + function: _||_ + args: { + IDENT [49] { + name: @ac:0:0 + } + CALL [50] { + function: _&&_ + args: { + CALL [51] { + function: _>_ + args: { + IDENT [52] { + name: @it:0:0 + } + CONSTANT [53] { value: 1 } + } + } + CALL [54] { + function: _>_ + args: { + IDENT [55] { + name: @it2:0:0 + } + CONSTANT [56] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [57] { + name: @ac:0:0 + } + } + } + COMPREHENSION [58] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [59] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [60] { value: false } + } + loop_condition: { + CALL [61] { + function: @not_strictly_false + args: { + CALL [62] { + function: !_ + args: { + IDENT [63] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [64] { + function: _||_ + args: { + IDENT [65] { + name: @ac:0:0 + } + CALL [66] { + function: _&&_ + args: { + CALL [67] { + function: _>_ + args: { + IDENT [68] { + name: @it:0:0 + } + CONSTANT [69] { value: 1 } + } + } + CALL [70] { + function: _>_ + args: { + IDENT [71] { + name: @it2:0:0 + } + CONSTANT [72] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [73] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -2238,8 +2882,287 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @it:1:0 + } + CONSTANT [32] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + } + } + LIST [12] { + elements: { + CONSTANT [13] { value: 2 } + } + } + LIST [14] { + elements: { + IDENT [15] { + name: @index2 + } + IDENT [16] { + name: @index3 + } + } + } + } + } + CALL [17] { + function: _==_ + args: { + COMPREHENSION [18] { + iter_var: @it:0:0 + iter_range: { + IDENT [19] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [20] { + elements: { + } + } + } + loop_condition: { + CONSTANT [21] { value: true } + } + loop_step: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @ac:0:0 + } + LIST [24] { + elements: { + COMPREHENSION [25] { + iter_var: @it:1:0 + iter_range: { + IDENT [26] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _?_:_ + args: { + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @it:1:0 + } + IDENT [32] { + name: @it:0:0 + } + } + } + CALL [33] { + function: _+_ + args: { + IDENT [34] { + name: @ac:1:0 + } + LIST [35] { + elements: { + IDENT [36] { + name: @it:1:0 + } + } + } + } + } + IDENT [37] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [38] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [39] { + name: @ac:0:0 + } + } + } + IDENT [40] { + name: @index4 + } + } + } + } +} +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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } LIST [11] { @@ -2262,6 +3185,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index0 @@ -2288,6 +3212,7 @@ CALL [1] { elements: { COMPREHENSION [23] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [24] { name: @index0 @@ -2315,10 +3240,18 @@ CALL [1] { CALL [30] { function: _+_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } } - CONSTANT [32] { value: 1 } + CONSTANT [34] { value: 1 } } } } @@ -2327,7 +3260,7 @@ CALL [1] { } } result: { - IDENT [33] { + IDENT [35] { name: @ac:1:0 } } @@ -2338,20 +3271,20 @@ CALL [1] { } } result: { - IDENT [34] { + IDENT [36] { name: @ac:0:0 } } } - IDENT [35] { + IDENT [37] { name: @index2 } } } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -2398,6 +3331,7 @@ CALL [1] { args: { COMPREHENSION [18] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [19] { name: @index0 @@ -2444,39 +3378,55 @@ CALL [1] { function: _?_:_ args: { CALL [30] { - function: _==_ + function: _&&_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _==_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:0:0 + } + } } - IDENT [32] { - name: @it:0:0 + CALL [34] { + function: _<_ + args: { + IDENT [35] { + name: @it:0:0 + } + IDENT [36] { + name: @it2:0:0 + } + } } } } - CALL [33] { + CALL [37] { function: _+_ args: { - IDENT [34] { + IDENT [38] { name: @ac:1:0 } - LIST [35] { + LIST [39] { elements: { - IDENT [36] { + IDENT [40] { name: @it:1:0 } } } } } - IDENT [37] { + IDENT [41] { name: @ac:1:0 } } } } result: { - IDENT [38] { + IDENT [42] { name: @ac:1:0 } } @@ -2487,12 +3437,12 @@ CALL [1] { } } result: { - IDENT [39] { + IDENT [43] { name: @ac:0:0 } } } - IDENT [40] { + IDENT [44] { name: @index4 } } @@ -2551,45 +3501,241 @@ CALL [1] { IDENT [16] { name: @index0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [17] { - elements: { - } + } + accu_var: @ac:1:0 + accu_init: { + LIST [17] { + elements: { + } + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @ac:1:0 + } + LIST [21] { + elements: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + CONSTANT [24] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [25] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:0:0 + } + } + } + COMPREHENSION [27] { + iter_var: @it:0:0 + iter_range: { + IDENT [28] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [29] { + elements: { + } + } + } + loop_condition: { + CONSTANT [30] { value: true } + } + loop_step: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:0:0 + } + LIST [33] { + elements: { + COMPREHENSION [34] { + iter_var: @it:1:0 + iter_range: { + IDENT [35] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [36] { + elements: { + } + } + } + loop_condition: { + CONSTANT [37] { value: true } + } + loop_step: { + CALL [38] { + function: _+_ + args: { + IDENT [39] { + name: @ac:1:0 + } + LIST [40] { + elements: { + CALL [41] { + function: _+_ + args: { + IDENT [42] { + name: @it:1:0 + } + CONSTANT [43] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [44] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [45] { + name: @ac:0:0 + } + } + } + } + } + } +} +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)) +=====> +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_var2: @it2:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [10] { + + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: cel.@mapInsert + args: { + IDENT [13] { + name: @ac:0:0 + } + IDENT [14] { + name: @it:0:0 + } + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [17] { + + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: cel.@mapInsert + args: { + IDENT [20] { + name: @ac:1:0 + } + IDENT [21] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [18] { value: true } - } - loop_step: { - CALL [19] { + CALL [22] { function: _+_ args: { - IDENT [20] { - name: @ac:1:0 - } - LIST [21] { - elements: { - CALL [22] { - function: _+_ - args: { - IDENT [23] { - name: @it:1:0 - } - CONSTANT [24] { value: 1 } - } + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @it:1:0 + } + IDENT [25] { + name: @it2:1:0 } } } + CONSTANT [26] { value: 1 } } } } - result: { - IDENT [25] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [27] { + name: @ac:1:0 } } } @@ -2597,82 +3743,88 @@ CALL [1] { } } result: { - IDENT [26] { + IDENT [28] { name: @ac:0:0 } } } - COMPREHENSION [27] { + COMPREHENSION [29] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [28] { + IDENT [30] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { - elements: { - } + MAP [31] { + } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [32] { value: true } } loop_step: { - CALL [31] { - function: _+_ + CALL [33] { + function: cel.@mapInsert args: { - IDENT [32] { + IDENT [34] { name: @ac:0:0 } - LIST [33] { - elements: { - COMPREHENSION [34] { - iter_var: @it:1:0 - iter_range: { - IDENT [35] { - name: @index0 + IDENT [35] { + name: @it:0:0 + } + COMPREHENSION [36] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [37] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [38] { + + } + } + loop_condition: { + CONSTANT [39] { value: true } + } + loop_step: { + CALL [40] { + function: cel.@mapInsert + args: { + IDENT [41] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [36] { - elements: { - } + IDENT [42] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [37] { value: true } - } - loop_step: { - CALL [38] { + CALL [43] { function: _+_ args: { - IDENT [39] { - name: @ac:1:0 - } - LIST [40] { - elements: { - CALL [41] { - function: _+_ - args: { - IDENT [42] { - name: @it:1:0 - } - CONSTANT [43] { value: 1 } - } + CALL [44] { + function: _+_ + args: { + IDENT [45] { + name: @it:1:0 + } + IDENT [46] { + name: @it2:1:0 } } } + CONSTANT [47] { value: 1 } } } } - result: { - IDENT [44] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [48] { + name: @ac:1:0 } } } @@ -2680,7 +3832,7 @@ CALL [1] { } } result: { - IDENT [45] { + IDENT [49] { name: @ac:0:0 } } @@ -2813,32 +3965,164 @@ CALL [1] { } } } - MAP_ENTRY [14] { - key: { - CONSTANT [15] { value: 3 } - } - value: { - IDENT [16] { - name: @index0 - } + MAP_ENTRY [14] { + key: { + CONSTANT [15] { value: 3 } + } + value: { + IDENT [16] { + name: @index0 + } + } + } + } + } + } + CALL [17] { + function: @in + args: { + CONSTANT [18] { value: 2 } + IDENT [19] { + name: @index1 + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 3 } + CONSTANT [8] { value: 4 } + } + } + LIST [9] { + elements: { + IDENT [10] { + name: @index1 + } + IDENT [11] { + name: @index1 + } + } + } + LIST [12] { + elements: { + IDENT [13] { + name: @index1 + } + } + } + LIST [14] { + elements: { + IDENT [15] { + name: @index2 + } + IDENT [16] { + name: @index2 + } + } + } + } + } + CALL [17] { + function: _==_ + args: { + COMPREHENSION [18] { + iter_var: @it:0:0 + iter_range: { + IDENT [19] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [20] { + elements: { + } + } + } + loop_condition: { + CONSTANT [21] { value: true } + } + loop_step: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @ac:0:0 + } + LIST [24] { + elements: { + COMPREHENSION [25] { + iter_var: @it:1:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:1:0 + } + IDENT [31] { + name: @index3 + } + } + } + } + result: { + IDENT [32] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:0:0 } } } - } - } - CALL [17] { - function: @in - args: { - CONSTANT [18] { value: 2 } - IDENT [19] { - name: @index1 + IDENT [34] { + name: @index4 } } } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2891,6 +4175,7 @@ CALL [1] { args: { COMPREHENSION [18] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [19] { name: @index0 @@ -2917,6 +4202,7 @@ CALL [1] { elements: { COMPREHENSION [25] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [26] { name: @index0 @@ -3224,6 +4510,279 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _-_ + args: { + IDENT [4] { + name: x + } + IDENT [5] { + name: y + } + } + } + CALL [6] { + function: _-_ + args: { + IDENT [7] { + name: @index0 + } + CONSTANT [8] { value: 1 } + } + } + CALL [9] { + function: _>_ + args: { + IDENT [10] { + name: @index1 + } + CONSTANT [11] { value: 3 } + } + } + CALL [12] { + function: _?_:_ + args: { + IDENT [13] { + name: @index2 + } + IDENT [14] { + name: @index1 + } + CONSTANT [15] { value: 5 } + } + } + LIST [16] { + elements: { + IDENT [17] { + name: @index3 + } + } + } + } + } + CALL [18] { + function: _||_ + args: { + COMPREHENSION [19] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [20] { + name: @index4 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: "foo" } + CONSTANT [5] { value: "bar" } + } + } + } + } + COMPREHENSION [6] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + COMPREHENSION [7] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:1:0 + } + IDENT [13] { + name: @it:1:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @it:1:0 + } + IDENT [17] { + name: @it:1:0 + } + } + } + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it2:1:0 + } + IDENT [20] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3867,7 +5426,7 @@ CALL [1] { } } STRUCT [7] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [8] { field_key: single_int64 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 285b6183c..e161e091a 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 @@ -2041,6 +2041,650 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + CALL [8] { + function: _+_ + args: { + CALL [9] { + function: _+_ + args: { + CALL [10] { + function: _+_ + args: { + CALL [11] { + function: size + args: { + LIST [12] { + elements: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [15] { value: false } + } + loop_condition: { + CALL [16] { + function: @not_strictly_false + args: { + CALL [17] { + function: !_ + args: { + IDENT [18] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [19] { + function: _||_ + args: { + IDENT [20] { + name: @ac:0:0 + } + CALL [21] { + function: _&&_ + args: { + CALL [22] { + function: _>_ + args: { + IDENT [23] { + name: @it:0:0 + } + CONSTANT [24] { value: 0 } + } + } + CALL [25] { + function: _>=_ + args: { + IDENT [26] { + name: @it2:0:0 + } + CONSTANT [27] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:0:0 + } + } + } + } + } + } + } + CALL [29] { + function: size + args: { + LIST [30] { + elements: { + COMPREHENSION [31] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [32] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [33] { value: false } + } + loop_condition: { + CALL [34] { + function: @not_strictly_false + args: { + CALL [35] { + function: !_ + args: { + IDENT [36] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [37] { + function: _||_ + args: { + IDENT [38] { + name: @ac:0:0 + } + CALL [39] { + function: _&&_ + args: { + CALL [40] { + function: _>_ + args: { + IDENT [41] { + name: @it:0:0 + } + CONSTANT [42] { value: 0 } + } + } + CALL [43] { + function: _>=_ + args: { + IDENT [44] { + name: @it2:0:0 + } + CONSTANT [45] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [46] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [47] { + function: size + args: { + LIST [48] { + elements: { + COMPREHENSION [49] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [50] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [51] { value: false } + } + loop_condition: { + CALL [52] { + function: @not_strictly_false + args: { + CALL [53] { + function: !_ + args: { + IDENT [54] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [55] { + function: _||_ + args: { + IDENT [56] { + name: @ac:0:0 + } + CALL [57] { + function: _&&_ + args: { + CALL [58] { + function: _>_ + args: { + IDENT [59] { + name: @it:0:0 + } + CONSTANT [60] { value: 1 } + } + } + CALL [61] { + function: _>=_ + args: { + IDENT [62] { + name: @it2:0:0 + } + CONSTANT [63] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [64] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [65] { + function: size + args: { + LIST [66] { + elements: { + COMPREHENSION [67] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [68] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [69] { value: false } + } + loop_condition: { + CALL [70] { + function: @not_strictly_false + args: { + CALL [71] { + function: !_ + args: { + IDENT [72] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [73] { + function: _||_ + args: { + IDENT [74] { + name: @ac:0:0 + } + CALL [75] { + function: _&&_ + args: { + CALL [76] { + function: _>_ + args: { + IDENT [77] { + name: @it:0:0 + } + CONSTANT [78] { value: 1 } + } + } + CALL [79] { + function: _>=_ + args: { + IDENT [80] { + name: @it2:0:0 + } + CONSTANT [81] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [82] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CONSTANT [83] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _&&_ + args: { + CALL [8] { + function: _&&_ + args: { + COMPREHENSION [9] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [10] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [11] { value: false } + } + loop_condition: { + CALL [12] { + function: @not_strictly_false + args: { + CALL [13] { + function: !_ + args: { + IDENT [14] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [15] { + function: _||_ + args: { + IDENT [16] { + name: @ac:0:0 + } + CALL [17] { + function: _&&_ + args: { + CALL [18] { + function: _>_ + args: { + IDENT [19] { + name: @it:0:0 + } + CONSTANT [20] { value: 0 } + } + } + CALL [21] { + function: _>_ + args: { + IDENT [22] { + name: @it2:0:0 + } + CONSTANT [23] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [24] { + name: @ac:0:0 + } + } + } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [27] { value: false } + } + loop_condition: { + CALL [28] { + function: @not_strictly_false + args: { + CALL [29] { + function: !_ + args: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [31] { + function: _||_ + args: { + IDENT [32] { + name: @ac:0:0 + } + CALL [33] { + function: _&&_ + args: { + CALL [34] { + function: _>_ + args: { + IDENT [35] { + name: @it:0:0 + } + CONSTANT [36] { value: 0 } + } + } + CALL [37] { + function: _>_ + args: { + IDENT [38] { + name: @it2:0:0 + } + CONSTANT [39] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [40] { + name: @ac:0:0 + } + } + } + } + } + CALL [41] { + function: _&&_ + args: { + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [43] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [44] { value: false } + } + loop_condition: { + CALL [45] { + function: @not_strictly_false + args: { + CALL [46] { + function: !_ + args: { + IDENT [47] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [48] { + function: _||_ + args: { + IDENT [49] { + name: @ac:0:0 + } + CALL [50] { + function: _&&_ + args: { + CALL [51] { + function: _>_ + args: { + IDENT [52] { + name: @it:0:0 + } + CONSTANT [53] { value: 1 } + } + } + CALL [54] { + function: _>_ + args: { + IDENT [55] { + name: @it2:0:0 + } + CONSTANT [56] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [57] { + name: @ac:0:0 + } + } + } + COMPREHENSION [58] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [59] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [60] { value: false } + } + loop_condition: { + CALL [61] { + function: @not_strictly_false + args: { + CALL [62] { + function: !_ + args: { + IDENT [63] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [64] { + function: _||_ + args: { + IDENT [65] { + name: @ac:0:0 + } + CALL [66] { + function: _&&_ + args: { + CALL [67] { + function: _>_ + args: { + IDENT [68] { + name: @it:0:0 + } + CONSTANT [69] { value: 1 } + } + } + CALL [70] { + function: _>_ + args: { + IDENT [71] { + name: @it2:0:0 + } + CONSTANT [72] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [73] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -2059,8 +2703,281 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @it:1:0 + } + CONSTANT [32] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 2 } + } + } + } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } + } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + 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 + } + } + } + } + } + } + } + } + result: { + IDENT [37] { + name: @ac:0:0 + } + } + } + IDENT [38] { + name: @index0 + } + } + } + } +} +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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } LIST [11] { @@ -2083,6 +3000,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index0 @@ -2109,6 +3027,7 @@ CALL [1] { elements: { COMPREHENSION [23] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [24] { name: @index0 @@ -2136,10 +3055,18 @@ CALL [1] { CALL [30] { function: _+_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } } - CONSTANT [32] { value: 1 } + CONSTANT [34] { value: 1 } } } } @@ -2148,7 +3075,7 @@ CALL [1] { } } result: { - IDENT [33] { + IDENT [35] { name: @ac:1:0 } } @@ -2159,20 +3086,20 @@ CALL [1] { } } result: { - IDENT [34] { + IDENT [36] { name: @ac:0:0 } } } - IDENT [35] { + IDENT [37] { name: @index2 } } } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -2213,6 +3140,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index1 @@ -2259,39 +3187,55 @@ CALL [1] { function: _?_:_ args: { CALL [28] { - function: _==_ + function: _&&_ args: { - IDENT [29] { - name: @it:1:0 + CALL [29] { + function: _==_ + args: { + IDENT [30] { + name: @it:1:0 + } + IDENT [31] { + name: @it2:0:0 + } + } } - IDENT [30] { - name: @it:0:0 + CALL [32] { + function: _<_ + args: { + IDENT [33] { + name: @it:0:0 + } + IDENT [34] { + name: @it2:0:0 + } + } } } } - CALL [31] { + CALL [35] { function: _+_ args: { - IDENT [32] { + IDENT [36] { name: @ac:1:0 } - LIST [33] { + LIST [37] { elements: { - IDENT [34] { + IDENT [38] { name: @it:1:0 } } } } } - IDENT [35] { + IDENT [39] { name: @ac:1:0 } } } } result: { - IDENT [36] { + IDENT [40] { name: @ac:1:0 } } @@ -2302,12 +3246,12 @@ CALL [1] { } } result: { - IDENT [37] { + IDENT [41] { name: @ac:0:0 } } } - IDENT [38] { + IDENT [42] { name: @index0 } } @@ -2353,58 +3297,254 @@ CALL [1] { } loop_step: { CALL [12] { - function: _+_ + function: _+_ + args: { + IDENT [13] { + name: @ac:0:0 + } + LIST [14] { + elements: { + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [17] { + elements: { + } + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @ac:1:0 + } + LIST [21] { + elements: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + CONSTANT [24] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [25] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:0:0 + } + } + } + COMPREHENSION [27] { + iter_var: @it:0:0 + iter_range: { + IDENT [28] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [29] { + elements: { + } + } + } + loop_condition: { + CONSTANT [30] { value: true } + } + loop_step: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:0:0 + } + LIST [33] { + elements: { + COMPREHENSION [34] { + iter_var: @it:1:0 + iter_range: { + IDENT [35] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [36] { + elements: { + } + } + } + loop_condition: { + CONSTANT [37] { value: true } + } + loop_step: { + CALL [38] { + function: _+_ + args: { + IDENT [39] { + name: @ac:1:0 + } + LIST [40] { + elements: { + CALL [41] { + function: _+_ + args: { + IDENT [42] { + name: @it:1:0 + } + CONSTANT [43] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [44] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [45] { + name: @ac:0:0 + } + } + } + } + } + } +} +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)) +=====> +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_var2: @it2:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [10] { + + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: cel.@mapInsert args: { IDENT [13] { name: @ac:0:0 } - LIST [14] { - elements: { - COMPREHENSION [15] { - iter_var: @it:1:0 - iter_range: { - IDENT [16] { - name: @index0 + IDENT [14] { + name: @it:0:0 + } + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [17] { + + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: cel.@mapInsert + args: { + IDENT [20] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [17] { - elements: { - } + IDENT [21] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [18] { value: true } - } - loop_step: { - CALL [19] { + CALL [22] { function: _+_ args: { - IDENT [20] { - name: @ac:1:0 - } - LIST [21] { - elements: { - CALL [22] { - function: _+_ - args: { - IDENT [23] { - name: @it:1:0 - } - CONSTANT [24] { value: 1 } - } + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @it:1:0 + } + IDENT [25] { + name: @it2:1:0 } } } + CONSTANT [26] { value: 1 } } } } - result: { - IDENT [25] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [27] { + name: @ac:1:0 } } } @@ -2412,82 +3552,88 @@ CALL [1] { } } result: { - IDENT [26] { + IDENT [28] { name: @ac:0:0 } } } - COMPREHENSION [27] { + COMPREHENSION [29] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [28] { + IDENT [30] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { - elements: { - } + MAP [31] { + } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [32] { value: true } } loop_step: { - CALL [31] { - function: _+_ + CALL [33] { + function: cel.@mapInsert args: { - IDENT [32] { + IDENT [34] { name: @ac:0:0 } - LIST [33] { - elements: { - COMPREHENSION [34] { - iter_var: @it:1:0 - iter_range: { - IDENT [35] { - name: @index0 + IDENT [35] { + name: @it:0:0 + } + COMPREHENSION [36] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [37] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [38] { + + } + } + loop_condition: { + CONSTANT [39] { value: true } + } + loop_step: { + CALL [40] { + function: cel.@mapInsert + args: { + IDENT [41] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [36] { - elements: { - } + IDENT [42] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [37] { value: true } - } - loop_step: { - CALL [38] { + CALL [43] { function: _+_ args: { - IDENT [39] { - name: @ac:1:0 - } - LIST [40] { - elements: { - CALL [41] { - function: _+_ - args: { - IDENT [42] { - name: @it:1:0 - } - CONSTANT [43] { value: 1 } - } + CALL [44] { + function: _+_ + args: { + IDENT [45] { + name: @it:1:0 + } + IDENT [46] { + name: @it2:1:0 } } } + CONSTANT [47] { value: 1 } } } } - result: { - IDENT [44] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [48] { + name: @ac:1:0 } } } @@ -2495,7 +3641,7 @@ CALL [1] { } } result: { - IDENT [45] { + IDENT [49] { name: @ac:0:0 } } @@ -2623,20 +3769,158 @@ CALL [1] { key: { CONSTANT [14] { value: 2 } } - value: { - IDENT [15] { - name: @index0 + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + LIST [13] { + elements: { + LIST [14] { + elements: { + CONSTANT [15] { value: 3 } + CONSTANT [16] { value: 4 } + } + } + } + } + COMPREHENSION [17] { + iter_var: @it:1:0 + iter_range: { + IDENT [18] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [19] { + elements: { + } + } + } + loop_condition: { + CONSTANT [20] { value: true } + } + loop_step: { + CALL [21] { + function: _+_ + args: { + IDENT [22] { + name: @ac:1:0 + } + IDENT [23] { + name: @index2 + } + } + } + } + result: { + IDENT [24] { + name: @ac:1:0 + } + } + } + LIST [25] { + elements: { + IDENT [26] { + name: @index3 + } + } + } + } + } + CALL [27] { + function: _==_ + args: { + COMPREHENSION [28] { + iter_var: @it:0:0 + iter_range: { + IDENT [29] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [30] { + elements: { + } + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [32] { + function: _+_ + args: { + IDENT [33] { + name: @ac:0:0 + } + IDENT [34] { + name: @index4 + } } } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + result: { + IDENT [35] { + name: @ac:0:0 } - value: { - IDENT [18] { - name: @index0 - } + } + } + LIST [36] { + elements: { + IDENT [37] { + name: @index0 + } + IDENT [38] { + name: @index0 } } } @@ -2644,8 +3928,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2686,6 +3970,7 @@ CALL [1] { } COMPREHENSION [17] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [18] { name: @index1 @@ -2734,6 +4019,7 @@ CALL [1] { args: { COMPREHENSION [28] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [29] { name: @index1 @@ -3040,6 +4326,273 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _-_ + args: { + CALL [4] { + function: _-_ + args: { + IDENT [5] { + name: x + } + IDENT [6] { + name: y + } + } + } + CONSTANT [7] { value: 1 } + } + } + CALL [8] { + function: _>_ + args: { + IDENT [9] { + name: @index0 + } + CONSTANT [10] { value: 3 } + } + } + LIST [11] { + elements: { + CALL [12] { + function: _?_:_ + args: { + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index0 + } + CONSTANT [15] { value: 5 } + } + } + } + } + } + } + CALL [16] { + function: _||_ + args: { + COMPREHENSION [17] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [18] { + name: @index2 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [19] { value: false } + } + loop_condition: { + CALL [20] { + function: @not_strictly_false + args: { + CALL [21] { + function: !_ + args: { + IDENT [22] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [23] { + function: _||_ + args: { + IDENT [24] { + name: @ac:0:0 + } + CALL [25] { + function: _>_ + args: { + CALL [26] { + function: _-_ + args: { + CALL [27] { + function: _-_ + args: { + IDENT [28] { + name: @it:0:0 + } + IDENT [29] { + name: @it2:0:0 + } + } + } + CONSTANT [30] { value: 1 } + } + } + CONSTANT [31] { value: 3 } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 + } + } + } + IDENT [33] { + name: @index1 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: "foo" } + CONSTANT [5] { value: "bar" } + } + } + } + } + COMPREHENSION [6] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + COMPREHENSION [7] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:1:0 + } + IDENT [13] { + name: @it:1:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @it:1:0 + } + IDENT [17] { + name: @it:1:0 + } + } + } + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it2:1:0 + } + IDENT [20] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3602,7 +5155,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 d9211978c..6123928e3 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 @@ -1736,6 +1736,650 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _==_ + args: { + CALL [8] { + function: _+_ + args: { + CALL [9] { + function: _+_ + args: { + CALL [10] { + function: _+_ + args: { + CALL [11] { + function: size + args: { + LIST [12] { + elements: { + COMPREHENSION [13] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [14] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [15] { value: false } + } + loop_condition: { + CALL [16] { + function: @not_strictly_false + args: { + CALL [17] { + function: !_ + args: { + IDENT [18] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [19] { + function: _||_ + args: { + IDENT [20] { + name: @ac:0:0 + } + CALL [21] { + function: _&&_ + args: { + CALL [22] { + function: _>_ + args: { + IDENT [23] { + name: @it:0:0 + } + CONSTANT [24] { value: 0 } + } + } + CALL [25] { + function: _>=_ + args: { + IDENT [26] { + name: @it2:0:0 + } + CONSTANT [27] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:0:0 + } + } + } + } + } + } + } + CALL [29] { + function: size + args: { + LIST [30] { + elements: { + COMPREHENSION [31] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [32] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [33] { value: false } + } + loop_condition: { + CALL [34] { + function: @not_strictly_false + args: { + CALL [35] { + function: !_ + args: { + IDENT [36] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [37] { + function: _||_ + args: { + IDENT [38] { + name: @ac:0:0 + } + CALL [39] { + function: _&&_ + args: { + CALL [40] { + function: _>_ + args: { + IDENT [41] { + name: @it:0:0 + } + CONSTANT [42] { value: 0 } + } + } + CALL [43] { + function: _>=_ + args: { + IDENT [44] { + name: @it2:0:0 + } + CONSTANT [45] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [46] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [47] { + function: size + args: { + LIST [48] { + elements: { + COMPREHENSION [49] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [50] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [51] { value: false } + } + loop_condition: { + CALL [52] { + function: @not_strictly_false + args: { + CALL [53] { + function: !_ + args: { + IDENT [54] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [55] { + function: _||_ + args: { + IDENT [56] { + name: @ac:0:0 + } + CALL [57] { + function: _&&_ + args: { + CALL [58] { + function: _>_ + args: { + IDENT [59] { + name: @it:0:0 + } + CONSTANT [60] { value: 1 } + } + } + CALL [61] { + function: _>=_ + args: { + IDENT [62] { + name: @it2:0:0 + } + CONSTANT [63] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [64] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CALL [65] { + function: size + args: { + LIST [66] { + elements: { + COMPREHENSION [67] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [68] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [69] { value: false } + } + loop_condition: { + CALL [70] { + function: @not_strictly_false + args: { + CALL [71] { + function: !_ + args: { + IDENT [72] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [73] { + function: _||_ + args: { + IDENT [74] { + name: @ac:0:0 + } + CALL [75] { + function: _&&_ + args: { + CALL [76] { + function: _>_ + args: { + IDENT [77] { + name: @it:0:0 + } + CONSTANT [78] { value: 1 } + } + } + CALL [79] { + function: _>=_ + args: { + IDENT [80] { + name: @it2:0:0 + } + CONSTANT [81] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [82] { + name: @ac:0:0 + } + } + } + } + } + } + } + } + } + CONSTANT [83] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + } + } + LIST [5] { + elements: { + CONSTANT [6] { value: 2 } + } + } + } + } + CALL [7] { + function: _&&_ + args: { + CALL [8] { + function: _&&_ + args: { + COMPREHENSION [9] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [10] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [11] { value: false } + } + loop_condition: { + CALL [12] { + function: @not_strictly_false + args: { + CALL [13] { + function: !_ + args: { + IDENT [14] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [15] { + function: _||_ + args: { + IDENT [16] { + name: @ac:0:0 + } + CALL [17] { + function: _&&_ + args: { + CALL [18] { + function: _>_ + args: { + IDENT [19] { + name: @it:0:0 + } + CONSTANT [20] { value: 0 } + } + } + CALL [21] { + function: _>_ + args: { + IDENT [22] { + name: @it2:0:0 + } + CONSTANT [23] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [24] { + name: @ac:0:0 + } + } + } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [27] { value: false } + } + loop_condition: { + CALL [28] { + function: @not_strictly_false + args: { + CALL [29] { + function: !_ + args: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [31] { + function: _||_ + args: { + IDENT [32] { + name: @ac:0:0 + } + CALL [33] { + function: _&&_ + args: { + CALL [34] { + function: _>_ + args: { + IDENT [35] { + name: @it:0:0 + } + CONSTANT [36] { value: 0 } + } + } + CALL [37] { + function: _>_ + args: { + IDENT [38] { + name: @it2:0:0 + } + CONSTANT [39] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [40] { + name: @ac:0:0 + } + } + } + } + } + CALL [41] { + function: _&&_ + args: { + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [43] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [44] { value: false } + } + loop_condition: { + CALL [45] { + function: @not_strictly_false + args: { + CALL [46] { + function: !_ + args: { + IDENT [47] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [48] { + function: _||_ + args: { + IDENT [49] { + name: @ac:0:0 + } + CALL [50] { + function: _&&_ + args: { + CALL [51] { + function: _>_ + args: { + IDENT [52] { + name: @it:0:0 + } + CONSTANT [53] { value: 1 } + } + } + CALL [54] { + function: _>_ + args: { + IDENT [55] { + name: @it2:0:0 + } + CONSTANT [56] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [57] { + name: @ac:0:0 + } + } + } + COMPREHENSION [58] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [59] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [60] { value: false } + } + loop_condition: { + CALL [61] { + function: @not_strictly_false + args: { + CALL [62] { + function: !_ + args: { + IDENT [63] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [64] { + function: _||_ + args: { + IDENT [65] { + name: @ac:0:0 + } + CALL [66] { + function: _&&_ + args: { + CALL [67] { + function: _>_ + args: { + IDENT [68] { + name: @it:0:0 + } + CONSTANT [69] { value: 1 } + } + } + CALL [70] { + function: _>_ + args: { + IDENT [71] { + name: @it2:0:0 + } + CONSTANT [72] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [73] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1754,8 +2398,281 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @it:1:0 + } + CONSTANT [32] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + LIST [6] { + elements: { + CONSTANT [7] { value: 2 } + } + } + } + } + LIST [8] { + elements: { + CONSTANT [9] { value: 1 } + CONSTANT [10] { value: 2 } + } + } + LIST [11] { + elements: { + CONSTANT [12] { value: 1 } + CONSTANT [13] { value: 2 } + CONSTANT [14] { value: 3 } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_range: { + IDENT [17] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + 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 + } + } + } + } + } + } + } + } + result: { + IDENT [37] { + name: @ac:0:0 + } + } + } + IDENT [38] { + name: @index0 + } + } + } + } +} +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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } LIST [11] { @@ -1778,6 +2695,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index0 @@ -1804,6 +2722,7 @@ CALL [1] { elements: { COMPREHENSION [23] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [24] { name: @index0 @@ -1831,10 +2750,18 @@ CALL [1] { CALL [30] { function: _+_ args: { - IDENT [31] { - name: @it:1:0 + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } } - CONSTANT [32] { value: 1 } + CONSTANT [34] { value: 1 } } } } @@ -1843,7 +2770,7 @@ CALL [1] { } } result: { - IDENT [33] { + IDENT [35] { name: @ac:1:0 } } @@ -1854,20 +2781,20 @@ CALL [1] { } } result: { - IDENT [34] { + IDENT [36] { name: @ac:0:0 } } } - IDENT [35] { + IDENT [37] { name: @index2 } } } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -1908,6 +2835,7 @@ CALL [1] { args: { COMPREHENSION [16] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [17] { name: @index1 @@ -1954,39 +2882,55 @@ CALL [1] { function: _?_:_ args: { CALL [28] { - function: _==_ + function: _&&_ args: { - IDENT [29] { - name: @it:1:0 + CALL [29] { + function: _==_ + args: { + IDENT [30] { + name: @it:1:0 + } + IDENT [31] { + name: @it2:0:0 + } + } } - IDENT [30] { - name: @it:0:0 + CALL [32] { + function: _<_ + args: { + IDENT [33] { + name: @it:0:0 + } + IDENT [34] { + name: @it2:0:0 + } + } } } } - CALL [31] { + CALL [35] { function: _+_ args: { - IDENT [32] { + IDENT [36] { name: @ac:1:0 } - LIST [33] { + LIST [37] { elements: { - IDENT [34] { + IDENT [38] { name: @it:1:0 } } } } } - IDENT [35] { + IDENT [39] { name: @ac:1:0 } } } } result: { - IDENT [36] { + IDENT [40] { name: @ac:1:0 } } @@ -1997,12 +2941,12 @@ CALL [1] { } } result: { - IDENT [37] { + IDENT [41] { name: @ac:0:0 } } } - IDENT [38] { + IDENT [42] { name: @index0 } } @@ -2061,45 +3005,241 @@ CALL [1] { IDENT [16] { name: @index0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [17] { - elements: { - } + } + accu_var: @ac:1:0 + accu_init: { + LIST [17] { + elements: { + } + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @ac:1:0 + } + LIST [21] { + elements: { + CALL [22] { + function: _+_ + args: { + IDENT [23] { + name: @it:1:0 + } + CONSTANT [24] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [25] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [26] { + name: @ac:0:0 + } + } + } + COMPREHENSION [27] { + iter_var: @it:0:0 + iter_range: { + IDENT [28] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [29] { + elements: { + } + } + } + loop_condition: { + CONSTANT [30] { value: true } + } + loop_step: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @ac:0:0 + } + LIST [33] { + elements: { + COMPREHENSION [34] { + iter_var: @it:1:0 + iter_range: { + IDENT [35] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [36] { + elements: { + } + } + } + loop_condition: { + CONSTANT [37] { value: true } + } + loop_step: { + CALL [38] { + function: _+_ + args: { + IDENT [39] { + name: @ac:1:0 + } + LIST [40] { + elements: { + CALL [41] { + function: _+_ + args: { + IDENT [42] { + name: @it:1:0 + } + CONSTANT [43] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [44] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [45] { + name: @ac:0:0 + } + } + } + } + } + } +} +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)) +=====> +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_var2: @it2:0:0 + iter_range: { + IDENT [9] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [10] { + + } + } + loop_condition: { + CONSTANT [11] { value: true } + } + loop_step: { + CALL [12] { + function: cel.@mapInsert + args: { + IDENT [13] { + name: @ac:0:0 + } + IDENT [14] { + name: @it:0:0 + } + COMPREHENSION [15] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [16] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [17] { + + } + } + loop_condition: { + CONSTANT [18] { value: true } + } + loop_step: { + CALL [19] { + function: cel.@mapInsert + args: { + IDENT [20] { + name: @ac:1:0 + } + IDENT [21] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [18] { value: true } - } - loop_step: { - CALL [19] { + CALL [22] { function: _+_ args: { - IDENT [20] { - name: @ac:1:0 - } - LIST [21] { - elements: { - CALL [22] { - function: _+_ - args: { - IDENT [23] { - name: @it:1:0 - } - CONSTANT [24] { value: 1 } - } + CALL [23] { + function: _+_ + args: { + IDENT [24] { + name: @it:1:0 + } + IDENT [25] { + name: @it2:1:0 } } } + CONSTANT [26] { value: 1 } } } } - result: { - IDENT [25] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [27] { + name: @ac:1:0 } } } @@ -2107,82 +3247,88 @@ CALL [1] { } } result: { - IDENT [26] { + IDENT [28] { name: @ac:0:0 } } } - COMPREHENSION [27] { + COMPREHENSION [29] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [28] { + IDENT [30] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [29] { - elements: { - } + MAP [31] { + } } loop_condition: { - CONSTANT [30] { value: true } + CONSTANT [32] { value: true } } loop_step: { - CALL [31] { - function: _+_ + CALL [33] { + function: cel.@mapInsert args: { - IDENT [32] { + IDENT [34] { name: @ac:0:0 } - LIST [33] { - elements: { - COMPREHENSION [34] { - iter_var: @it:1:0 - iter_range: { - IDENT [35] { - name: @index0 + IDENT [35] { + name: @it:0:0 + } + COMPREHENSION [36] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [37] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [38] { + + } + } + loop_condition: { + CONSTANT [39] { value: true } + } + loop_step: { + CALL [40] { + function: cel.@mapInsert + args: { + IDENT [41] { + name: @ac:1:0 } - } - accu_var: @ac:1:0 - accu_init: { - LIST [36] { - elements: { - } + IDENT [42] { + name: @it:1:0 } - } - loop_condition: { - CONSTANT [37] { value: true } - } - loop_step: { - CALL [38] { + CALL [43] { function: _+_ args: { - IDENT [39] { - name: @ac:1:0 - } - LIST [40] { - elements: { - CALL [41] { - function: _+_ - args: { - IDENT [42] { - name: @it:1:0 - } - CONSTANT [43] { value: 1 } - } + CALL [44] { + function: _+_ + args: { + IDENT [45] { + name: @it:1:0 + } + IDENT [46] { + name: @it2:1:0 } } } + CONSTANT [47] { value: 1 } } } } - result: { - IDENT [44] { - name: @ac:1:0 - } - } + } + } + result: { + IDENT [48] { + name: @ac:1:0 } } } @@ -2190,7 +3336,7 @@ CALL [1] { } } result: { - IDENT [45] { + IDENT [49] { name: @ac:0:0 } } @@ -2311,24 +3457,159 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + LIST [13] { + elements: { + LIST [14] { + elements: { + CONSTANT [15] { value: 3 } + CONSTANT [16] { value: 4 } + } + } + } + } + COMPREHENSION [17] { + iter_var: @it:1:0 + iter_range: { + IDENT [18] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [19] { + elements: { + } + } + } + loop_condition: { + CONSTANT [20] { value: true } + } + loop_step: { + CALL [21] { + function: _+_ + args: { + IDENT [22] { + name: @ac:1:0 + } + IDENT [23] { + name: @index2 + } + } + } + } + result: { + IDENT [24] { + name: @ac:1:0 + } + } + } + } + } + CALL [25] { + function: _==_ + args: { + COMPREHENSION [26] { + iter_var: @it:0:0 + iter_range: { + IDENT [27] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [28] { + elements: { + } + } + } + loop_condition: { + CONSTANT [29] { value: true } + } + loop_step: { + CALL [30] { + function: _+_ + args: { + IDENT [31] { + name: @ac:0:0 + } + LIST [32] { + elements: { + IDENT [33] { + name: @index3 + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [35] { + elements: { + IDENT [36] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [37] { + name: @index0 } } } @@ -2336,8 +3617,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2378,6 +3659,7 @@ CALL [1] { } COMPREHENSION [17] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [18] { name: @index1 @@ -2419,6 +3701,7 @@ CALL [1] { args: { COMPREHENSION [26] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [27] { name: @index1 @@ -2726,6 +4009,287 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + CALL [10] { + function: _?_:_ + args: { + IDENT [11] { + name: @index0 + } + CALL [12] { + function: _-_ + args: { + CALL [13] { + function: _-_ + args: { + IDENT [14] { + name: x + } + IDENT [15] { + name: y + } + } + } + CONSTANT [16] { value: 1 } + } + } + CONSTANT [17] { value: 5 } + } + } + LIST [18] { + elements: { + IDENT [19] { + name: @index1 + } + } + } + } + } + CALL [20] { + function: _||_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [22] { + name: @index2 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _>_ + args: { + CALL [30] { + function: _-_ + args: { + CALL [31] { + function: _-_ + args: { + IDENT [32] { + name: @it:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + CONSTANT [34] { value: 1 } + } + } + CONSTANT [35] { value: 3 } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + IDENT [37] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: "foo" } + CONSTANT [5] { value: "bar" } + } + } + } + } + COMPREHENSION [6] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + COMPREHENSION [7] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [8] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [9] { + + } + } + loop_condition: { + CONSTANT [10] { value: true } + } + loop_step: { + CALL [11] { + function: cel.@mapInsert + args: { + IDENT [12] { + name: @ac:1:0 + } + IDENT [13] { + name: @it:1:0 + } + LIST [14] { + elements: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @it:1:0 + } + IDENT [17] { + name: @it:1:0 + } + } + } + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it2:1:0 + } + IDENT [20] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3266,7 +4830,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 cf40f26ee..c3de5d723 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 @@ -1700,6 +1700,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1882,60 +2324,354 @@ CALL [1] { } LIST [16] { elements: { - IDENT [17] { + IDENT [17] { + name: @it:1:0 + } + } + } + } + } + IDENT [18] { + name: @ac:1:0 + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + CALL [20] { + function: _==_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_range: { + LIST [22] { + elements: { + CONSTANT [23] { value: 1 } + CONSTANT [24] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index0 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + LIST [11] { + elements: { + IDENT [12] { + name: @index1 + } + IDENT [13] { + name: @index1 + } + IDENT [14] { + name: @index1 + } + } + } + } + } + CALL [15] { + function: _==_ + args: { + COMPREHENSION [16] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [17] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [18] { + elements: { + } + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: _+_ + args: { + IDENT [21] { + name: @ac:0:0 + } + LIST [22] { + elements: { + COMPREHENSION [23] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:1:0 + } + LIST [29] { + elements: { + CALL [30] { + function: _+_ + args: { + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it:1:0 + } + IDENT [33] { + name: @it2:1:0 + } + } + } + CONSTANT [34] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [35] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + IDENT [37] { + name: @index2 + } + } + } + } +} +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + 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 [18] { + IDENT [22] { name: @ac:1:0 } } } } result: { - IDENT [19] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [20] { + CALL [24] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - LIST [22] { + LIST [26] { elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } + CONSTANT [27] { value: 1 } + CONSTANT [28] { value: 2 } } } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [27] { + CALL [31] { function: _+_ args: { - IDENT [28] { + IDENT [32] { name: @ac:0:0 } - LIST [29] { + LIST [33] { elements: { - IDENT [30] { + IDENT [34] { name: @index0 } } @@ -1944,21 +2680,21 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [35] { name: @ac:0:0 } } } - LIST [32] { + LIST [36] { elements: { - LIST [33] { + LIST [37] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [38] { value: 1 } } } - LIST [35] { + LIST [39] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [40] { value: 2 } } } } @@ -2084,6 +2820,129 @@ CALL [1] { } } } +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)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:1:0 + } + IDENT [12] { + name: @it:1:0 + } + CALL [13] { + function: _+_ + args: { + CALL [14] { + function: _+_ + args: { + IDENT [15] { + name: @it:1:0 + } + IDENT [16] { + name: @it2:1:0 + } + } + } + CONSTANT [17] { value: 1 } + } + } + } + } + } + result: { + IDENT [18] { + name: @ac:1:0 + } + } + } + COMPREHENSION [19] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [20] { + elements: { + CONSTANT [21] { value: 1 } + CONSTANT [22] { value: 2 } + CONSTANT [23] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [24] { + + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [26] { + function: cel.@mapInsert + args: { + IDENT [27] { + name: @ac:0:0 + } + IDENT [28] { + name: @it:0:0 + } + IDENT [29] { + name: @index0 + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + CALL [31] { + function: _==_ + args: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + } + } + } +} 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] =====> @@ -2193,24 +3052,156 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_range: { + IDENT [14] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:1:0 + } + LIST [19] { + elements: { + LIST [20] { + elements: { + CONSTANT [21] { value: 3 } + CONSTANT [22] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_range: { + IDENT [26] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [34] { + elements: { + IDENT [35] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [36] { + name: @index0 } } } @@ -2218,8 +3209,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2250,6 +3241,7 @@ CALL [1] { } COMPREHENSION [13] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [14] { name: @index1 @@ -2298,6 +3290,7 @@ CALL [1] { args: { COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [26] { name: @index1 @@ -2602,6 +3595,284 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + LIST [10] { + elements: { + CALL [11] { + function: _?_:_ + args: { + IDENT [12] { + name: @index0 + } + CALL [13] { + function: _-_ + args: { + CALL [14] { + function: _-_ + args: { + IDENT [15] { + name: x + } + IDENT [16] { + name: y + } + } + } + CONSTANT [17] { value: 1 } + } + } + CONSTANT [18] { value: 5 } + } + } + } + } + } + } + CALL [19] { + function: _||_ + args: { + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [21] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [22] { value: false } + } + loop_condition: { + CALL [23] { + function: @not_strictly_false + args: { + CALL [24] { + function: !_ + args: { + IDENT [25] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [26] { + function: _||_ + args: { + IDENT [27] { + name: @ac:0:0 + } + CALL [28] { + function: _>_ + args: { + CALL [29] { + function: _-_ + args: { + CALL [30] { + function: _-_ + args: { + IDENT [31] { + name: @it:0:0 + } + IDENT [32] { + name: @it2:0:0 + } + } + } + CONSTANT [33] { value: 1 } + } + } + CONSTANT [34] { value: 3 } + } + } + } + } + } + result: { + IDENT [35] { + name: @ac:0:0 + } + } + } + IDENT [36] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: "foo" } + CONSTANT [6] { value: "bar" } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [7] { + + } + } + loop_condition: { + CONSTANT [8] { value: true } + } + loop_step: { + CALL [9] { + function: cel.@mapInsert + args: { + IDENT [10] { + name: @ac:1:0 + } + IDENT [11] { + name: @it:1:0 + } + LIST [12] { + elements: { + CALL [13] { + function: _+_ + args: { + IDENT [14] { + name: @it:1:0 + } + IDENT [15] { + name: @it:1:0 + } + } + } + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @it2:1:0 + } + IDENT [18] { + name: @it2:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [21] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [22] { + + } + } + loop_condition: { + CONSTANT [23] { value: true } + } + loop_step: { + CALL [24] { + function: cel.@mapInsert + args: { + IDENT [25] { + name: @ac:0:0 + } + IDENT [26] { + name: @it:0:0 + } + LIST [27] { + elements: { + CALL [28] { + function: _+_ + args: { + IDENT [29] { + name: @it:0:0 + } + IDENT [30] { + name: @it:0:0 + } + } + } + CALL [31] { + function: _+_ + args: { + IDENT [32] { + name: @it2:0:0 + } + IDENT [33] { + name: @it2:0:0 + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3130,7 +4401,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 0eadf7a83..e9797057a 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 @@ -1682,6 +1682,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1700,12 +2142,280 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + COMPREHENSION [11] { + iter_var: @it:1:0 + iter_range: { + IDENT [12] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [13] { + elements: { + } + } + } + loop_condition: { + CONSTANT [14] { value: true } + } + loop_step: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @ac:1:0 + } + LIST [17] { + elements: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it:1:0 + } + CONSTANT [20] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + } + CALL [22] { + function: _==_ + args: { + COMPREHENSION [23] { + iter_var: @it:0:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + IDENT [35] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + 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 + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + CALL [20] { + function: _==_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_range: { + LIST [22] { + elements: { + CONSTANT [23] { value: 1 } + CONSTANT [24] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index0 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } COMPREHENSION [11] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [12] { name: @index0 @@ -1733,10 +2443,18 @@ CALL [1] { CALL [18] { function: _+_ args: { - IDENT [19] { - name: @it:1:0 + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @it:1:0 + } + IDENT [21] { + name: @it2:1:0 + } + } } - CONSTANT [20] { value: 1 } + CONSTANT [22] { value: 1 } } } } @@ -1745,43 +2463,44 @@ CALL [1] { } } result: { - IDENT [21] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [22] { + CALL [24] { function: _==_ args: { - COMPREHENSION [23] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [24] { + IDENT [26] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [27] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [28] { value: true } } loop_step: { - CALL [27] { + CALL [29] { function: _+_ args: { - IDENT [28] { + IDENT [30] { name: @ac:0:0 } - LIST [29] { + LIST [31] { elements: { - IDENT [30] { + IDENT [32] { name: @index2 } } @@ -1790,20 +2509,20 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [33] { name: @ac:0:0 } } } - LIST [32] { + LIST [34] { elements: { - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } - IDENT [35] { + IDENT [37] { name: @index1 } } @@ -1812,8 +2531,8 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -1846,78 +2565,95 @@ CALL [1] { function: _?_:_ args: { CALL [11] { - function: _==_ - args: { - IDENT [12] { - name: @it:1:0 + function: _&&_ + args: { + CALL [12] { + function: _==_ + args: { + IDENT [13] { + name: @it:1:0 + } + IDENT [14] { + name: @it2:0:0 + } + } } - IDENT [13] { - name: @it:0:0 + CALL [15] { + function: _<_ + args: { + IDENT [16] { + name: @it:0:0 + } + IDENT [17] { + name: @it2:0:0 + } + } } } } - CALL [14] { + CALL [18] { function: _+_ args: { - IDENT [15] { + IDENT [19] { name: @ac:1:0 } - LIST [16] { + LIST [20] { elements: { - IDENT [17] { + IDENT [21] { name: @it:1:0 } } } } } - IDENT [18] { + IDENT [22] { name: @ac:1:0 } } } } result: { - IDENT [19] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [20] { + CALL [24] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - LIST [22] { + LIST [26] { elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } + CONSTANT [27] { value: 1 } + CONSTANT [28] { value: 2 } } } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [27] { + CALL [31] { function: _+_ args: { - IDENT [28] { + IDENT [32] { name: @ac:0:0 } - LIST [29] { + LIST [33] { elements: { - IDENT [30] { + IDENT [34] { name: @index0 } } @@ -1926,21 +2662,21 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [35] { name: @ac:0:0 } } } - LIST [32] { + LIST [36] { elements: { - LIST [33] { + LIST [37] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [38] { value: 1 } } } - LIST [35] { + LIST [39] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [40] { value: 2 } } } } @@ -2066,6 +2802,129 @@ CALL [1] { } } } +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)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:1:0 + } + IDENT [12] { + name: @it:1:0 + } + CALL [13] { + function: _+_ + args: { + CALL [14] { + function: _+_ + args: { + IDENT [15] { + name: @it:1:0 + } + IDENT [16] { + name: @it2:1:0 + } + } + } + CONSTANT [17] { value: 1 } + } + } + } + } + } + result: { + IDENT [18] { + name: @ac:1:0 + } + } + } + COMPREHENSION [19] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [20] { + elements: { + CONSTANT [21] { value: 1 } + CONSTANT [22] { value: 2 } + CONSTANT [23] { value: 3 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + MAP [24] { + + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [26] { + function: cel.@mapInsert + args: { + IDENT [27] { + name: @ac:0:0 + } + IDENT [28] { + name: @it:0:0 + } + IDENT [29] { + name: @index0 + } + } + } + } + result: { + IDENT [30] { + name: @ac:0:0 + } + } + } + } + } + CALL [31] { + function: _==_ + args: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + } + } + } +} 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] =====> @@ -2162,37 +3021,169 @@ CALL [1] { } } } - CALL [7] { - function: @in + CALL [7] { + function: @in + args: { + CONSTANT [8] { value: 2 } + MAP [9] { + MAP_ENTRY [10] { + key: { + CONSTANT [11] { value: "a" } + } + value: { + CONSTANT [12] { value: 1 } + } + } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_range: { + IDENT [14] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:1:0 + } + LIST [19] { + elements: { + LIST [20] { + elements: { + CONSTANT [21] { value: 3 } + CONSTANT [22] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ args: { - CONSTANT [8] { value: 2 } - MAP [9] { - MAP_ENTRY [10] { - key: { - CONSTANT [11] { value: "a" } - } - value: { - CONSTANT [12] { value: 1 } + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_range: { + IDENT [26] { + name: @index1 } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } } - value: { - IDENT [15] { - name: @index0 + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } } } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + result: { + IDENT [33] { + name: @ac:0:0 } - value: { - IDENT [18] { - name: @index0 - } + } + } + LIST [34] { + elements: { + IDENT [35] { + name: @index0 + } + IDENT [36] { + name: @index0 } } } @@ -2200,8 +3191,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2232,6 +3223,7 @@ CALL [1] { } COMPREHENSION [13] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [14] { name: @index1 @@ -2280,6 +3272,7 @@ CALL [1] { args: { COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [26] { name: @index1 @@ -2584,6 +3577,269 @@ CALL [1] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3106,7 +4362,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 7eadf9498..d220cfff4 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 @@ -1676,6 +1676,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1694,12 +2136,280 @@ CALL [1] { LIST [7] { elements: { CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } - CONSTANT [10] { value: 4 } + CONSTANT [9] { value: 3 } + CONSTANT [10] { value: 4 } + } + } + COMPREHENSION [11] { + iter_var: @it:1:0 + iter_range: { + IDENT [12] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [13] { + elements: { + } + } + } + loop_condition: { + CONSTANT [14] { value: true } + } + loop_step: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @ac:1:0 + } + LIST [17] { + elements: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @it:1:0 + } + CONSTANT [20] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [21] { + name: @ac:1:0 + } + } + } + } + } + CALL [22] { + function: _==_ + args: { + COMPREHENSION [23] { + iter_var: @it:0:0 + iter_range: { + IDENT [24] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + IDENT [35] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:1:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + CONSTANT [6] { value: 2 } + CONSTANT [7] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [8] { + elements: { + } + } + } + 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 + } + } + } + } + result: { + IDENT [19] { + name: @ac:1:0 + } + } + } + } + } + CALL [20] { + function: _==_ + args: { + COMPREHENSION [21] { + iter_var: @it:0:0 + iter_range: { + LIST [22] { + elements: { + CONSTANT [23] { value: 1 } + CONSTANT [24] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [25] { + elements: { + } + } + } + loop_condition: { + CONSTANT [26] { value: true } + } + loop_step: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @ac:0:0 + } + LIST [29] { + elements: { + IDENT [30] { + name: @index0 + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:0:0 + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } } } COMPREHENSION [11] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [12] { name: @index0 @@ -1727,10 +2437,18 @@ CALL [1] { CALL [18] { function: _+_ args: { - IDENT [19] { - name: @it:1:0 + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @it:1:0 + } + IDENT [21] { + name: @it2:1:0 + } + } } - CONSTANT [20] { value: 1 } + CONSTANT [22] { value: 1 } } } } @@ -1739,43 +2457,44 @@ CALL [1] { } } result: { - IDENT [21] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [22] { + CALL [24] { function: _==_ args: { - COMPREHENSION [23] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - IDENT [24] { + IDENT [26] { name: @index0 } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [27] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [28] { value: true } } loop_step: { - CALL [27] { + CALL [29] { function: _+_ args: { - IDENT [28] { + IDENT [30] { name: @ac:0:0 } - LIST [29] { + LIST [31] { elements: { - IDENT [30] { + IDENT [32] { name: @index2 } } @@ -1784,20 +2503,20 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [33] { name: @ac:0:0 } } } - LIST [32] { + LIST [34] { elements: { - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } - IDENT [35] { + IDENT [37] { name: @index1 } } @@ -1806,8 +2525,8 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> CALL [1] { function: cel.@block @@ -1840,78 +2559,95 @@ CALL [1] { function: _?_:_ args: { CALL [11] { - function: _==_ + function: _&&_ args: { - IDENT [12] { - name: @it:1:0 + CALL [12] { + function: _==_ + args: { + IDENT [13] { + name: @it:1:0 + } + IDENT [14] { + name: @it2:0:0 + } + } } - IDENT [13] { - name: @it:0:0 + CALL [15] { + function: _<_ + args: { + IDENT [16] { + name: @it:0:0 + } + IDENT [17] { + name: @it2:0:0 + } + } } } } - CALL [14] { + CALL [18] { function: _+_ args: { - IDENT [15] { + IDENT [19] { name: @ac:1:0 } - LIST [16] { + LIST [20] { elements: { - IDENT [17] { + IDENT [21] { name: @it:1:0 } } } } } - IDENT [18] { + IDENT [22] { name: @ac:1:0 } } } } result: { - IDENT [19] { + IDENT [23] { name: @ac:1:0 } } } } } - CALL [20] { + CALL [24] { function: _==_ args: { - COMPREHENSION [21] { + COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { - LIST [22] { + LIST [26] { elements: { - CONSTANT [23] { value: 1 } - CONSTANT [24] { value: 2 } + CONSTANT [27] { value: 1 } + CONSTANT [28] { value: 2 } } } } accu_var: @ac:0:0 accu_init: { - LIST [25] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [26] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [27] { + CALL [31] { function: _+_ args: { - IDENT [28] { + IDENT [32] { name: @ac:0:0 } - LIST [29] { + LIST [33] { elements: { - IDENT [30] { + IDENT [34] { name: @index0 } } @@ -1920,21 +2656,21 @@ CALL [1] { } } result: { - IDENT [31] { + IDENT [35] { name: @ac:0:0 } } } - LIST [32] { + LIST [36] { elements: { - LIST [33] { + LIST [37] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [38] { value: 1 } } } - LIST [35] { + LIST [39] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [40] { value: 2 } } } } @@ -2060,6 +2796,126 @@ CALL [1] { } } } +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)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2: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: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @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] =====> @@ -2169,24 +3025,156 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_range: { + IDENT [14] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [17] { + function: _+_ + args: { + IDENT [18] { + name: @ac:1:0 + } + LIST [19] { + elements: { + LIST [20] { + elements: { + CONSTANT [21] { value: 3 } + CONSTANT [22] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_range: { + IDENT [26] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } } } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + result: { + IDENT [33] { + name: @ac:0:0 } - value: { - IDENT [18] { - name: @index0 - } + } + } + LIST [34] { + elements: { + IDENT [35] { + name: @index0 + } + IDENT [36] { + name: @index0 } } } @@ -2194,8 +3182,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2226,6 +3214,7 @@ CALL [1] { } COMPREHENSION [13] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [14] { name: @index1 @@ -2274,6 +3263,7 @@ CALL [1] { args: { COMPREHENSION [25] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [26] { name: @index1 @@ -2566,6 +3556,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3085,7 +4338,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 8bba9777e..6d8187a17 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 @@ -1673,6 +1673,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1783,15 +2225,280 @@ CALL [1] { } } } - LIST [31] { + LIST [31] { + elements: { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + COMPREHENSION [11] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + IDENT [12] { + name: @index0 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [13] { + elements: { + } + } + } + loop_condition: { + CONSTANT [14] { value: true } + } + loop_step: { + CALL [15] { + function: _+_ + args: { + IDENT [16] { + name: @ac:1:0 + } + LIST [17] { + elements: { + CALL [18] { + function: _+_ + args: { + CALL [19] { + function: _+_ + args: { + IDENT [20] { + name: @it:1:0 + } + IDENT [21] { + name: @it2:1:0 + } + } + } + CONSTANT [22] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [23] { + name: @ac:1:0 + } + } + } + } + } + CALL [24] { + function: _==_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [26] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [27] { + elements: { + } + } + } + loop_condition: { + CONSTANT [28] { value: true } + } + loop_step: { + CALL [29] { + function: _+_ + args: { + IDENT [30] { + name: @ac:0:0 + } + LIST [31] { + elements: { + IDENT [32] { + name: @index2 + } + } + } + } + } + } + result: { + IDENT [33] { + name: @ac:0:0 + } + } + } + LIST [34] { elements: { - IDENT [32] { + IDENT [35] { name: @index1 } - IDENT [33] { + IDENT [36] { name: @index1 } - IDENT [34] { + IDENT [37] { name: @index1 } } @@ -1800,14 +2507,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1818,82 +2526,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1904,21 +2628,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2039,6 +2763,126 @@ CALL [1] { } } } +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)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2: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: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @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] =====> @@ -2148,24 +2992,153 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + } + } + CALL [13] { + function: _==_ + args: { + COMPREHENSION [14] { + iter_var: @it:0:0 + iter_range: { + IDENT [15] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [16] { + elements: { + } + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @ac:0:0 + } + LIST [20] { + elements: { + COMPREHENSION [21] { + iter_var: @it:1:0 + iter_range: { + IDENT [22] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [23] { + elements: { + } + } + } + loop_condition: { + CONSTANT [24] { value: true } + } + loop_step: { + CALL [25] { + function: _+_ + args: { + IDENT [26] { + name: @ac:1:0 + } + LIST [27] { + elements: { + LIST [28] { + elements: { + CONSTANT [29] { value: 3 } + CONSTANT [30] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [33] { + elements: { + IDENT [34] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [35] { + name: @index0 } } } @@ -2173,8 +3146,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2210,6 +3183,7 @@ CALL [1] { args: { COMPREHENSION [14] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [15] { name: @index1 @@ -2236,6 +3210,7 @@ CALL [1] { elements: { COMPREHENSION [21] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [22] { name: @index1 @@ -2542,6 +3517,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3061,7 +4299,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 2bee1cefa..726b6aa91 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 @@ -1673,6 +1673,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1785,13 +2227,275 @@ CALL [1] { } LIST [31] { elements: { - IDENT [32] { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + } + } + CALL [11] { + function: _==_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [13] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [14] { + elements: { + } + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @ac:0:0 + } + LIST [18] { + elements: { + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_var2: @it2: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: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @it:1:0 + } + IDENT [29] { + name: @it2:1:0 + } + } + } + CONSTANT [30] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 + } + } + } + LIST [33] { + elements: { + IDENT [34] { name: @index1 } - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } } @@ -1800,14 +2504,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1818,82 +2523,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1904,21 +2625,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2039,6 +2760,126 @@ CALL [1] { } } } +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)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2: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: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @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] =====> @@ -2148,24 +2989,153 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + } + } + CALL [13] { + function: _==_ + args: { + COMPREHENSION [14] { + iter_var: @it:0:0 + iter_range: { + IDENT [15] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [16] { + elements: { + } + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @ac:0:0 + } + LIST [20] { + elements: { + COMPREHENSION [21] { + iter_var: @it:1:0 + iter_range: { + IDENT [22] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [23] { + elements: { + } + } + } + loop_condition: { + CONSTANT [24] { value: true } + } + loop_step: { + CALL [25] { + function: _+_ + args: { + IDENT [26] { + name: @ac:1:0 + } + LIST [27] { + elements: { + LIST [28] { + elements: { + CONSTANT [29] { value: 3 } + CONSTANT [30] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [33] { + elements: { + IDENT [34] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [35] { + name: @index0 } } } @@ -2173,8 +3143,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2210,6 +3180,7 @@ CALL [1] { args: { COMPREHENSION [14] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [15] { name: @index1 @@ -2236,6 +3207,7 @@ CALL [1] { elements: { COMPREHENSION [21] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [22] { name: @index1 @@ -2542,6 +3514,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3061,7 +4296,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 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 740b3e04f..ba147d0df 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 @@ -1661,6 +1661,448 @@ CALL [1] { } } } +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>=_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + COMPREHENSION [20] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [21] { + elements: { + CONSTANT [22] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [23] { value: false } + } + loop_condition: { + CALL [24] { + function: @not_strictly_false + args: { + CALL [25] { + function: !_ + args: { + IDENT [26] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [27] { + function: _||_ + args: { + IDENT [28] { + name: @ac:0:0 + } + CALL [29] { + function: _&&_ + args: { + CALL [30] { + function: _>_ + args: { + IDENT [31] { + name: @it:0:0 + } + CONSTANT [32] { value: 1 } + } + } + CALL [33] { + function: _>=_ + args: { + IDENT [34] { + name: @it2:0:0 + } + CONSTANT [35] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [36] { + name: @ac:0:0 + } + } + } + CALL [37] { + function: size + args: { + LIST [38] { + elements: { + IDENT [39] { + name: @index0 + } + } + } + } + } + CALL [40] { + function: size + args: { + LIST [41] { + elements: { + IDENT [42] { + name: @index1 + } + } + } + } + } + } + } + CALL [43] { + function: _==_ + args: { + CALL [44] { + function: _+_ + args: { + CALL [45] { + function: _+_ + args: { + CALL [46] { + function: _+_ + args: { + IDENT [47] { + name: @index2 + } + IDENT [48] { + name: @index2 + } + } + } + IDENT [49] { + name: @index3 + } + } + } + IDENT [50] { + name: @index3 + } + } + } + CONSTANT [51] { value: 4 } + } + } + } +} +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [4] { + elements: { + CONSTANT [5] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [6] { value: false } + } + loop_condition: { + CALL [7] { + function: @not_strictly_false + args: { + CALL [8] { + function: !_ + args: { + IDENT [9] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [10] { + function: _||_ + args: { + IDENT [11] { + name: @ac:0:0 + } + CALL [12] { + function: _&&_ + args: { + CALL [13] { + function: _>_ + args: { + IDENT [14] { + name: @it:0:0 + } + CONSTANT [15] { value: 0 } + } + } + CALL [16] { + function: _>_ + args: { + IDENT [17] { + name: @it2:0:0 + } + CONSTANT [18] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [19] { + name: @ac:0:0 + } + } + } + } + } + CALL [20] { + function: _&&_ + args: { + CALL [21] { + function: _&&_ + args: { + IDENT [22] { + name: @index0 + } + IDENT [23] { + name: @index0 + } + } + } + CALL [24] { + function: _&&_ + args: { + COMPREHENSION [25] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [26] { + elements: { + CONSTANT [27] { value: 1 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [28] { value: false } + } + loop_condition: { + CALL [29] { + function: @not_strictly_false + args: { + CALL [30] { + function: !_ + args: { + IDENT [31] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [32] { + function: _||_ + args: { + IDENT [33] { + name: @ac:0:0 + } + CALL [34] { + function: _&&_ + args: { + CALL [35] { + function: _>_ + args: { + IDENT [36] { + name: @it:0:0 + } + CONSTANT [37] { value: 1 } + } + } + CALL [38] { + function: _>_ + args: { + IDENT [39] { + name: @it2:0:0 + } + CONSTANT [40] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [41] { + name: @ac:0:0 + } + } + } + COMPREHENSION [42] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [43] { + elements: { + CONSTANT [44] { value: 2 } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [45] { value: false } + } + loop_condition: { + CALL [46] { + function: @not_strictly_false + args: { + CALL [47] { + function: !_ + args: { + IDENT [48] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [49] { + function: _||_ + args: { + IDENT [50] { + name: @ac:0:0 + } + CALL [51] { + function: _&&_ + args: { + CALL [52] { + function: _>_ + args: { + IDENT [53] { + name: @it:0:0 + } + CONSTANT [54] { value: 1 } + } + } + CALL [55] { + function: _>_ + args: { + IDENT [56] { + name: @it2:0:0 + } + CONSTANT [57] { value: 0 } + } + } + } + } + } + } + } + result: { + IDENT [58] { + name: @ac:0:0 + } + } + } + } + } + } + } + } +} 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]] =====> @@ -1773,13 +2215,275 @@ CALL [1] { } LIST [31] { elements: { - IDENT [32] { + IDENT [32] { + name: @index1 + } + IDENT [33] { + name: @index1 + } + IDENT [34] { + name: @index1 + } + } + } + } + } + } +} +Test case: NESTED_MACROS_2 +Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +=====> +CALL [31] { + function: _==_ + args: { + COMPREHENSION [30] { + iter_var: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: 1 } + CONSTANT [3] { value: 2 } + } + } + } + accu_var: @result + accu_init: { + LIST [24] { + elements: { + } + } + } + loop_condition: { + CONSTANT [25] { value: true } + } + loop_step: { + CALL [28] { + function: _+_ + args: { + IDENT [26] { + name: @result + } + LIST [27] { + elements: { + COMPREHENSION [23] { + iter_var: x + iter_range: { + LIST [6] { + elements: { + CONSTANT [7] { value: 1 } + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 3 } + } + } + } + accu_var: @result + accu_init: { + LIST [15] { + elements: { + } + } + } + loop_condition: { + CONSTANT [16] { value: true } + } + loop_step: { + CALL [21] { + function: _?_:_ + args: { + CALL [13] { + function: _==_ + args: { + IDENT [12] { + name: x + } + IDENT [14] { + name: y + } + } + } + CALL [19] { + function: _+_ + args: { + IDENT [17] { + name: @result + } + LIST [18] { + elements: { + IDENT [11] { + name: x + } + } + } + } + } + IDENT [20] { + name: @result + } + } + } + } + result: { + IDENT [22] { + name: @result + } + } + } + } + } + } + } + } + result: { + IDENT [29] { + name: @result + } + } + } + LIST [32] { + elements: { + LIST [33] { + elements: { + CONSTANT [34] { value: 1 } + } + } + LIST [35] { + elements: { + CONSTANT [36] { value: 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]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + CONSTANT [4] { value: 1 } + CONSTANT [5] { value: 2 } + CONSTANT [6] { value: 3 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 2 } + CONSTANT [9] { value: 4 } + CONSTANT [10] { value: 6 } + } + } + } + } + CALL [11] { + function: _==_ + args: { + COMPREHENSION [12] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + IDENT [13] { + name: @index0 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [14] { + elements: { + } + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [16] { + function: _+_ + args: { + IDENT [17] { + name: @ac:0:0 + } + LIST [18] { + elements: { + COMPREHENSION [19] { + iter_var: @it:1:0 + iter_var2: @it2: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: { + CALL [27] { + function: _+_ + args: { + IDENT [28] { + name: @it:1:0 + } + IDENT [29] { + name: @it2:1:0 + } + } + } + CONSTANT [30] { value: 1 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 + } + } + } + LIST [33] { + elements: { + IDENT [34] { name: @index1 } - IDENT [33] { + IDENT [35] { name: @index1 } - IDENT [34] { + IDENT [36] { name: @index1 } } @@ -1788,14 +2492,15 @@ CALL [1] { } } } -Test case: NESTED_MACROS_2 -Source: [1, 2].map(y, [1, 2, 3].filter(x, x == y)) == [[1], [2]] +Test case: NESTED_MACROS_COMP_V2_2 +Source: [1, 2].transformList(i, y, [1, 2, 3].filter(x, x == y && i < y)) == [[1], [2]] =====> -CALL [31] { +CALL [36] { function: _==_ args: { - COMPREHENSION [30] { - iter_var: y + COMPREHENSION [35] { + iter_var: i + iter_var2: y iter_range: { LIST [1] { elements: { @@ -1806,82 +2511,98 @@ CALL [31] { } accu_var: @result accu_init: { - LIST [24] { + LIST [29] { elements: { } } } loop_condition: { - CONSTANT [25] { value: true } + CONSTANT [30] { value: true } } loop_step: { - CALL [28] { + CALL [33] { function: _+_ args: { - IDENT [26] { + IDENT [31] { name: @result } - LIST [27] { + LIST [32] { elements: { - COMPREHENSION [23] { + COMPREHENSION [28] { iter_var: x iter_range: { - LIST [6] { + LIST [7] { elements: { - CONSTANT [7] { value: 1 } - CONSTANT [8] { value: 2 } - CONSTANT [9] { value: 3 } + CONSTANT [8] { value: 1 } + CONSTANT [9] { value: 2 } + CONSTANT [10] { value: 3 } } } } accu_var: @result accu_init: { - LIST [15] { + LIST [20] { elements: { } } } loop_condition: { - CONSTANT [16] { value: true } + CONSTANT [21] { value: true } } loop_step: { - CALL [21] { + CALL [26] { function: _?_:_ args: { - CALL [13] { - function: _==_ + CALL [16] { + function: _&&_ args: { - IDENT [12] { - name: x + CALL [14] { + function: _==_ + args: { + IDENT [13] { + name: x + } + IDENT [15] { + name: y + } + } } - IDENT [14] { - name: y + CALL [18] { + function: _<_ + args: { + IDENT [17] { + name: i + } + IDENT [19] { + name: y + } + } } } } - CALL [19] { + CALL [24] { function: _+_ args: { - IDENT [17] { + IDENT [22] { name: @result } - LIST [18] { + LIST [23] { elements: { - IDENT [11] { + IDENT [12] { name: x } } } } } - IDENT [20] { + IDENT [25] { name: @result } } } } result: { - IDENT [22] { + IDENT [27] { name: @result } } @@ -1892,21 +2613,21 @@ CALL [31] { } } result: { - IDENT [29] { + IDENT [34] { name: @result } } } - LIST [32] { + LIST [37] { elements: { - LIST [33] { + LIST [38] { elements: { - CONSTANT [34] { value: 1 } + CONSTANT [39] { value: 1 } } } - LIST [35] { + LIST [40] { elements: { - CONSTANT [36] { value: 2 } + CONSTANT [41] { value: 2 } } } } @@ -2027,6 +2748,126 @@ CALL [1] { } } } +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)) +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + COMPREHENSION [3] { + iter_var: @it:0:0 + iter_var2: @it2: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: { + MAP [8] { + + } + } + loop_condition: { + CONSTANT [9] { value: true } + } + loop_step: { + CALL [10] { + function: cel.@mapInsert + args: { + IDENT [11] { + name: @ac:0:0 + } + IDENT [12] { + name: @it:0:0 + } + COMPREHENSION [13] { + iter_var: @it:1:0 + iter_var2: @it2:1:0 + iter_range: { + LIST [14] { + elements: { + CONSTANT [15] { value: 1 } + CONSTANT [16] { value: 2 } + CONSTANT [17] { value: 3 } + } + } + } + accu_var: @ac:1:0 + accu_init: { + MAP [18] { + + } + } + loop_condition: { + CONSTANT [19] { value: true } + } + loop_step: { + CALL [20] { + function: cel.@mapInsert + args: { + IDENT [21] { + name: @ac:1:0 + } + IDENT [22] { + name: @it:1:0 + } + CALL [23] { + function: _+_ + args: { + CALL [24] { + function: _+_ + args: { + IDENT [25] { + name: @it:1:0 + } + IDENT [26] { + name: @it2:1:0 + } + } + } + CONSTANT [27] { value: 1 } + } + } + } + } + } + result: { + IDENT [28] { + name: @ac:1:0 + } + } + } + } + } + } + result: { + IDENT [29] { + name: @ac:0:0 + } + } + } + } + } + CALL [30] { + function: _==_ + args: { + IDENT [31] { + name: @index0 + } + IDENT [32] { + name: @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] =====> @@ -2136,24 +2977,153 @@ CALL [1] { CONSTANT [12] { value: 1 } } } - MAP_ENTRY [13] { - key: { - CONSTANT [14] { value: 2 } - } - value: { - IDENT [15] { - name: @index0 - } + MAP_ENTRY [13] { + key: { + CONSTANT [14] { value: 2 } + } + value: { + IDENT [15] { + name: @index0 + } + } + } + MAP_ENTRY [16] { + key: { + CONSTANT [17] { value: 3 } + } + value: { + IDENT [18] { + name: @index0 + } + } + } + } + } + } + } +} +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]]] +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + LIST [3] { + elements: { + LIST [4] { + elements: { + CONSTANT [5] { value: 3 } + CONSTANT [6] { value: 4 } + } + } + LIST [7] { + elements: { + CONSTANT [8] { value: 3 } + CONSTANT [9] { value: 4 } + } + } + } + } + LIST [10] { + elements: { + CONSTANT [11] { value: 1 } + CONSTANT [12] { value: 2 } + } + } + } + } + CALL [13] { + function: _==_ + args: { + COMPREHENSION [14] { + iter_var: @it:0:0 + iter_range: { + IDENT [15] { + name: @index1 + } + } + accu_var: @ac:0:0 + accu_init: { + LIST [16] { + elements: { + } + } + } + loop_condition: { + CONSTANT [17] { value: true } + } + loop_step: { + CALL [18] { + function: _+_ + args: { + IDENT [19] { + name: @ac:0:0 + } + LIST [20] { + elements: { + COMPREHENSION [21] { + iter_var: @it:1:0 + iter_range: { + IDENT [22] { + name: @index1 + } + } + accu_var: @ac:1:0 + accu_init: { + LIST [23] { + elements: { + } + } + } + loop_condition: { + CONSTANT [24] { value: true } + } + loop_step: { + CALL [25] { + function: _+_ + args: { + IDENT [26] { + name: @ac:1:0 + } + LIST [27] { + elements: { + LIST [28] { + elements: { + CONSTANT [29] { value: 3 } + CONSTANT [30] { value: 4 } + } + } + } + } + } + } + } + result: { + IDENT [31] { + name: @ac:1:0 + } + } + } + } + } + } + } + } + result: { + IDENT [32] { + name: @ac:0:0 } } - MAP_ENTRY [16] { - key: { - CONSTANT [17] { value: 3 } + } + LIST [33] { + elements: { + IDENT [34] { + name: @index0 } - value: { - IDENT [18] { - name: @index0 - } + IDENT [35] { + name: @index0 } } } @@ -2161,8 +3131,8 @@ CALL [1] { } } } -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]]] +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]]] =====> CALL [1] { function: cel.@block @@ -2198,6 +3168,7 @@ CALL [1] { args: { COMPREHENSION [14] { iter_var: @it:0:0 + iter_var2: @it2:0:0 iter_range: { IDENT [15] { name: @index1 @@ -2224,6 +3195,7 @@ CALL [1] { elements: { COMPREHENSION [21] { iter_var: @it:1:0 + iter_var2: @it2:1:0 iter_range: { IDENT [22] { name: @index1 @@ -2530,6 +3502,269 @@ COMPREHENSION [35] { } } } +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +CALL [1] { + function: cel.@block + args: { + LIST [2] { + elements: { + CALL [3] { + function: _>_ + args: { + CALL [4] { + function: _-_ + args: { + CALL [5] { + function: _-_ + args: { + IDENT [6] { + name: x + } + IDENT [7] { + name: y + } + } + } + CONSTANT [8] { value: 1 } + } + } + CONSTANT [9] { value: 3 } + } + } + } + } + CALL [10] { + function: _||_ + args: { + COMPREHENSION [11] { + iter_var: @it:0:0 + iter_var2: @it2:0:0 + iter_range: { + LIST [12] { + elements: { + CALL [13] { + function: _?_:_ + args: { + IDENT [14] { + name: @index0 + } + CALL [15] { + function: _-_ + args: { + CALL [16] { + function: _-_ + args: { + IDENT [17] { + name: x + } + IDENT [18] { + name: y + } + } + } + CONSTANT [19] { value: 1 } + } + } + CONSTANT [20] { value: 5 } + } + } + } + } + } + accu_var: @ac:0:0 + accu_init: { + CONSTANT [21] { value: false } + } + loop_condition: { + CALL [22] { + function: @not_strictly_false + args: { + CALL [23] { + function: !_ + args: { + IDENT [24] { + name: @ac:0:0 + } + } + } + } + } + } + loop_step: { + CALL [25] { + function: _||_ + args: { + IDENT [26] { + name: @ac:0:0 + } + CALL [27] { + function: _>_ + args: { + CALL [28] { + function: _-_ + args: { + CALL [29] { + function: _-_ + args: { + IDENT [30] { + name: @it:0:0 + } + IDENT [31] { + name: @it2:0:0 + } + } + } + CONSTANT [32] { value: 1 } + } + } + CONSTANT [33] { value: 3 } + } + } + } + } + } + result: { + IDENT [34] { + name: @ac:0:0 + } + } + } + IDENT [35] { + name: @index0 + } + } + } + } +} +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +COMPREHENSION [35] { + iter_var: x + iter_var2: y + iter_range: { + COMPREHENSION [19] { + iter_var: x + iter_var2: y + iter_range: { + LIST [1] { + elements: { + CONSTANT [2] { value: "foo" } + CONSTANT [3] { value: "bar" } + } + } + } + accu_var: @result + accu_init: { + MAP [14] { + + } + } + loop_condition: { + CONSTANT [15] { value: true } + } + loop_step: { + CALL [17] { + function: cel.@mapInsert + args: { + IDENT [16] { + name: @result + } + IDENT [5] { + name: x + } + LIST [7] { + elements: { + CALL [9] { + function: _+_ + args: { + IDENT [8] { + name: x + } + IDENT [10] { + name: x + } + } + } + CALL [12] { + function: _+_ + args: { + IDENT [11] { + name: y + } + IDENT [13] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [18] { + name: @result + } + } + } + } + accu_var: @result + accu_init: { + MAP [30] { + + } + } + loop_condition: { + CONSTANT [31] { value: true } + } + loop_step: { + CALL [33] { + function: cel.@mapInsert + args: { + IDENT [32] { + name: @result + } + IDENT [21] { + name: x + } + LIST [23] { + elements: { + CALL [25] { + function: _+_ + args: { + IDENT [24] { + name: x + } + IDENT [26] { + name: x + } + } + } + CALL [28] { + function: _+_ + args: { + IDENT [27] { + name: y + } + IDENT [29] { + name: y + } + } + } + } + } + } + } + } + result: { + IDENT [34] { + name: @result + } + } +} Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -3049,7 +4284,7 @@ CALL [1] { LIST [2] { elements: { STRUCT [3] { - name: TestAllTypes + name: cel.expr.conformance.proto3.TestAllTypes entries: { ENTRY [4] { field_key: single_int64 diff --git a/optimizer/src/test/resources/subexpression_unparsed.baseline b/optimizer/src/test/resources/subexpression_unparsed.baseline index f2ec443d3..684c0ccb5 100644 --- a/optimizer/src/test/resources/subexpression_unparsed.baseline +++ b/optimizer/src/test/resources/subexpression_unparsed.baseline @@ -298,6 +298,36 @@ Result: false [BLOCK_RECURSION_DEPTH_8]: cel.@block([[1].exists(@it:0:0, @it:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it:0:0 > 1) && [2].exists(@it:0:0, @it:0:0 > 1)) [BLOCK_RECURSION_DEPTH_9]: cel.@block([[1].exists(@it:0:0, @it:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it:0:0 > 1) && [2].exists(@it:0:0, @it:0:0 > 1)) +Test case: MULTIPLE_MACROS_COMP_V2_1 +Source: size([[1].exists(i,x,i > 0 && x >= 0)]) + size([[1].exists(j, y, j > 0 && y >= 0)]) + size([[2].exists(k,z,k > 1 && z >= 0)]) + size([[2].exists(l, w, l > 1 && w >= 0)]) == 4 +=====> +Result: true +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), size([@index0]), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index2])], @index1 + @index1 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1], [2]], size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) == 4) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1], [2]], size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) == 4) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1], [2]], size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) + size([@index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0)]) == 4) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 >= 0), [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 >= 0), size([@index0]), size([@index1])], @index2 + @index2 + @index3 + @index3 == 4) + +Test case: MULTIPLE_MACROS_COMP_V2_2 +Source: [1].exists(i, x, i > 0 && x > 0) && [1].exists(j, y, j > 0 && y > 0) && [1].exists(k, z, k > 1 && z > 0) && [2].exists(l, w, l > 1 && w > 0) +=====> +Result: false +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([[1], [2]], @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && @index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([[1], [2]], @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && @index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([[1], [2]], @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0) && @index0.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && @index1.exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([[1].exists(@it:0:0, @it2:0:0, @it:0:0 > 0 && @it2:0:0 > 0)], @index0 && @index0 && [1].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0) && [2].exists(@it:0:0, @it2:0:0, @it:0:0 > 1 && @it2:0:0 > 0)) + 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]] =====> @@ -328,6 +358,36 @@ Result: true [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_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]) + +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, 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_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]] + 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)) =====> @@ -343,6 +403,21 @@ Result: true [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) +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) + 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] =====> @@ -388,6 +463,21 @@ Result: true [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]) +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]) + Test case: MACRO_SHADOWED_VARIABLE Source: [x - 1 > 3 ? x - 1 : 5].exists(x, x - 1 > 3) || x - 1 > 3 =====> @@ -418,6 +508,36 @@ Result: [[[foofoo, foofoo, foofoo, foofoo], [foofoo, foofoo, foofoo, foofoo]], [ [BLOCK_RECURSION_DEPTH_8]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) [BLOCK_RECURSION_DEPTH_9]: ["foo", "bar"].map(x, [x + x, x + x]).map(x, [x + x, x + x]) +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_1 +Source: [x - y - 1 > 3 ? x - y - 1 : 5].exists(x, y, x - y - 1 > 3) || x - y - 1 > 3 +=====> +Result: CelUnknownSet{attributes=[], unknownExprIds=[6]} +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([x - y - 1, @index0 > 3], [@index1 ? @index0 : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([x - y, @index0 - 1, @index1 > 3, @index2 ? @index1 : 5, [@index3]], @index4.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index2) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([x - y - 1, @index0 > 3, [@index1 ? @index0 : 5]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index1) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([x - y - 1 > 3, @index0 ? (x - y - 1) : 5, [@index1]], @index2.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([x - y - 1 > 3, [@index0 ? (x - y - 1) : 5]], @index1.exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([x - y - 1 > 3], [@index0 ? (x - y - 1) : 5].exists(@it:0:0, @it2:0:0, @it:0:0 - @it2:0:0 - 1 > 3) || @index0) + +Test case: MACRO_SHADOWED_VARIABLE_COMP_V2_2 +Source: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +=====> +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_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]) +[BLOCK_RECURSION_DEPTH_8]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) +[BLOCK_RECURSION_DEPTH_9]: ["foo", "bar"].transformMap(x, y, [x + x, y + y]).transformMap(x, y, [x + x, y + y]) + Test case: PRESENCE_TEST Source: has({'a': true}.a) && {'a':true}['a'] =====> @@ -557,16 +677,16 @@ Test case: OPTIONAL_MESSAGE Source: TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}.single_int32 + TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}.single_int64 == 5 =====> Result: true -[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_1]: cel.@block([optional.ofNonZeroValue(1), optional.of(4), TestAllTypes{?single_int64: @index0, ?single_int32: @index1}, @index2.single_int32, @index2.single_int64, @index3 + @index4], @index5 == 5) -[BLOCK_RECURSION_DEPTH_2]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}, @index0.single_int32 + @index0.single_int64], @index1 == 5) -[BLOCK_RECURSION_DEPTH_3]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_4]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_5]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_6]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_7]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_8]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) -[BLOCK_RECURSION_DEPTH_9]: cel.@block([TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_COMMON_SUBEXPR_ONLY]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_1]: cel.@block([optional.ofNonZeroValue(1), optional.of(4), cel.expr.conformance.proto3.TestAllTypes{?single_int64: @index0, ?single_int32: @index1}, @index2.single_int32, @index2.single_int64, @index3 + @index4], @index5 == 5) +[BLOCK_RECURSION_DEPTH_2]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}, @index0.single_int32 + @index0.single_int64], @index1 == 5) +[BLOCK_RECURSION_DEPTH_3]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_4]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_5]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_6]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_7]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_8]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) +[BLOCK_RECURSION_DEPTH_9]: cel.@block([cel.expr.conformance.proto3.TestAllTypes{?single_int64: optional.ofNonZeroValue(1), ?single_int32: optional.of(4)}], @index0.single_int32 + @index0.single_int64 == 5) Test case: CALL Source: ('h' + 'e' + 'l' + 'l' + 'o' + ' world').matches('h' + 'e' + 'l' + 'l' + 'o') diff --git a/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java b/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java index 398a554ca..a743c0439 100644 --- a/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java +++ b/parser/src/main/java/dev/cel/parser/CelUnparserVisitor.java @@ -66,6 +66,17 @@ public String unparse() { return stringBuilder.toString(); } + /** + * Unparses a specific {@link CelExpr} node within the AST. + * + *

    This method exists to allow unparsing of an arbitrary node within the stored AST in this + * visitor. + */ + public String unparse(CelExpr expr) { + visit(expr); + return stringBuilder.toString(); + } + private static String maybeQuoteField(String field) { if (RESTRICTED_FIELD_NAMES.contains(field) || !IDENTIFIER_SEGMENT_PATTERN.matcher(field).matches()) { diff --git a/policy/src/main/java/dev/cel/policy/CelPolicy.java b/policy/src/main/java/dev/cel/policy/CelPolicy.java index 00c23e7e9..9980d0cad 100644 --- a/policy/src/main/java/dev/cel/policy/CelPolicy.java +++ b/policy/src/main/java/dev/cel/policy/CelPolicy.java @@ -60,6 +60,8 @@ public static Builder newBuilder() { .setMetadata(ImmutableMap.of()); } + public abstract Builder toBuilder(); + /** Builder for {@link CelPolicy}. */ @AutoValue.Builder public abstract static class Builder { diff --git a/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel b/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel index 666915526..6e7b473eb 100644 --- a/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel +++ b/protobuf/src/main/java/dev/cel/protobuf/BUILD.bazel @@ -20,7 +20,7 @@ java_binary( ":debug_printer", ":java_file_generator", ":proto_descriptor_collector", - "//common:cel_descriptors", + "//common:cel_descriptor_util", "//common/internal:proto_java_qualified_names", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", diff --git a/publish/cel_version.bzl b/publish/cel_version.bzl index 935b2f7b1..7938f15a1 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.0" +CEL_VERSION = "0.11.1" diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel index 7c0539d00..184a01534 100644 --- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel @@ -100,6 +100,7 @@ java_library( ], deps = [ ":runtime_type_provider", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:error_codes", "//common:options", @@ -842,6 +843,7 @@ java_library( ":unknown_attributes", "//:auto_value", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:options", "//common/annotations", diff --git a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java index 37247f3d3..0e02af273 100644 --- a/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java +++ b/runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java @@ -55,16 +55,23 @@ public static RuntimeHelpers create() { // Functions // ========= - /** Convert a string to a Duration. */ + /** 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); // Ensure that the duration value can be adequately represented within a protobuf.Duration. checkArgument( dv.compareTo(DURATION_MAX) <= 0 && dv.compareTo(DURATION_MIN) >= 0, "invalid duration range"); - return Duration.newBuilder().setSeconds(dv.getSeconds()).setNanos(dv.getNano()).build(); + return dv; } catch (DateTimeParseException e) { throw new IllegalArgumentException("invalid duration format", e); } diff --git a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java index b9cc42cc1..905120988 100644 --- a/runtime/src/main/java/dev/cel/runtime/TypeResolver.java +++ b/runtime/src/main/java/dev/cel/runtime/TypeResolver.java @@ -35,6 +35,7 @@ import dev.cel.common.types.StructTypeReference; import dev.cel.common.types.TypeType; import dev.cel.common.values.CelByteString; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -64,8 +65,17 @@ 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(Duration.class, TypeType.create(SimpleType.DURATION)) - .put(Timestamp.class, TypeType.create(SimpleType.TIMESTAMP)) + .put(java.time.Duration.class, TypeType.create(SimpleType.DURATION)) + .put(Instant.class, TypeType.create(SimpleType.TIMESTAMP)) + .put( + Duration.class, + TypeType.create( + SimpleType.DURATION)) // TODO: Remove once clients have been migrated + .put( + Timestamp.class, + TypeType.create( + SimpleType + .TIMESTAMP)) // TODO: Remove once clients have been migrated .put(ArrayList.class, TypeType.create(ListType.create(SimpleType.DYN))) .put(HashMap.class, TypeType.create(MapType.create(SimpleType.DYN, SimpleType.DYN))) .put(Optional.class, TypeType.create(OptionalType.create(SimpleType.DYN))) 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 9d75b15ce..42386a942 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/AddOperator.java @@ -23,11 +23,13 @@ import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; import java.util.List; @@ -104,24 +106,51 @@ public enum AddOverload implements CelStandardOverload { (celOptions, runtimeEquality) -> CelFunctionBinding.from("add_double", Double.class, Double.class, Double::sum)), ADD_DURATION_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "add_duration_duration", Duration.class, Duration.class, ProtoTimeUtils::add)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_duration_duration", + java.time.Duration.class, + java.time.Duration.class, + DateTimeHelpers::add); + } else { + return CelFunctionBinding.from( + "add_duration_duration", Duration.class, Duration.class, ProtoTimeUtils::add); + } + }), ADD_TIMESTAMP_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "add_timestamp_duration", Timestamp.class, Duration.class, ProtoTimeUtils::add)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_timestamp_duration", + Instant.class, + java.time.Duration.class, + DateTimeHelpers::add); + } else { + return CelFunctionBinding.from( + "add_timestamp_duration", Timestamp.class, Duration.class, ProtoTimeUtils::add); + } + }), ADD_STRING( (celOptions, runtimeEquality) -> CelFunctionBinding.from( "add_string", String.class, String.class, (String x, String y) -> x + y)), ADD_DURATION_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "add_duration_timestamp", + java.time.Duration.class, + Instant.class, + (java.time.Duration d, Instant i) -> DateTimeHelpers.add(i, d)); + } else { + return CelFunctionBinding.from( "add_duration_timestamp", Duration.class, Timestamp.class, - (Duration x, Timestamp y) -> ProtoTimeUtils.add(y, x))), + (Duration d, Timestamp t) -> ProtoTimeUtils.add(t, d)); + } + }), @SuppressWarnings({"unchecked"}) ADD_LIST( (celOptions, runtimeEquality) -> 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 bac80475d..08177a474 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel +++ b/runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel @@ -50,6 +50,7 @@ java_library( ":standard_overload", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", "//runtime:function_binding", @@ -71,6 +72,7 @@ cel_android_library( ":standard_overload_android", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", "//runtime:function_binding_android", @@ -92,6 +94,7 @@ java_library( ":standard_overload", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -112,6 +115,7 @@ cel_android_library( ":standard_overload_android", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -389,9 +393,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -406,10 +410,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -423,9 +427,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -440,10 +444,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -457,9 +461,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -474,10 +478,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -491,9 +495,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -508,10 +512,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -525,9 +529,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -542,10 +546,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -559,9 +563,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -577,10 +581,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -595,9 +599,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -613,10 +617,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -631,9 +635,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -649,10 +653,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -667,9 +671,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//runtime:function_binding", "//runtime:runtime_equality", "//runtime/standard:standard_function", @@ -684,10 +688,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", "@maven_android//:com_google_guava_guava", @@ -701,9 +705,9 @@ java_library( tags = [ ], deps = [ - ":date_time_helpers", ":standard_overload", "//common:options", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -719,10 +723,10 @@ cel_android_library( tags = [ ], deps = [ - ":date_time_helpers_android", ":standard_function_android", ":standard_overload_android", "//common:options", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1307,6 +1311,7 @@ java_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//common/values:cel_byte_string", "//runtime:function_binding", @@ -1328,6 +1333,7 @@ cel_android_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//common/values:cel_byte_string", "//runtime:function_binding_android", @@ -1347,6 +1353,7 @@ java_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers", "//common/internal:proto_time_utils", "//runtime:function_binding", "//runtime:runtime_equality", @@ -1367,6 +1374,7 @@ cel_android_library( "//common:error_codes", "//common:options", "//common:runtime_exception", + "//common/internal:date_time_helpers_android", "//common/internal:proto_time_utils_android", "//runtime:function_binding_android", "//runtime:runtime_equality_android", @@ -1444,25 +1452,3 @@ java_library( "//common:error_codes", ], ) - -java_library( - name = "date_time_helpers", - srcs = ["DateTimeHelpers.java"], - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - "//common:runtime_exception", - "@maven//:com_google_protobuf_protobuf_java", - ], -) - -cel_android_library( - name = "date_time_helpers_android", - srcs = ["DateTimeHelpers.java"], - visibility = ["//visibility:private"], - deps = [ - "//common:error_codes", - "//common:runtime_exception", - "@maven_android//:com_google_protobuf_protobuf_javalite", - ], -) 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 b45d6d65b..d9b4babd5 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/BytesFunction.java @@ -50,8 +50,15 @@ public enum BytesOverload implements CelStandardOverload { } }), STRING_TO_BYTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("string_to_bytes", String.class, CelByteString::copyFromUtf8)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "string_to_bytes", String.class, CelByteString::copyFromUtf8); + } else { + return CelFunctionBinding.from( + "string_to_bytes", String.class, ByteString::copyFromUtf8); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java b/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java deleted file mode 100644 index d05125ed1..000000000 --- a/runtime/src/main/java/dev/cel/runtime/standard/DateTimeHelpers.java +++ /dev/null @@ -1,85 +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 com.google.protobuf.Timestamp; -import dev.cel.common.CelErrorCode; -import dev.cel.common.CelRuntimeException; -import java.time.DateTimeException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Locale; - -final class DateTimeHelpers { - static final String UTC = "UTC"; - - /** - * Constructs a new {@link LocalDateTime} instance - * - * @param ts Timestamp protobuf object - * @param tz Timezone based on the CEL specification. This is either the canonical name from tz - * database or a standard offset represented in (+/-)HH:MM. Few valid examples are: - *

    - * - * @return If an Invalid timezone is supplied. - */ - static LocalDateTime newLocalDateTime(Timestamp ts, String tz) { - return Instant.ofEpochSecond(ts.getSeconds(), ts.getNanos()) - .atZone(timeZone(tz)) - .toLocalDateTime(); - } - - /** - * Get the DateTimeZone Instance. - * - * @param tz the ID of the datetime zone - * @return the ZoneId object - */ - private static ZoneId timeZone(String tz) { - try { - return ZoneId.of(tz); - } catch (DateTimeException e) { - // If timezone is not a string name (for example, 'US/Central'), it should be a numerical - // offset from UTC in the format [+/-]HH:MM. - try { - int ind = tz.indexOf(":"); - if (ind == -1) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); - } - - int hourOffset = Integer.parseInt(tz.substring(0, ind)); - int minOffset = Integer.parseInt(tz.substring(ind + 1)); - // Ensures that the offset are properly formatted in [+/-]HH:MM to conform with - // ZoneOffset's format requirements. - // Example: "-9:30" -> "-09:30" and "9:30" -> "+09:30" - String formattedOffset = - ((hourOffset < 0) ? "-" : "+") - + String.format(Locale.getDefault(), "%02d:%02d", Math.abs(hourOffset), minOffset); - - return ZoneId.of(formattedOffset); - - } catch (DateTimeException e2) { - throw new CelRuntimeException(e2, CelErrorCode.BAD_FORMAT); - } - } - } - - private DateTimeHelpers() {} -} 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 d54e2999b..b17a9871e 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/DurationFunction.java @@ -43,18 +43,33 @@ public static DurationFunction create(Iterable - CelFunctionBinding.from("duration_to_duration", Duration.class, (Duration x) -> x)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_duration", java.time.Duration.class, (java.time.Duration d) -> d); + } else { + return CelFunctionBinding.from( + "duration_to_duration", Duration.class, (Duration d) -> d); + } + }), STRING_TO_DURATION( (celOptions, runtimeEquality) -> CelFunctionBinding.from( "string_to_duration", String.class, (String d) -> { - try { - return RuntimeHelpers.createDurationFromString(d); - } catch (IllegalArgumentException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + try { + return RuntimeHelpers.createJavaDurationFromString(d); + } catch (IllegalArgumentException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + } else { + try { + return RuntimeHelpers.createDurationFromString(d); + } catch (IllegalArgumentException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } } })), ; 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 bd48ec7da..1c1b7ffd4 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDateFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDate}. */ @@ -43,18 +44,35 @@ public static GetDateFunction create(Iterable o /** Overloads for the standard function. */ public enum GetDateOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_MONTH_1_BASED( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_1_based", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth()); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_1_based", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth()); + } + }), TIMESTAMP_TO_DAY_OF_MONTH_1_BASED_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_1_based_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth()); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_1_based_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth()); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 171353c04..1efa43620 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfMonthFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfMonth}. */ @@ -45,18 +46,35 @@ public static GetDayOfMonthFunction create( /** Overloads for the standard function. */ public enum GetDayOfMonthOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_MONTH( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfMonth() - 1); + } + }), TIMESTAMP_TO_DAY_OF_MONTH_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_month_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_month_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfMonth() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 92ef90e79..4fa7000eb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfWeekFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; @@ -23,6 +23,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.time.DayOfWeek; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfWeek}. */ @@ -46,18 +47,41 @@ public static GetDayOfWeekFunction create( /** Overloads for the standard function. */ public enum GetDayOfWeekOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_WEEK( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_week", + Instant.class, + (Instant ts) -> { + // CEL treats Sunday as day 0, but Java.time treats it as day 7. + DayOfWeek dayOfWeek = newLocalDateTime(ts, UTC).getDayOfWeek(); + return (long) dayOfWeek.getValue() % 7; + }); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_week", Timestamp.class, (Timestamp ts) -> { // CEL treats Sunday as day 0, but Java.time treats it as day 7. DayOfWeek dayOfWeek = newLocalDateTime(ts, UTC).getDayOfWeek(); return (long) dayOfWeek.getValue() % 7; - })), + }); + } + }), TIMESTAMP_TO_DAY_OF_WEEK_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_week_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> { + // CEL treats Sunday as day 0, but Java.time treats it as day 7. + DayOfWeek dayOfWeek = newLocalDateTime(ts, tz).getDayOfWeek(); + return (long) dayOfWeek.getValue() % 7; + }); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_week_with_tz", Timestamp.class, String.class, @@ -65,7 +89,9 @@ public enum GetDayOfWeekOverload implements CelStandardOverload { // CEL treats Sunday as day 0, but Java.time treats it as day 7. DayOfWeek dayOfWeek = newLocalDateTime(ts, tz).getDayOfWeek(); return (long) dayOfWeek.getValue() % 7; - })); + }); + } + }); private final FunctionBindingCreator bindingCreator; @Override 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 8c4d90e64..c74a85c1c 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetDayOfYearFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getDayOfYear}. */ @@ -45,18 +46,35 @@ public static GetDayOfYearFunction create( /** Overloads for the standard function. */ public enum GetDayOfYearOverload implements CelStandardOverload { TIMESTAMP_TO_DAY_OF_YEAR( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_year", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_year", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getDayOfYear() - 1); + } + }), TIMESTAMP_TO_DAY_OF_YEAR_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_day_of_year_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_day_of_year_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getDayOfYear() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 61fe189cb..ca816eb80 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetFullYearFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getFullYear}. */ @@ -44,18 +45,35 @@ public static GetFullYearFunction create( /** Overloads for the standard function. */ public enum GetFullYearOverload implements CelStandardOverload { TIMESTAMP_TO_YEAR( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_year", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getYear()); + } else { + return CelFunctionBinding.from( "timestamp_to_year", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getYear())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getYear()); + } + }), TIMESTAMP_TO_YEAR_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_year_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear()); + } else { + return CelFunctionBinding.from( "timestamp_to_year_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getYear()); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 ee36bfbb0..28d559248 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetHoursFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getHours}. */ @@ -45,21 +46,45 @@ public static GetHoursFunction create(Iterable - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_hours", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getHour()); + } else { + return CelFunctionBinding.from( "timestamp_to_hours", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getHour())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getHour()); + } + }), TIMESTAMP_TO_HOURS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_hours_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour()); + } else { + return CelFunctionBinding.from( "timestamp_to_hours_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getHour()); + } + }), DURATION_TO_HOURS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("duration_to_hours", Duration.class, ProtoTimeUtils::toHours)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_hours", java.time.Duration.class, java.time.Duration::toHours); + } else { + return CelFunctionBinding.from( + "duration_to_hours", Duration.class, ProtoTimeUtils::toHours); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 369cd9ea2..53ec92a82 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMillisecondsFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getMilliseconds}. */ @@ -51,27 +52,51 @@ public enum GetMillisecondsOverload implements CelStandardOverload { // timestamp_to_milliseconds overload @SuppressWarnings("JavaLocalDateTimeGetNano") TIMESTAMP_TO_MILLISECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_milliseconds", + Instant.class, + (Instant ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6)); + } else { + return CelFunctionBinding.from( "timestamp_to_milliseconds", Timestamp.class, - (Timestamp ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6))), - + (Timestamp ts) -> (long) (newLocalDateTime(ts, UTC).getNano() / 1e+6)); + } + }), @SuppressWarnings("JavaLocalDateTimeGetNano") TIMESTAMP_TO_MILLISECONDS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_milliseconds_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6)); + } else { + return CelFunctionBinding.from( "timestamp_to_milliseconds_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6))), + (Timestamp ts, String tz) -> (long) (newLocalDateTime(ts, tz).getNano() / 1e+6)); + } + }), DURATION_TO_MILLISECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_milliseconds", + java.time.Duration.class, + (java.time.Duration d) -> d.toMillis() % 1_000); + } else { + return CelFunctionBinding.from( "duration_to_milliseconds", Duration.class, (Duration arg) -> - ProtoTimeUtils.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis())); + ProtoTimeUtils.toMillis(arg) % java.time.Duration.ofSeconds(1).toMillis()); + } + }); private final FunctionBindingCreator bindingCreator; 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 24c285c46..62ee130b6 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMinutesFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getMinutes}. */ @@ -46,22 +47,45 @@ public static GetMinutesFunction create( /** Overloads for the standard function. */ public enum GetMinutesOverload implements CelStandardOverload { TIMESTAMP_TO_MINUTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_minutes", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getMinute()); + } else { + return CelFunctionBinding.from( "timestamp_to_minutes", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMinute())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMinute()); + } + }), TIMESTAMP_TO_MINUTES_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_minutes_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute()); + } else { + return CelFunctionBinding.from( "timestamp_to_minutes_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMinute()); + } + }), DURATION_TO_MINUTES( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_minutes", Duration.class, ProtoTimeUtils::toMinutes)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_minutes", java.time.Duration.class, java.time.Duration::toMinutes); + } else { + return CelFunctionBinding.from( + "duration_to_minutes", Duration.class, ProtoTimeUtils::toMinutes); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 34a33815d..99770e69f 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetMonthFunction.java @@ -14,14 +14,15 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function runtime definition for {@code getMonth}. */ @@ -43,18 +44,35 @@ public static GetMonthFunction create(Iterable - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_month", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_month", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1)), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getMonthValue() - 1); + } + }), TIMESTAMP_TO_MONTH_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_month_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1); + } else { + return CelFunctionBinding.from( "timestamp_to_month_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1)), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getMonthValue() - 1); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 e40fc62c5..f0357990b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GetSecondsFunction.java @@ -14,8 +14,8 @@ package dev.cel.runtime.standard; -import static dev.cel.runtime.standard.DateTimeHelpers.UTC; -import static dev.cel.runtime.standard.DateTimeHelpers.newLocalDateTime; +import static dev.cel.common.internal.DateTimeHelpers.UTC; +import static dev.cel.common.internal.DateTimeHelpers.newLocalDateTime; import com.google.common.collect.ImmutableSet; import com.google.protobuf.Duration; @@ -24,6 +24,7 @@ import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code getSeconds}. */ @@ -47,22 +48,56 @@ public static GetSecondsFunction create( /** Overloads for the standard function. */ public enum GetSecondsOverload implements CelStandardOverload { TIMESTAMP_TO_SECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_seconds", + Instant.class, + (Instant ts) -> (long) newLocalDateTime(ts, UTC).getSecond()); + } else { + return CelFunctionBinding.from( "timestamp_to_seconds", Timestamp.class, - (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getSecond())), + (Timestamp ts) -> (long) newLocalDateTime(ts, UTC).getSecond()); + } + }), TIMESTAMP_TO_SECONDS_WITH_TZ( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_seconds_with_tz", + Instant.class, + String.class, + (Instant ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond()); + } else { + return CelFunctionBinding.from( "timestamp_to_seconds_with_tz", Timestamp.class, String.class, - (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond())), + (Timestamp ts, String tz) -> (long) newLocalDateTime(ts, tz).getSecond()); + } + }), DURATION_TO_SECONDS( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_seconds", Duration.class, ProtoTimeUtils::toSeconds)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_seconds", + java.time.Duration.class, + dur -> { + long truncatedSeconds = dur.getSeconds(); + // Preserve the existing behavior from protobuf where seconds is truncated towards + // 0 when negative. + if (dur.isNegative() && dur.getNano() > 0) { + truncatedSeconds++; + } + + return truncatedSeconds; + }); + } else { + return CelFunctionBinding.from( + "duration_to_seconds", Duration.class, ProtoTimeUtils::toSeconds); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 1eaced251..03be9c41b 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterEqualsOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the greater equals (>=) operator. */ @@ -81,12 +82,21 @@ public enum GreaterEqualsOverload implements CelStandardOverload { Double.class, (Double x, Double y) -> x >= y)), GREATER_EQUALS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_equals_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) >= 0); + } else { + return CelFunctionBinding.from( "greater_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) >= 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) >= 0); + } + }), GREATER_EQUALS_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -99,12 +109,21 @@ public enum GreaterEqualsOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) >= 0)), GREATER_EQUALS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_equals_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.compareTo(i2) >= 0); + } else { + return CelFunctionBinding.from( "greater_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) >= 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) >= 0); + } + }), GREATER_EQUALS_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { 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 9eed5747b..f0db202a8 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/GreaterOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the greater (>) operator. */ @@ -73,12 +74,21 @@ public enum GreaterOverload implements CelStandardOverload { CelFunctionBinding.from( "greater_double", Double.class, Double.class, (Double x, Double y) -> x > y)), GREATER_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) > 0); + } else { + return CelFunctionBinding.from( "greater_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) > 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) > 0); + } + }), GREATER_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -91,12 +101,21 @@ public enum GreaterOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) > 0)), GREATER_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "greater_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.isAfter(i2)); + } else { + return CelFunctionBinding.from( "greater_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) > 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) > 0); + } + }), GREATER_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { 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 b29ca37a6..17ad835cb 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/IntFunction.java @@ -24,6 +24,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code int} conversion function. */ @@ -104,9 +105,15 @@ public enum IntOverload implements CelStandardOverload { } })), TIMESTAMP_TO_INT64( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "timestamp_to_int64", Timestamp.class, ProtoTimeUtils::toSeconds)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_int64", Instant.class, Instant::getEpochSecond); + } else { + return CelFunctionBinding.from( + "timestamp_to_int64", Timestamp.class, ProtoTimeUtils::toSeconds); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 3da852f8a..8ff6f5248 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessEqualsOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the less equals (<=) operator. */ @@ -77,12 +78,21 @@ public enum LessEqualsOverload implements CelStandardOverload { CelFunctionBinding.from( "less_equals_double", Double.class, Double.class, (Double x, Double y) -> x <= y)), LESS_EQUALS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_equals_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) <= 0); + } else { + return CelFunctionBinding.from( "less_equals_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) <= 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) <= 0); + } + }), LESS_EQUALS_INT64( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -95,12 +105,21 @@ public enum LessEqualsOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) <= 0)), LESS_EQUALS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_equals_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.compareTo(i2) <= 0); + } else { + return CelFunctionBinding.from( "less_equals_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) <= 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) <= 0); + } + }), LESS_EQUALS_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { 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 8bae06a85..d348555b7 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/LessOperator.java @@ -26,6 +26,7 @@ import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the less (<) operator. */ @@ -135,12 +136,21 @@ public enum LessOverload implements CelStandardOverload { Double.class, (UnsignedLong x, Double y) -> ComparisonFunctions.compareUintDouble(x, y) == -1)), LESS_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_duration", + java.time.Duration.class, + java.time.Duration.class, + (java.time.Duration d1, java.time.Duration d2) -> d1.compareTo(d2) < 0); + } else { + return CelFunctionBinding.from( "less_duration", Duration.class, Duration.class, - (Duration x, Duration y) -> ProtoTimeUtils.compare(x, y) < 0)), + (Duration d1, Duration d2) -> ProtoTimeUtils.compare(d1, d2) < 0); + } + }), LESS_STRING( (celOptions, runtimeEquality) -> CelFunctionBinding.from( @@ -149,12 +159,21 @@ public enum LessOverload implements CelStandardOverload { String.class, (String x, String y) -> x.compareTo(y) < 0)), LESS_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "less_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> i1.isBefore(i2)); + } else { + return CelFunctionBinding.from( "less_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.compare(x, y) < 0)), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.compare(t1, t2) < 0); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 3525ba5e0..aa1935876 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/StringFunction.java @@ -23,10 +23,12 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.values.CelByteString; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; +import java.time.Instant; import java.util.Arrays; /** Standard function for {@code string} conversion function. */ @@ -90,13 +92,24 @@ public enum StringOverload implements CelStandardOverload { } }), TIMESTAMP_TO_STRING( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "timestamp_to_string", Timestamp.class, ProtoTimeUtils::toString)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from("timestamp_to_string", Instant.class, Instant::toString); + } else { + return CelFunctionBinding.from( + "timestamp_to_string", Timestamp.class, ProtoTimeUtils::toString); + } + }), DURATION_TO_STRING( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "duration_to_string", Duration.class, ProtoTimeUtils::toString)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "duration_to_string", java.time.Duration.class, DateTimeHelpers::toString); + } else { + return CelFunctionBinding.from( + "duration_to_string", Duration.class, ProtoTimeUtils::toString); + } + }), UINT64_TO_STRING( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { 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 695650390..a5d1359cc 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/SubtractOperator.java @@ -22,10 +22,12 @@ import com.google.protobuf.Timestamp; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import dev.cel.runtime.RuntimeHelpers; +import java.time.Instant; import java.util.Arrays; /** Standard function for the subtraction (-) operator. */ @@ -60,19 +62,37 @@ public enum SubtractOverload implements CelStandardOverload { } })), SUBTRACT_TIMESTAMP_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_timestamp_timestamp", + Instant.class, + Instant.class, + (Instant i1, Instant i2) -> java.time.Duration.between(i2, i1)); + } else { + return CelFunctionBinding.from( "subtract_timestamp_timestamp", Timestamp.class, Timestamp.class, - (Timestamp x, Timestamp y) -> ProtoTimeUtils.between(y, x))), + (Timestamp t1, Timestamp t2) -> ProtoTimeUtils.between(t2, t1)); + } + }), SUBTRACT_TIMESTAMP_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_timestamp_duration", + Instant.class, + java.time.Duration.class, + DateTimeHelpers::subtract); + } else { + return CelFunctionBinding.from( "subtract_timestamp_duration", Timestamp.class, Duration.class, - ProtoTimeUtils::subtract)), + ProtoTimeUtils::subtract); + } + }), SUBTRACT_UINT64( (celOptions, runtimeEquality) -> { if (celOptions.enableUnsignedLongs()) { @@ -106,12 +126,21 @@ public enum SubtractOverload implements CelStandardOverload { CelFunctionBinding.from( "subtract_double", Double.class, Double.class, (Double x, Double y) -> x - y)), SUBTRACT_DURATION_DURATION( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "subtract_duration_duration", + java.time.Duration.class, + java.time.Duration.class, + DateTimeHelpers::subtract); + } else { + return CelFunctionBinding.from( "subtract_duration_duration", Duration.class, Duration.class, - ProtoTimeUtils::subtract)), + ProtoTimeUtils::subtract); + } + }), ; private final FunctionBindingCreator bindingCreator; 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 9b64b78cb..8ed250143 100644 --- a/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java +++ b/runtime/src/main/java/dev/cel/runtime/standard/TimestampFunction.java @@ -19,10 +19,13 @@ import dev.cel.common.CelErrorCode; import dev.cel.common.CelOptions; import dev.cel.common.CelRuntimeException; +import dev.cel.common.internal.DateTimeHelpers; import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.RuntimeEquality; import java.text.ParseException; +import java.time.Instant; +import java.time.format.DateTimeParseException; import java.util.Arrays; /** Standard function for {@code timestamp} conversion function. */ @@ -42,6 +45,7 @@ public static TimestampFunction create(Iterable @@ -49,19 +53,41 @@ public enum TimestampOverload implements CelStandardOverload { "string_to_timestamp", String.class, (String ts) -> { - try { - return ProtoTimeUtils.parse(ts); - } catch (ParseException e) { - throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + try { + return DateTimeHelpers.parse(ts); + } catch (DateTimeParseException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } + + } else { + try { + return ProtoTimeUtils.parse(ts); + } catch (ParseException e) { + throw new CelRuntimeException(e, CelErrorCode.BAD_FORMAT); + } } })), TIMESTAMP_TO_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from("timestamp_to_timestamp", Timestamp.class, (Timestamp x) -> x)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "timestamp_to_timestamp", Instant.class, (Instant x) -> x); + } else { + return CelFunctionBinding.from( + "timestamp_to_timestamp", Timestamp.class, (Timestamp x) -> x); + } + }), INT64_TO_TIMESTAMP( - (celOptions, runtimeEquality) -> - CelFunctionBinding.from( - "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp)), + (celOptions, runtimeEquality) -> { + if (celOptions.evaluateCanonicalTypesToNativeValues()) { + return CelFunctionBinding.from( + "int64_to_timestamp", Long.class, Instant::ofEpochSecond); + } else { + return CelFunctionBinding.from( + "int64_to_timestamp", Long.class, ProtoTimeUtils::fromSecondsToTimestamp); + } + }), ; private final FunctionBindingCreator bindingCreator; diff --git a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel index 5d083cdc9..5137cad39 100644 --- a/runtime/src/test/java/dev/cel/runtime/BUILD.bazel +++ b/runtime/src/test/java/dev/cel/runtime/BUILD.bazel @@ -28,6 +28,7 @@ java_library( "//:java_truth", "//bundle:cel", "//common:cel_ast", + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common:cel_exception", "//common:cel_source", diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java index 2896e36f4..a960b6249 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeAndroidTest.java @@ -51,6 +51,8 @@ import dev.cel.runtime.standard.EqualsOperator; import dev.cel.runtime.standard.IntFunction; import dev.cel.runtime.standard.IntFunction.IntOverload; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import org.junit.Test; @@ -705,16 +707,8 @@ public void eval_protoMessage_mapFields(String checkedExpr) throws Exception { ImmutableMap.of(true, 9.1d, false, 10.2d), ImmutableMap.of(true, 11.3d, false, 12.4d), ImmutableMap.of(true, 1L, false, 2L), // Note: Enums are converted into integers - ImmutableMap.of( - true, - ProtoTimeUtils.fromSecondsToDuration(15), - false, - ProtoTimeUtils.fromSecondsToDuration(16)), - ImmutableMap.of( - true, - ProtoTimeUtils.fromSecondsToTimestamp(17), - false, - ProtoTimeUtils.fromSecondsToTimestamp(18))) + ImmutableMap.of(true, Duration.ofSeconds(15), false, Duration.ofSeconds(16)), + ImmutableMap.of(true, Instant.ofEpochSecond(17), false, Instant.ofEpochSecond(18))) .inOrder(); } diff --git a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java index 4083ae494..4ffe0941c 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelLiteRuntimeTest.java @@ -25,14 +25,12 @@ 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.ListValue; import com.google.protobuf.StringValue; import com.google.protobuf.Struct; -import com.google.protobuf.Timestamp; import com.google.protobuf.UInt32Value; import com.google.protobuf.UInt64Value; import com.google.protobuf.util.Values; @@ -63,6 +61,8 @@ import dev.cel.testing.testdata.SimpleEnum; import dev.cel.testing.testdata.SingleFileCelDescriptor; import dev.cel.testing.testdata.SingleFileProto.SingleFile; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -301,7 +301,7 @@ public void fieldSelection_duration() throws Exception { Duration result = (Duration) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToDuration(600)); + assertThat(result).isEqualTo(Duration.ofMinutes(10)); } @Test @@ -313,9 +313,9 @@ public void fieldSelection_timestamp() throws Exception { .setSingleTimestamp(ProtoTimeUtils.fromSecondsToTimestamp(50)) .build(); - Timestamp result = (Timestamp) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); + Instant result = (Instant) CEL_RUNTIME.createProgram(ast).eval(ImmutableMap.of("msg", msg)); - assertThat(result).isEqualTo(ProtoTimeUtils.fromSecondsToTimestamp(50)); + assertThat(result).isEqualTo(Instant.ofEpochSecond(50)); } @Test diff --git a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java index 3a2bcc1f4..39723083e 100644 --- a/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java +++ b/runtime/src/test/java/dev/cel/runtime/CelRuntimeTest.java @@ -340,7 +340,8 @@ public void trace_struct() throws Exception { CelEvaluationListener listener = (expr, res) -> { assertThat(res).isEqualTo(TestAllTypes.getDefaultInstance()); - assertThat(expr.struct().messageName()).isEqualTo("TestAllTypes"); + assertThat(expr.struct().messageName()) + .isEqualTo("cel.expr.conformance.proto3.TestAllTypes"); }; Cel cel = CelFactory.standardCelBuilder() diff --git a/runtime/src/test/resources/arithmDouble.baseline b/runtime/src/test/resources/arithmDouble.baseline index 8d914202a..3c219ef29 100644 --- a/runtime/src/test/resources/arithmDouble.baseline +++ b/runtime/src/test/resources/arithmDouble.baseline @@ -1,3 +1,8 @@ +Source: 0.0 == -0.0 +=====> +bindings: {} +result: true + Source: 1.9 < 2.0 && 1.1 <= 1.1 && 2.0 > 1.9 && 1.1 >= 1.1 && 1.1 == 1.1 && 2.0 != 1.9 =====> bindings: {} diff --git a/runtime/src/test/resources/arithmDuration.baseline b/runtime/src/test/resources/arithmDuration.baseline index 3422aced4..9aa73405d 100644 --- a/runtime/src/test/resources/arithmDuration.baseline +++ b/runtime/src/test/resources/arithmDuration.baseline @@ -9,13 +9,7 @@ declare d3 { value google.protobuf.Duration } =====> -bindings: {d3=seconds: 25 -nanos: 45 -} +> {d2=seconds: 10 -nanos: 20 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {d3=PT25.000000045S} +> {d2=PT10.00000002S} +> {d1=PT15.000000025S} result: true Source: d3 - d1 == d2 @@ -29,11 +23,5 @@ declare d3 { value google.protobuf.Duration } =====> -bindings: {d3=seconds: 25 -nanos: 45 -} +> {d2=seconds: 10 -nanos: 20 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {d3=PT25.000000045S} +> {d2=PT10.00000002S} +> {d1=PT15.000000025S} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/arithmTimestamp.baseline b/runtime/src/test/resources/arithmTimestamp.baseline index d7a1f318c..7ddf702ae 100644 --- a/runtime/src/test/resources/arithmTimestamp.baseline +++ b/runtime/src/test/resources/arithmTimestamp.baseline @@ -9,13 +9,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: ts1 - d1 == ts2 @@ -29,13 +23,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: ts2 + d1 == ts1 @@ -49,13 +37,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true Source: d1 + ts2 == ts1 @@ -69,11 +51,5 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {ts2=seconds: 10 -nanos: 10 -} +> {ts1=seconds: 25 -nanos: 35 -} +> {d1=seconds: 15 -nanos: 25 -} +bindings: {ts2=1970-01-01T00:00:10.000000010Z} +> {ts1=1970-01-01T00:00:25.000000035Z} +> {d1=PT15.000000025S} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/duration.baseline b/runtime/src/test/resources/duration.baseline index 9737a23c7..89d058086 100644 --- a/runtime/src/test/resources/duration.baseline +++ b/runtime/src/test/resources/duration.baseline @@ -6,11 +6,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -21,11 +17,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -36,11 +28,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 < d2 @@ -51,11 +39,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: true Source: d1 < d2 @@ -66,11 +50,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: true Source: d1 <= d2 @@ -81,11 +61,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: false Source: d1 <= d2 @@ -96,11 +72,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 <= d2 @@ -111,11 +83,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 <= d2 @@ -126,11 +94,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: true Source: d1 <= d2 @@ -141,11 +105,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: true Source: d1 > d2 @@ -156,11 +116,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: true Source: d1 > d2 @@ -171,11 +127,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 > d2 @@ -186,11 +138,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: false Source: d1 > d2 @@ -201,11 +149,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: false Source: d1 > d2 @@ -216,11 +160,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: false Source: d1 >= d2 @@ -231,11 +171,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 9 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.000000009S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -246,11 +182,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 9 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT9.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -261,11 +193,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.00000001S} result: true Source: d1 >= d2 @@ -276,11 +204,7 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 10 -nanos: 9 -} +bindings: {d2=PT10.00000001S} +> {d1=PT10.000000009S} result: false Source: d1 >= d2 @@ -291,9 +215,5 @@ declare d2 { value google.protobuf.Duration } =====> -bindings: {d2=seconds: 10 -nanos: 10 -} +> {d1=seconds: 9 -nanos: 10 -} +bindings: {d2=PT10.00000001S} +> {d1=PT9.00000001S} result: false \ No newline at end of file diff --git a/runtime/src/test/resources/durationFunctions.baseline b/runtime/src/test/resources/durationFunctions.baseline index 8e942e0d8..9bc61d4ae 100644 --- a/runtime/src/test/resources/durationFunctions.baseline +++ b/runtime/src/test/resources/durationFunctions.baseline @@ -3,9 +3,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 25 Source: d1.getHours() @@ -13,9 +11,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -25 Source: d1.getMinutes() @@ -23,9 +19,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 1559 Source: d1.getMinutes() @@ -33,9 +27,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -1559 Source: d1.getSeconds() @@ -43,9 +35,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 93541 Source: d1.getSeconds() @@ -53,9 +43,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -93541 Source: d1.getMilliseconds() @@ -63,9 +51,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {d1=PT25H59M1.011S} result: 11 Source: d1.getMilliseconds() @@ -73,9 +59,7 @@ declare d1 { value google.protobuf.Duration } =====> -bindings: {d1=seconds: -93541 -nanos: -11000000 -} +bindings: {d1=PT-25H-59M-1.011S} result: -11 Source: d1.getHours() < val @@ -86,9 +70,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getMinutes() > val @@ -99,9 +81,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getSeconds() > val @@ -112,9 +92,7 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} +bindings: {val=30} +> {d1=PT25H59M1.011S} result: true Source: d1.getMilliseconds() < val @@ -125,7 +103,5 @@ declare val { value int } =====> -bindings: {val=30} +> {d1=seconds: 93541 -nanos: 11000000 -} -result: true \ No newline at end of file +bindings: {val=30} +> {d1=PT25H59M1.011S} +result: true diff --git a/runtime/src/test/resources/dynamicMessage_adapted.baseline b/runtime/src/test/resources/dynamicMessage_adapted.baseline index 789a39488..b012c3727 100644 --- a/runtime/src/test/resources/dynamicMessage_adapted.baseline +++ b/runtime/src/test/resources/dynamicMessage_adapted.baseline @@ -690,9 +690,7 @@ list_value { } } } -result: seconds: 10 -nanos: 20 - +result: PT10.00000002S Source: msg.single_timestamp declare msg { @@ -755,9 +753,7 @@ list_value { } } } -result: seconds: 100 -nanos: 200 - +result: 1970-01-01T00:01:40.000000200Z Source: msg.single_value declare msg { diff --git a/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline b/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline index 36dac04c3..8f91353b4 100644 --- a/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline +++ b/runtime/src/test/resources/dynamicMessage_dynamicDescriptor.baseline @@ -103,8 +103,7 @@ declare dur { bindings: {dur=type_url: "type.googleapis.com/google.protobuf.Duration" value: "\bd" } -result: seconds: 100 - +result: PT1M40S Source: TestAllTypes { single_any: any_packed_test_msg }.single_any declare any_packed_test_msg { diff --git a/runtime/src/test/resources/timeConversions.baseline b/runtime/src/test/resources/timeConversions.baseline index 68892ee2d..12a521441 100644 --- a/runtime/src/test/resources/timeConversions.baseline +++ b/runtime/src/test/resources/timeConversions.baseline @@ -4,9 +4,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 63126020 -nanos: 21000000 - +result: 1972-01-01T15:00:20.021Z Source: timestamp(123) declare t1 { @@ -14,8 +12,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 123 - +result: 1970-01-01T00:02:03Z Source: duration("15.11s") declare t1 { @@ -23,17 +20,14 @@ declare t1 { } =====> bindings: {} -result: seconds: 15 -nanos: 110000000 - +result: PT15.11S Source: int(t1) == 100 declare t1 { value google.protobuf.Timestamp } =====> -bindings: {t1=seconds: 100 -} +bindings: {t1=1970-01-01T00:01:40Z} result: true Source: duration("1h2m3.4s") @@ -42,9 +36,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 3723 -nanos: 400000000 - +result: PT1H2M3.4S Source: duration(duration('15.0s')) declare t1 { @@ -52,8 +44,7 @@ declare t1 { } =====> bindings: {} -result: seconds: 15 - +result: PT15S Source: timestamp(timestamp(123)) declare t1 { @@ -61,4 +52,4 @@ declare t1 { } =====> bindings: {} -result: seconds: 123 \ No newline at end of file +result: 1970-01-01T00:02:03Z \ No newline at end of file diff --git a/runtime/src/test/resources/timestamp.baseline b/runtime/src/test/resources/timestamp.baseline index c215b581f..828e7f912 100644 --- a/runtime/src/test/resources/timestamp.baseline +++ b/runtime/src/test/resources/timestamp.baseline @@ -6,11 +6,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -21,11 +17,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -36,11 +28,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 < t2 @@ -51,11 +39,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: true Source: t1 < t2 @@ -66,11 +50,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: true Source: t1 <= t2 @@ -81,11 +61,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 <= t2 @@ -96,11 +72,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 <= t2 @@ -111,11 +83,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 <= t2 @@ -126,11 +94,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: true Source: t1 <= t2 @@ -141,11 +105,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: true Source: t1 > t2 @@ -156,11 +116,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 > t2 @@ -171,11 +127,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 > t2 @@ -186,11 +138,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: false Source: t1 > t2 @@ -201,11 +149,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: false Source: t1 > t2 @@ -216,11 +160,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} result: false Source: t1 >= t2 @@ -231,11 +171,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 9 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000009Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -246,11 +182,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 9 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:09.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -261,11 +193,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 10 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000010Z} result: true Source: t1 >= t2 @@ -276,11 +204,7 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 10 -nanos: 9 -} +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:10.000000009Z} result: false Source: t1 >= t2 @@ -291,9 +215,5 @@ declare t2 { value google.protobuf.Timestamp } =====> -bindings: {t2=seconds: 10 -nanos: 10 -} +> {t1=seconds: 9 -nanos: 10 -} -result: false +bindings: {t2=1970-01-01T00:00:10.000000010Z} +> {t1=1970-01-01T00:00:09.000000010Z} +result: false \ No newline at end of file diff --git a/runtime/src/test/resources/timestampFunctions.baseline b/runtime/src/test/resources/timestampFunctions.baseline index 322137892..e932867e8 100644 --- a/runtime/src/test/resources/timestampFunctions.baseline +++ b/runtime/src/test/resources/timestampFunctions.baseline @@ -3,9 +3,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1969 Source: ts1.getFullYear() @@ -13,9 +11,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getFullYear() @@ -23,8 +19,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 1969 Source: ts1.getFullYear("Indian/Cocos") @@ -32,9 +27,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getFullYear("2:00") @@ -42,9 +35,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1970 Source: ts1.getMonth("America/Los_Angeles") @@ -52,9 +43,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMonth() @@ -62,9 +51,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMonth() @@ -72,8 +59,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 11 Source: ts1.getMonth("Indian/Cocos") @@ -81,9 +67,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMonth("-8:15") @@ -91,9 +75,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getDayOfYear("America/Los_Angeles") @@ -101,9 +83,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 364 Source: ts1.getDayOfYear() @@ -111,9 +91,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfYear() @@ -121,8 +99,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 364 Source: ts1.getDayOfYear("Indian/Cocos") @@ -130,9 +107,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfYear("-9:00") @@ -140,9 +115,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 364 Source: ts1.getDayOfMonth("America/Los_Angeles") @@ -150,9 +123,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 30 Source: ts1.getDayOfMonth() @@ -160,9 +131,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfMonth() @@ -170,8 +139,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 30 Source: ts1.getDayOfMonth("Indian/Cocos") @@ -179,9 +147,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDayOfMonth("8:00") @@ -189,9 +155,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getDate("America/Los_Angeles") @@ -199,9 +163,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 31 Source: ts1.getDate() @@ -209,9 +171,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDate() @@ -219,8 +179,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 31 Source: ts1.getDate("Indian/Cocos") @@ -228,9 +187,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDate("9:30") @@ -238,9 +195,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getDayOfWeek("America/Los_Angeles") @@ -248,8 +203,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 6 Source: ts1.getDayOfWeek() @@ -257,8 +211,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 0 Source: ts1.getDayOfWeek() @@ -266,8 +219,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 3 Source: ts1.getDayOfWeek("Indian/Cocos") @@ -275,8 +227,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 0 Source: ts1.getDayOfWeek("-9:30") @@ -284,8 +235,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 259200 -} +bindings: {ts1=1970-01-04T00:00:00Z} result: 6 Source: ts1.getHours("America/Los_Angeles") @@ -293,9 +243,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 16 Source: ts1.getHours() @@ -303,9 +251,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getHours() @@ -313,8 +259,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 23 Source: ts1.getHours("Indian/Cocos") @@ -322,9 +267,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 6 Source: ts1.getHours("6:30") @@ -332,9 +275,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 6 Source: ts1.getMinutes("America/Los_Angeles") @@ -342,9 +283,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMinutes() @@ -352,9 +291,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getMinutes() @@ -362,8 +299,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 59 Source: ts1.getMinutes("Indian/Cocos") @@ -371,9 +307,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 30 Source: ts1.getMinutes("-8:00") @@ -381,9 +315,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 0 Source: ts1.getSeconds("America/Los_Angeles") @@ -391,9 +323,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds() @@ -401,9 +331,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds() @@ -411,8 +339,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: -1 -} +bindings: {ts1=1969-12-31T23:59:59Z} result: 59 Source: ts1.getSeconds("Indian/Cocos") @@ -420,9 +347,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getSeconds("-8:00") @@ -430,9 +355,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 1 Source: ts1.getMilliseconds("America/Los_Angeles") @@ -440,9 +363,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds() @@ -450,9 +371,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds("Indian/Cocos") @@ -460,9 +379,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getMilliseconds("-8:00") @@ -470,9 +387,7 @@ declare ts1 { value google.protobuf.Timestamp } =====> -bindings: {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {ts1=1970-01-01T00:00:01.011Z} result: 11 Source: ts1.getFullYear() < val @@ -483,9 +398,7 @@ declare val { value int } =====> -bindings: {val=2013} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=2013} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMonth() < val @@ -496,9 +409,7 @@ declare val { value int } =====> -bindings: {val=12} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=12} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfYear() < val @@ -509,9 +420,7 @@ declare val { value int } =====> -bindings: {val=13} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=13} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfMonth() < val @@ -522,9 +431,7 @@ declare val { value int } =====> -bindings: {val=10} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=10} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDate() < val @@ -535,9 +442,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getDayOfWeek() < val @@ -548,9 +453,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getHours() < val @@ -561,9 +464,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMinutes() < val @@ -574,9 +475,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getSeconds() < val @@ -587,9 +486,7 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true Source: ts1.getMilliseconds() < val @@ -600,7 +497,5 @@ declare val { value int } =====> -bindings: {val=15} +> {ts1=seconds: 1 -nanos: 11000000 -} +bindings: {val=15} +> {ts1=1970-01-01T00:00:01.011Z} result: true \ No newline at end of file diff --git a/runtime/src/test/resources/typeComparisons.baseline b/runtime/src/test/resources/typeComparisons.baseline index ae3e8aa01..e8fc3473f 100644 --- a/runtime/src/test/resources/typeComparisons.baseline +++ b/runtime/src/test/resources/typeComparisons.baseline @@ -42,3 +42,14 @@ Source: type(null) == null_type =====> bindings: {} result: true + +Source: type(duration) == google.protobuf.Duration && type(timestamp) == google.protobuf.Timestamp +declare duration { + value dyn +} +declare timestamp { + value dyn +} +=====> +bindings: {duration=PT0S, timestamp=1970-01-01T00:00:00Z} +result: true diff --git a/runtime/src/test/resources/unknownResultSet.baseline b/runtime/src/test/resources/unknownResultSet.baseline index 8c7183761..ad97004f3 100644 --- a/runtime/src/test/resources/unknownResultSet.baseline +++ b/runtime/src/test/resources/unknownResultSet.baseline @@ -60,7 +60,7 @@ declare x { } =====> bindings: {} -error: evaluation error at test_location:31: Failed to parse timestamp: invalid timestamp "bad timestamp string" +error: evaluation error at test_location:31: Text 'bad timestamp string' could not be parsed at index 0 error_code: BAD_FORMAT Source: x.single_int32 == 1 || x.single_string == "test" @@ -125,7 +125,7 @@ declare x { } =====> bindings: {} -error: evaluation error at test_location:31: Failed to parse timestamp: invalid timestamp "bad timestamp string" +error: evaluation error at test_location:31: Text 'bad timestamp string' could not be parsed at index 0 error_code: BAD_FORMAT Source: x.single_int32.f(1) diff --git a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java index cd0da5b80..24ed1ea8d 100644 --- a/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java +++ b/testing/src/main/java/dev/cel/testing/BaseInterpreterTest.java @@ -37,7 +37,6 @@ import com.google.protobuf.Descriptors.Descriptor; import com.google.protobuf.Descriptors.FileDescriptor; import com.google.protobuf.DoubleValue; -import com.google.protobuf.Duration; import com.google.protobuf.DynamicMessage; import com.google.protobuf.FloatValue; import com.google.protobuf.Int32Value; @@ -83,6 +82,8 @@ import dev.cel.runtime.CelVariableResolver; import dev.cel.testing.testdata.proto3.StandaloneGlobalEnum; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -305,6 +306,9 @@ public void arithmUInt64_error() { @Test public void arithmDouble() { + source = "0.0 == -0.0"; + runTest(); + source = "1.9 < 2.0 && 1.1 <= 1.1 && 2.0 > 1.9 && 1.1 >= 1.1 && 1.1 == 1.1 && 2.0 != 1.9"; runTest(); @@ -344,9 +348,9 @@ public void arithmTimestamp() { declareVariable("ts1", SimpleType.TIMESTAMP); declareVariable("ts2", SimpleType.TIMESTAMP); declareVariable("d1", SimpleType.DURATION); - Duration d1 = Duration.newBuilder().setSeconds(15).setNanos(25).build(); - Timestamp ts1 = Timestamp.newBuilder().setSeconds(25).setNanos(35).build(); - Timestamp ts2 = Timestamp.newBuilder().setSeconds(10).setNanos(10).build(); + Duration d1 = Duration.ofSeconds(15, 25); + Instant ts1 = Instant.ofEpochSecond(25, 35); + Instant ts2 = Instant.ofEpochSecond(10, 10); CelVariableResolver resolver = extend( extend(ImmutableMap.of("d1", d1), ImmutableMap.of("ts1", ts1)), @@ -371,9 +375,9 @@ public void arithmDuration() { declareVariable("d1", SimpleType.DURATION); declareVariable("d2", SimpleType.DURATION); declareVariable("d3", SimpleType.DURATION); - Duration d1 = Duration.newBuilder().setSeconds(15).setNanos(25).build(); - Duration d2 = Duration.newBuilder().setSeconds(10).setNanos(20).build(); - Duration d3 = Duration.newBuilder().setSeconds(25).setNanos(45).build(); + java.time.Duration d1 = java.time.Duration.ofSeconds(15, 25); + java.time.Duration d2 = java.time.Duration.ofSeconds(10, 20); + java.time.Duration d3 = java.time.Duration.ofSeconds(25, 45); CelVariableResolver resolver = extend( @@ -572,7 +576,7 @@ public void containers() { source = "proto3.TestAllTypes{} == cel.expr.conformance.proto3.TestAllTypes{}"; runTest(); - source = "SGAR"; // From StandaloneGLobaLEnum + source = "SGAR"; // From StandaloneGlobalEnum runTest(); } @@ -606,9 +610,9 @@ public void has() throws Exception { public void duration() throws Exception { declareVariable("d1", SimpleType.DURATION); declareVariable("d2", SimpleType.DURATION); - Duration d1010 = Duration.newBuilder().setSeconds(10).setNanos(10).build(); - Duration d1009 = Duration.newBuilder().setSeconds(10).setNanos(9).build(); - Duration d0910 = Duration.newBuilder().setSeconds(9).setNanos(10).build(); + 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()); source = "d1 < d2"; @@ -644,9 +648,9 @@ public void duration() throws Exception { public void timestamp() throws Exception { declareVariable("t1", SimpleType.TIMESTAMP); declareVariable("t2", SimpleType.TIMESTAMP); - Timestamp ts1010 = Timestamp.newBuilder().setSeconds(10).setNanos(10).build(); - Timestamp ts1009 = Timestamp.newBuilder().setSeconds(10).setNanos(9).build(); - Timestamp ts0910 = Timestamp.newBuilder().setSeconds(9).setNanos(10).build(); + 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()); source = "t1 < t2"; @@ -691,7 +695,7 @@ public void packUnpackAny() { declareVariable( "message", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName())); declareVariable("list", ListType.create(SimpleType.DYN)); - Duration duration = ProtoTimeUtils.fromSecondsToDuration(100); + com.google.protobuf.Duration duration = ProtoTimeUtils.fromSecondsToDuration(100); Any any = Any.pack(duration); TestAllTypes message = TestAllTypes.newBuilder().setSingleAny(any).build(); @@ -960,10 +964,11 @@ public void namespacedVariables() { @Test public void durationFunctions() { declareVariable("d1", SimpleType.DURATION); - Duration d1 = - Duration.newBuilder().setSeconds(25 * 3600 + 59 * 60 + 1).setNanos(11000000).build(); - Duration d2 = - Duration.newBuilder().setSeconds(-(25 * 3600 + 59 * 60 + 1)).setNanos(-11000000).build(); + long totalSeconds = 25 * 3600 + 59 * 60 + 1; + long nanos = 11000000; + Duration d1 = Duration.ofSeconds(totalSeconds, nanos); + Duration d2 = Duration.ofSeconds(-totalSeconds, -nanos); + container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); source = "d1.getHours()"; @@ -997,8 +1002,8 @@ public void durationFunctions() { public void timestampFunctions() { declareVariable("ts1", SimpleType.TIMESTAMP); container = CelContainer.ofName(Type.getDescriptor().getFile().getPackage()); - Timestamp ts1 = Timestamp.newBuilder().setSeconds(1).setNanos(11000000).build(); - Timestamp ts2 = ProtoTimeUtils.fromSecondsToTimestamp(-1); + Instant ts1 = Instant.ofEpochSecond(1, 11000000); + Instant ts2 = Instant.ofEpochSecond(-1, 0); source = "ts1.getFullYear(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", ts1)); @@ -1050,7 +1055,7 @@ public void timestampFunctions() { source = "ts1.getDate(\"9:30\")"; runTest(ImmutableMap.of("ts1", ts1)); - Timestamp tsSunday = ProtoTimeUtils.fromSecondsToTimestamp(3 * 24 * 3600); + Instant tsSunday = Instant.ofEpochSecond(3 * 24 * 3600); source = "ts1.getDayOfWeek(\"America/Los_Angeles\")"; runTest(ImmutableMap.of("ts1", tsSunday)); source = "ts1.getDayOfWeek()"; @@ -1372,7 +1377,7 @@ public void timeConversions() { runTest(); source = "int(t1) == 100"; - runTest(ImmutableMap.of("t1", ProtoTimeUtils.fromSecondsToTimestamp(100))); + runTest(ImmutableMap.of("t1", Instant.ofEpochSecond(100))); source = "duration(\"1h2m3.4s\")"; runTest(); @@ -1927,6 +1932,15 @@ public void typeComparisons() { // Test whether null resolves to null_type. source = "type(null) == null_type"; runTest(); + + // Test runtime resolution of types + source = + "type(duration) == google.protobuf.Duration && " + + "type(timestamp) == google.protobuf.Timestamp"; + // Intentionally declare as dyns + declareVariable("duration", SimpleType.DYN); + declareVariable("timestamp", SimpleType.DYN); + runTest(ImmutableMap.of("duration", java.time.Duration.ZERO, "timestamp", Instant.EPOCH)); } @Test @@ -2075,7 +2089,8 @@ public void dynamicMessage_adapted() throws Exception { .setSingleStringWrapper(StringValue.of("hello")) .setSingleUint32Wrapper(UInt32Value.of(12)) .setSingleUint64Wrapper(UInt64Value.of(34)) - .setSingleDuration(Duration.newBuilder().setSeconds(10).setNanos(20)) + .setSingleDuration( + com.google.protobuf.Duration.newBuilder().setSeconds(10).setNanos(20)) .setSingleTimestamp(Timestamp.newBuilder().setSeconds(100).setNanos(200)) .setSingleValue(Value.newBuilder().setStringValue("a")) .setSingleStruct( @@ -2127,10 +2142,10 @@ public void dynamicMessage_adapted() throws Exception { .isInstanceOf(BASE_CEL_OPTIONS.enableUnsignedLongs() ? UnsignedLong.class : Long.class); source = "msg.single_duration"; - assertThat(runTest(input)).isInstanceOf(Duration.class); + assertThat(runTest(input)).isInstanceOf(java.time.Duration.class); source = "msg.single_timestamp"; - assertThat(runTest(input)).isInstanceOf(Timestamp.class); + assertThat(runTest(input)).isInstanceOf(Instant.class); source = "msg.single_value"; assertThat(runTest(input)).isInstanceOf(String.class); diff --git a/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel b/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel index bfbe68cab..6924f753f 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/testrunner/BUILD.bazel @@ -15,6 +15,7 @@ java_library( ], deps = [ ":annotations", + ":cel_coverage_index", ":cel_test_suite", ":cel_test_suite_exception", ":cel_test_suite_text_proto_parser", @@ -33,7 +34,11 @@ java_library( srcs = ["JUnitXmlReporter.java"], tags = [ ], - deps = ["@maven//:com_google_guava_guava"], + deps = [ + ":cel_coverage_index", + "@maven//:com_google_guava_guava", + "@maven//:org_jspecify_jspecify", + ], ) java_library( @@ -42,11 +47,32 @@ java_library( tags = [ ], deps = [ + ":cel_coverage_index", ":cel_expression_source", ":cel_test_context", ":cel_test_suite", ":test_runner_library", "@maven//:junit_junit", + "@maven//:org_jspecify_jspecify", + ], +) + +java_library( + name = "cel_coverage_index", + srcs = ["CelCoverageIndex.java"], + tags = [ + ], + deps = [ + "//:auto_value", + "//common:cel_ast", + "//common/ast", + "//common/navigation", + "//common/types:type_providers", + "//parser:unparser_visitor", + "//runtime:evaluation_listener", + "@maven//:com_google_code_findbugs_annotations", + "@maven//:com_google_errorprone_error_prone_annotations", + "@maven//:com_google_guava_guava", ], ) @@ -56,6 +82,7 @@ java_library( tags = [ ], deps = [ + ":cel_coverage_index", ":cel_expression_source", ":cel_test_context", ":cel_test_suite", @@ -80,6 +107,7 @@ java_library( "@cel_spec//proto/cel/expr:expr_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_protobuf_protobuf_java", + "@maven//:org_jspecify_jspecify", ], ) diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java new file mode 100644 index 000000000..d99525934 --- /dev/null +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelCoverageIndex.java @@ -0,0 +1,429 @@ +// 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.testing.testrunner; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.io.Files; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import javax.annotation.concurrent.ThreadSafe; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.ast.CelExpr; +import dev.cel.common.ast.CelExpr.ExprKind; +import dev.cel.common.navigation.CelNavigableAst; +import dev.cel.common.navigation.CelNavigableExpr; +import dev.cel.common.types.CelKind; +import dev.cel.parser.CelUnparserVisitor; +import dev.cel.runtime.CelEvaluationListener; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import java.util.logging.Logger; + +/** + * A class for managing the coverage index for CEL tests. + * + *

    This class is used to manage the coverage index for CEL tests. It provides a method for + * getting the coverage index for a given test case. + */ +final class CelCoverageIndex { + + private static final Logger logger = Logger.getLogger(CelCoverageIndex.class.getName()); + + private static final String DIGRAPH_HEADER = "digraph {\n"; + private static final String UNCOVERED_NODE_STYLE = "color=\"indianred2\", style=filled"; + private static final String PARTIALLY_COVERED_NODE_STYLE = "color=\"lightyellow\"," + + "style=filled"; + private static final String COMPLETELY_COVERED_NODE_STYLE = "color=\"lightgreen\"," + + "style=filled"; + + private CelAbstractSyntaxTree ast; + private final ConcurrentHashMap nodeCoverageStatsMap = + new ConcurrentHashMap<>(); + + public void init(CelAbstractSyntaxTree ast) { + // If the AST and node coverage stats map are already initialized, then we don't need to + // re-initialize them. + if (this.ast == null && nodeCoverageStatsMap.isEmpty()) { + this.ast = ast; + CelNavigableExpr.fromExpr(ast.getExpr()) + .allNodes() + .forEach( + celNavigableExpr -> { + NodeCoverageStats nodeCoverageStats = new NodeCoverageStats(); + nodeCoverageStats.isBooleanNode.set(isNodeTypeBoolean(celNavigableExpr.expr())); + nodeCoverageStatsMap.put(celNavigableExpr.id(), nodeCoverageStats); + }); + } + } + + /** + * Returns the evaluation listener for the CEL test suite. + * + *

    This listener is used to track the coverage of the CEL test suite. + */ + public CelEvaluationListener newEvaluationListener() { + return new EvaluationListener(nodeCoverageStatsMap); + } + + /** A class for managing the coverage report for a CEL test suite. */ + @AutoValue + public abstract static class CoverageReport { + public abstract String celExpression(); + + public abstract long nodes(); + + public abstract long coveredNodes(); + + public abstract long branches(); + + public abstract long coveredBooleanOutcomes(); + + public abstract ImmutableList unencounteredNodes(); + + public abstract ImmutableList unencounteredBranches(); + + public abstract String dotGraph(); + + // Currently only supported inside google3. + public abstract String graphUrl(); + + public static Builder builder() { + return new AutoValue_CelCoverageIndex_CoverageReport.Builder() + .setNodes(0L) + .setCoveredNodes(0L) + .setBranches(0L) + .setCelExpression("") + .setDotGraph("") + .setGraphUrl("") + .setCoveredBooleanOutcomes(0L); + } + + /** Builder for {@link CoverageReport}. */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setCelExpression(String value); + + public abstract long nodes(); + + public abstract Builder setNodes(long value); + + public abstract long coveredNodes(); + + public abstract Builder setCoveredNodes(long value); + + public abstract long branches(); + + public abstract Builder setBranches(long value); + + public abstract long coveredBooleanOutcomes(); + + public abstract Builder setCoveredBooleanOutcomes(long value); + + public abstract Builder setDotGraph(String value); + + public abstract Builder setGraphUrl(String value); + + public abstract ImmutableList.Builder unencounteredNodesBuilder(); + + public abstract ImmutableList.Builder unencounteredBranchesBuilder(); + + @CanIgnoreReturnValue + public final Builder addUnencounteredNodes(String value) { + unencounteredNodesBuilder().add(value); + return this; + } + + @CanIgnoreReturnValue + public final Builder addUnencounteredBranches(String value) { + unencounteredBranchesBuilder().add(value); + return this; + } + + public abstract CoverageReport build(); + } + } + + /** + * Generates a coverage report for the CEL test suite. + * + *

    Note: If the generated graph URL results in a `Request Entity Too Large` error, download the + * `coverage_graph.txt` file from the test artifacts and upload its contents to Graphviz to render the coverage graph. + */ + public CoverageReport generateCoverageReport() { + CoverageReport.Builder reportBuilder = + CoverageReport.builder().setCelExpression(new CelUnparserVisitor(ast).unparse()); + StringBuilder dotGraphBuilder = new StringBuilder(DIGRAPH_HEADER); + traverseAndCalculateCoverage( + CelNavigableAst.fromAst(ast).getRoot(), + nodeCoverageStatsMap, + true, + "", + reportBuilder, + dotGraphBuilder); + dotGraphBuilder.append("}"); + String dotGraph = dotGraphBuilder.toString(); + + CoverageReport report = reportBuilder.setDotGraph(dotGraph).build(); + logger.info("CEL Expression: " + report.celExpression()); + logger.info("Nodes: " + report.nodes()); + logger.info("Covered Nodes: " + report.coveredNodes()); + logger.info("Branches: " + report.branches()); + logger.info("Covered Boolean Outcomes: " + report.coveredBooleanOutcomes()); + logger.info("Unencountered Nodes: \n" + String.join("\n", report.unencounteredNodes())); + logger.info("Unencountered Branches: \n" + String.join("\n", + report.unencounteredBranches())); + logger.info("Dot Graph: " + report.dotGraph()); + + writeDotGraphToArtifact(dotGraph); + return report; + } + + /** A class for managing the coverage stats for a CEL node. */ + @ThreadSafe + private static final class NodeCoverageStats { + final AtomicBoolean isBooleanNode = new AtomicBoolean(false); + final AtomicBoolean covered = new AtomicBoolean(false); + final AtomicBoolean hasTrueBranch = new AtomicBoolean(false); + final AtomicBoolean hasFalseBranch = new AtomicBoolean(false); + } + + private Boolean isNodeTypeBoolean(CelExpr celExpr) { + return ast.getTypeMap().containsKey(celExpr.id()) + && ast.getTypeMap().get(celExpr.id()).kind().equals(CelKind.BOOL); + } + + private void traverseAndCalculateCoverage( + CelNavigableExpr node, + Map statsMap, + boolean logUnencountered, + String precedingTabs, + CoverageReport.Builder reportBuilder, + StringBuilder dotGraphBuilder) { + long nodeId = node.id(); + NodeCoverageStats stats = statsMap.getOrDefault(nodeId, new NodeCoverageStats()); + reportBuilder.setNodes(reportBuilder.nodes() + 1); + + boolean isInterestingBooleanNode = isInterestingBooleanNode(node, stats); + + String exprText = new CelUnparserVisitor(ast).unparse(node.expr()); + String nodeCoverageStyle = UNCOVERED_NODE_STYLE; + if (stats.covered.get()) { + if (isInterestingBooleanNode) { + if (stats.hasTrueBranch.get() && stats.hasFalseBranch.get()) { + nodeCoverageStyle = COMPLETELY_COVERED_NODE_STYLE; + } else { + nodeCoverageStyle = PARTIALLY_COVERED_NODE_STYLE; + } + } else { + nodeCoverageStyle = COMPLETELY_COVERED_NODE_STYLE; + } + } + String escapedExprText = escapeSpecialCharacters(exprText); + dotGraphBuilder.append( + String.format( + "%d [shape=record, %s, label=\"{<1> exprID: %d | <2> %s} | <3> %s\"];\n", + nodeId, nodeCoverageStyle, nodeId, kindToString(node), escapedExprText)); + + // Update coverage for the current node and determine if we should continue logging + // unencountered. + logUnencountered = + updateNodeCoverage( + nodeId, stats, isInterestingBooleanNode, exprText, logUnencountered, reportBuilder); + + if (isInterestingBooleanNode) { + precedingTabs = + updateBooleanBranchCoverage( + nodeId, stats, exprText, precedingTabs, logUnencountered, reportBuilder); + } + + for (CelNavigableExpr child : node.children().collect(toImmutableList())) { + dotGraphBuilder.append(String.format("%d -> %d;\n", nodeId, child.id())); + traverseAndCalculateCoverage( + child, statsMap, logUnencountered, precedingTabs, reportBuilder, dotGraphBuilder); + } + } + + private boolean isInterestingBooleanNode(CelNavigableExpr node, NodeCoverageStats stats) { + return stats.isBooleanNode.get() + && !node.expr().getKind().equals(ExprKind.Kind.CONSTANT) + && !(node.expr().getKind().equals(ExprKind.Kind.CALL) + && node.expr().call().function().equals("cel.@block")); + } + + /** + * Updates the coverage report based on whether the current node was covered. Returns true if + * logging of unencountered nodes should continue for children, false otherwise. + */ + private boolean updateNodeCoverage( + long nodeId, + NodeCoverageStats stats, + boolean isInterestingBooleanNode, + String exprText, + boolean logUnencountered, + CoverageReport.Builder reportBuilder) { + if (stats.covered.get()) { + reportBuilder.setCoveredNodes(reportBuilder.coveredNodes() + 1); + } else { + if (logUnencountered) { + if (isInterestingBooleanNode) { + reportBuilder.addUnencounteredNodes( + String.format("Expression ID %d ('%s')", nodeId, exprText)); + } + // Once an unencountered node is found, we don't log further unencountered nodes in its + // subtree to avoid noise. + return false; + } + } + return logUnencountered; + } + + /** + * Updates the coverage report for boolean nodes, including branch coverage. Returns the + * potentially modified `precedingTabs` string. + */ + private String updateBooleanBranchCoverage( + long nodeId, + NodeCoverageStats stats, + String exprText, + String precedingTabs, + boolean logUnencountered, + CoverageReport.Builder reportBuilder) { + reportBuilder.setBranches(reportBuilder.branches() + 2); + if (stats.hasTrueBranch.get()) { + reportBuilder.setCoveredBooleanOutcomes(reportBuilder.coveredBooleanOutcomes() + 1); + } else if (logUnencountered) { + reportBuilder.addUnencounteredBranches( + String.format( + "%sExpression ID %d ('%s'): lacks 'true' coverage", precedingTabs, nodeId, exprText)); + precedingTabs += "\t\t"; + } + if (stats.hasFalseBranch.get()) { + reportBuilder.setCoveredBooleanOutcomes(reportBuilder.coveredBooleanOutcomes() + 1); + } else if (logUnencountered) { + reportBuilder.addUnencounteredBranches( + String.format( + "%sExpression ID %d ('%s'): lacks 'false' coverage", + precedingTabs, nodeId, exprText)); + precedingTabs += "\t\t"; + } + return precedingTabs; + } + + @ThreadSafe + private static final class EvaluationListener implements CelEvaluationListener { + + private final ConcurrentHashMap nodeCoverageStatsMap; + + EvaluationListener(ConcurrentHashMap nodeCoverageStatsMap) { + this.nodeCoverageStatsMap = nodeCoverageStatsMap; + } + + @Override + public void callback(CelExpr celExpr, Object evaluationResult) { + NodeCoverageStats nodeCoverageStats = nodeCoverageStatsMap.get(celExpr.id()); + nodeCoverageStats.covered.set(true); + if (nodeCoverageStats.isBooleanNode.get()) { + if (evaluationResult instanceof Boolean) { + if ((Boolean) evaluationResult) { + nodeCoverageStats.hasTrueBranch.set(true); + } else { + nodeCoverageStats.hasFalseBranch.set(true); + } + } + } + } + } + + private String kindToString(CelNavigableExpr node) { + if (node.parent().isPresent() + && node.parent().get().expr().getKind().equals(ExprKind.Kind.COMPREHENSION)) { + CelExpr.CelComprehension comp = node.parent().get().expr().comprehension(); + if (node.id() == comp.iterRange().id()) { + return "IterRange"; + } + if (node.id() == comp.accuInit().id()) { + return "AccuInit"; + } + if (node.id() == comp.loopCondition().id()) { + return "LoopCondition"; + } + if (node.id() == comp.loopStep().id()) { + return "LoopStep"; + } + if (node.id() == comp.result().id()) { + return "Result"; + } + } + + switch (node.getKind()) { + case CALL: + return "Call Node"; + case COMPREHENSION: + return "Comprehension Node"; + case IDENT: + return "Ident Node"; + case LIST: + return "List Node"; + case CONSTANT: + return "Literal Node"; + case MAP: + return "Map Node"; + case SELECT: + return "Select Node"; + case STRUCT: + return "Struct Node"; + default: + return "Unspecified Node"; + } + } + + private static String escapeSpecialCharacters(String exprText) { + return exprText + .replace("\\\"", "\"") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("||", " \\| \\| ") + .replace("<", "\\<") + .replace(">", "\\>") + .replace("{", "\\{") + .replace("}", "\\}"); + } + + private void writeDotGraphToArtifact(String dotGraph) { + String testOutputsDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + if (testOutputsDir == null) { + // Only for non-bazel/blaze users, we write to a subdirectory under the cwd. + testOutputsDir = "cel_artifacts"; + } + File outputDir = new File(testOutputsDir, "cel_test_coverage"); + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + try { + Files.asCharSink(new File(outputDir, "coverage_graph.txt"), UTF_8).write(dotGraph); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java b/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java index 15c68d6b4..02180ffb7 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/CelUserTestTemplate.java @@ -15,6 +15,7 @@ package dev.cel.testing.testrunner; import dev.cel.testing.testrunner.CelTestSuite.CelTestSection.CelTestCase; +import org.jspecify.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -27,7 +28,12 @@ @RunWith(Parameterized.class) public abstract class CelUserTestTemplate { - @Parameter public CelTestCase testCase; + @Parameter(0) + public CelTestCase testCase; + + @Parameter(1) + public @Nullable CelCoverageIndex celCoverageIndex; + private final CelTestContext celTestContext; public CelUserTestTemplate(CelTestContext celTestContext) { @@ -36,7 +42,11 @@ public CelUserTestTemplate(CelTestContext celTestContext) { @Test public void test() throws Exception { - TestRunnerLibrary.runTest(testCase, updateCelTestContext(celTestContext)); + if (celCoverageIndex != null) { + TestRunnerLibrary.runTest(testCase, updateCelTestContext(celTestContext), celCoverageIndex); + } else { + TestRunnerLibrary.runTest(testCase, updateCelTestContext(celTestContext)); + } } /** diff --git a/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java b/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java index df45f4c2c..7be21e1e3 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/JUnitXmlReporter.java @@ -31,6 +31,7 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; +import org.jspecify.annotations.Nullable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; @@ -70,9 +71,13 @@ void onStart(TestContext context) { testContext = context; } - /** Called after all tests are run */ void onFinish() { - generateReport(); + generateReport(/* coverageReport= */ null); + } + + /** Called after all tests are run */ + void onFinish(CelCoverageIndex.@Nullable CoverageReport coverageReport) { + generateReport(coverageReport); } /** Returns the number of failed tests */ @@ -84,7 +89,7 @@ int getNumFailed() { * Generates junit equivalent xml report that sponge/fusion can understand. Called after all tests * are run */ - void generateReport() { + void generateReport(CelCoverageIndex.@Nullable CoverageReport coverageReport) { try { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); @@ -123,6 +128,7 @@ void generateReport() { prevSuite = currentSuite; currentSuite = doc.createElement(XmlConstants.TESTSUITE); rootElement.appendChild(currentSuite); + addCoverageAttributes(currentSuite, coverageReport); currentSuite.setAttribute(XmlConstants.ATTR_NAME, tr.getTestClassName()); if (prevSuite != null) { prevSuite.setAttribute(XmlConstants.ATTR_TESTS, "" + testsInSuite); @@ -186,6 +192,52 @@ void generateReport() { } } + private void addCoverageAttributes( + Element currentSuite, CelCoverageIndex.@Nullable CoverageReport coverageReport) { + if (coverageReport == null) { + return; + } + if (coverageReport.nodes() == 0) { + currentSuite.setAttribute(XmlConstants.ATTR_CEL_COVERAGE, "No coverage stats found"); + } else { + // CEL expression + currentSuite.setAttribute(XmlConstants.ATTR_CEL_EXPR, coverageReport.celExpression()); + // Node coverage + double nodeCoverage = + (double) coverageReport.coveredNodes() / (double) coverageReport.nodes() * 100.0; + String nodeCoverageString = + String.format( + "%.2f%% (%d out of %d nodes covered)", + nodeCoverage, coverageReport.coveredNodes(), coverageReport.nodes()); + currentSuite.setAttribute(XmlConstants.ATTR_AST_NODE_COVERAGE, nodeCoverageString); + if (!coverageReport.unencounteredNodes().isEmpty()) { + currentSuite.setAttribute( + XmlConstants.ATTR_INTERESTING_UNENCOUNTERED_NODES, + String.join("\n", coverageReport.unencounteredNodes())); + } + // Branch coverage + double branchCoverage = 0.0; + if (coverageReport.branches() > 0) { + branchCoverage = + (double) coverageReport.coveredBooleanOutcomes() + / (double) coverageReport.branches() + * 100.0; + } + String branchCoverageString = + String.format( + "%.2f%% (%d out of %d branch outcomes covered)", + branchCoverage, coverageReport.coveredBooleanOutcomes(), coverageReport.branches()); + currentSuite.setAttribute(XmlConstants.ATTR_AST_BRANCH_COVERAGE, branchCoverageString); + if (!coverageReport.unencounteredBranches().isEmpty()) { + currentSuite.setAttribute( + XmlConstants.ATTR_INTERESTING_UNENCOUNTERED_BRANCH_PATHS, + String.join("\n", coverageReport.unencounteredBranches())); + } + currentSuite.setAttribute( + XmlConstants.ATTR_CEL_TEST_COVERAGE_GRAPH_URL, coverageReport.graphUrl()); + } + } + /** Description of a test suite execution. */ static interface TestContext { String getSuiteName(); @@ -226,5 +278,14 @@ private static final class XmlConstants { static final String ATTR_TYPE = "type"; static final String ATTR_MESSAGE = "message"; static final String ATTR_CLASSNAME = "classname"; + // Coverage attributes. + static final String ATTR_CEL_EXPR = "Cel_Expr"; + static final String ATTR_CEL_COVERAGE = "Cel_Coverage"; + static final String ATTR_AST_NODE_COVERAGE = "Ast_Node_Coverage"; + static final String ATTR_INTERESTING_UNENCOUNTERED_NODES = "Interesting_Unencountered_Nodes"; + static final String ATTR_AST_BRANCH_COVERAGE = "Ast_Branch_Coverage"; + static final String ATTR_INTERESTING_UNENCOUNTERED_BRANCH_PATHS = + "Interesting_Unencountered_Branch_Paths"; + static final String ATTR_CEL_TEST_COVERAGE_GRAPH_URL = "Cel_Test_Coverage_Graph_URL"; } } diff --git a/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java b/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java index f853b117b..6f6dff3c1 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/TestExecutor.java @@ -20,7 +20,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.ZoneId.systemDefault; -import com.google.common.collect.ImmutableList; import com.google.common.io.Files; import dev.cel.testing.testrunner.Annotations.TestSuiteSupplier; import dev.cel.testing.testrunner.CelTestSuite.CelTestSection; @@ -32,6 +31,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; +import java.util.ArrayList; import java.util.Arrays; import org.junit.runner.Description; import org.junit.runner.JUnitCore; @@ -187,15 +187,21 @@ public static void runTests() throws Exception { testReporter.onStart(testContext); boolean allTestsPassed = true; + CelCoverageIndex celCoverageIndex = null; + // If coverage is enabled, the celCoverageIndex parameter will be added in the test class. + // This will be used to run the test case multiple times with different inputs and collect + // the coverage data. + String isCoverageEnabled = System.getProperty("is_coverage_enabled"); + if (isCoverageEnabled != null && Boolean.parseBoolean(isCoverageEnabled)) { + celCoverageIndex = new CelCoverageIndex(); + } for (CelTestSection testSection : testSuite.sections()) { for (CelTestCase testCase : testSection.tests()) { String testName = testSection.name() + "." + testCase.name(); - - Object[] parameter = new Object[] {testCase}; + ArrayList parameter = new ArrayList<>(Arrays.asList(testCase, celCoverageIndex)); TestWithParameters test = - new TestWithParameters( - testName, new TestClass(testClass), ImmutableList.copyOf(parameter)); + new TestWithParameters(testName, new TestClass(testClass), parameter); TestResult testResult = new TestResult(testName, testClass.getName()); testReporter.onTestStart(testResult); @@ -235,7 +241,12 @@ public String describe() { } testContext.done(); - testReporter.onFinish(); + if (celCoverageIndex != null) { + CelCoverageIndex.CoverageReport report = celCoverageIndex.generateCoverageReport(); + testReporter.onFinish(report); + } else { + testReporter.onFinish(); + } if (!allTestsPassed) { throw new RuntimeException(testReporter.getNumFailed() + " tests failed"); } diff --git a/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java b/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java index 13b660036..a5e912ccb 100644 --- a/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java +++ b/testing/src/main/java/dev/cel/testing/testrunner/TestRunnerLibrary.java @@ -58,6 +58,7 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.logging.Logger; +import org.jspecify.annotations.Nullable; /** Runner library for creating the environment and running the assertions. */ public final class TestRunnerLibrary { @@ -80,9 +81,40 @@ public static void runTest(CelTestCase testCase, CelTestContext celTestContext) } } + /** + * Run the assertions for a given raw/checked expression test case with coverage enabled. + * + *

    This method is used for generating coverage data. It will be used to run the test case + * multiple times with different inputs and collect the coverage data. + * + * @param testCase The test case to run. + * @param celTestContext The test context containing the {@link Cel} bundle and other + * configurations. + * @param celCoverageIndex The coverage index to use for the test case. + */ + public static void runTest( + CelTestCase testCase, + CelTestContext celTestContext, + @Nullable CelCoverageIndex celCoverageIndex) + throws Exception { + if (celTestContext.celExpression().isPresent()) { + evaluateTestCase(testCase, celTestContext, celCoverageIndex); + } else { + throw new IllegalArgumentException("No cel expression provided."); + } + } + @VisibleForTesting static void evaluateTestCase(CelTestCase testCase, CelTestContext celTestContext) throws Exception { + evaluateTestCase(testCase, celTestContext, /* celCoverageIndex= */ null); + } + + static void evaluateTestCase( + CelTestCase testCase, + CelTestContext celTestContext, + @Nullable CelCoverageIndex celCoverageIndex) + throws Exception { celTestContext = extendCelTestContext(celTestContext); CelAbstractSyntaxTree ast; CelExpressionSource celExpressionSource = celTestContext.celExpression().get(); @@ -108,7 +140,10 @@ static void evaluateTestCase(CelTestCase testCase, CelTestContext celTestContext throw new IllegalArgumentException( "Unsupported expression type: " + celExpressionSource.type()); } - evaluate(ast, testCase, celTestContext); + if (celCoverageIndex != null) { + celCoverageIndex.init(ast); + } + evaluate(ast, testCase, celTestContext, celCoverageIndex); } private static CelAbstractSyntaxTree readAstFromCheckedExpression( @@ -198,7 +233,10 @@ private static CelAbstractSyntaxTree compilePolicy( } private static void evaluate( - CelAbstractSyntaxTree ast, CelTestCase testCase, CelTestContext celTestContext) + CelAbstractSyntaxTree ast, + CelTestCase testCase, + CelTestContext celTestContext, + @Nullable CelCoverageIndex celCoverageIndex) throws Exception { Cel cel = celTestContext.cel(); Program program = cel.createProgram(ast); @@ -206,7 +244,7 @@ private static void evaluate( CelEvaluationException error = null; Object evaluationResult = null; try { - evaluationResult = getEvaluationResult(testCase, celTestContext, program); + evaluationResult = getEvaluationResult(testCase, celTestContext, program, celCoverageIndex); exprValue = toExprValue(evaluationResult, ast.getResultType()); } catch (CelEvaluationException e) { String errorMessage = @@ -245,7 +283,10 @@ private static void evaluate( } private static Object getEvaluationResult( - CelTestCase testCase, CelTestContext celTestContext, Program program) + CelTestCase testCase, + CelTestContext celTestContext, + Program program, + @Nullable CelCoverageIndex celCoverageIndex) throws CelEvaluationException, IOException, CelValidationException { if (celTestContext.celLateFunctionBindings().isPresent()) { return program.eval( @@ -253,11 +294,16 @@ private static Object getEvaluationResult( } switch (testCase.input().kind()) { case CONTEXT_MESSAGE: - return program.eval(unpackAny(testCase.input().contextMessage(), celTestContext)); + return getEvaluationResultWithMessage( + unpackAny(testCase.input().contextMessage(), celTestContext), + program, + celCoverageIndex); case CONTEXT_EXPR: - return program.eval(getEvaluatedContextExpr(testCase, celTestContext)); + return getEvaluationResultWithMessage( + getEvaluatedContextExpr(testCase, celTestContext), program, celCoverageIndex); case BINDINGS: - return program.eval(getBindings(testCase, celTestContext)); + return getEvaluationResultWithBindings( + getBindings(testCase, celTestContext), program, celCoverageIndex); case NO_INPUT: ImmutableMap.Builder newBindings = ImmutableMap.builder(); for (Map.Entry entry : celTestContext.variableBindings().entrySet()) { @@ -267,11 +313,30 @@ private static Object getEvaluationResult( newBindings.put(entry); } } - return program.eval(newBindings.buildOrThrow()); + return getEvaluationResultWithBindings( + newBindings.buildOrThrow(), program, celCoverageIndex); } throw new IllegalArgumentException("Unexpected input type: " + testCase.input().kind()); } + private static Object getEvaluationResultWithBindings( + Map bindings, Program program, @Nullable CelCoverageIndex celCoverageIndex) + throws CelEvaluationException { + if (celCoverageIndex != null) { + return program.trace(bindings, celCoverageIndex.newEvaluationListener()); + } + return program.eval(bindings); + } + + private static Object getEvaluationResultWithMessage( + Message message, Program program, @Nullable CelCoverageIndex celCoverageIndex) + throws CelEvaluationException { + if (celCoverageIndex != null) { + return program.trace(message, celCoverageIndex.newEvaluationListener()); + } + return program.eval(message); + } + private static Message unpackAny(Any any, CelTestContext celTestContext) throws IOException { if (!celTestContext.fileDescriptorSetPath().isPresent()) { throw new IllegalArgumentException( diff --git a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel index a7e4c628c..2947709e5 100644 --- a/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel +++ b/testing/src/main/java/dev/cel/testing/utils/BUILD.bazel @@ -15,8 +15,10 @@ java_library( tags = [ ], deps = [ + "//common:cel_descriptor_util", "//common:cel_descriptors", "//common/internal:default_instance_message_factory", + "//common/internal:proto_time_utils", "//common/types", "//common/types:type_providers", "//common/values", @@ -50,6 +52,7 @@ java_library( tags = [ ], deps = [ + "//common:cel_descriptor_util", "//common:cel_descriptors", "//testing/testrunner:class_loader_utils", "@maven//:com_google_guava_guava", diff --git a/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java b/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java index 0b27fa452..041c0f52d 100644 --- a/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java +++ b/testing/src/main/java/dev/cel/testing/utils/ExprValueUtils.java @@ -31,6 +31,7 @@ import dev.cel.common.CelDescriptorUtil; import dev.cel.common.CelDescriptors; import dev.cel.common.internal.DefaultInstanceMessageFactory; +import dev.cel.common.internal.ProtoTimeUtils; import dev.cel.common.types.CelType; import dev.cel.common.types.ListType; import dev.cel.common.types.MapType; @@ -41,6 +42,8 @@ import dev.cel.runtime.CelUnknownSet; import dev.cel.testing.testrunner.RegistryUtils; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -252,6 +255,19 @@ public static Value toValue(Object object, CelType type) throws Exception { } return Value.newBuilder().setMapValue(builder.build()).build(); } + + if (object instanceof Instant) { + return Value.newBuilder() + .setObjectValue(Any.pack(ProtoTimeUtils.toProtoTimestamp((Instant) object))) + .build(); + } + + if (object instanceof Duration) { + return Value.newBuilder() + .setObjectValue(Any.pack(ProtoTimeUtils.toProtoDuration((Duration) object))) + .build(); + } + if (object instanceof Message) { return Value.newBuilder().setObjectValue(Any.pack((Message) object)).build(); } 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 db1202aa8..8ae4ea580 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel +++ b/testing/src/test/java/dev/cel/testing/testrunner/BUILD.bazel @@ -82,12 +82,34 @@ java_test( test_class = "dev.cel.testing.testrunner.JUnitXmlReporterTest", deps = [ "//:java_truth", + "//testing/testrunner:cel_coverage_index", "//testing/testrunner:junit_xml_reporter", "@maven//:junit_junit", "@maven//:org_mockito_mockito_core", ], ) +java_test( + name = "cel_coverage_index_test", + srcs = ["CelCoverageIndexTest.java"], + test_class = "dev.cel.testing.testrunner.CelCoverageIndexTest", + deps = [ + "//:java_truth", + "//bundle:cel", + "//common:cel_ast", + "//common:options", + "//common/types", + "//compiler:compiler_builder", + "//extensions", + "//parser:macro", + "//runtime", + "//runtime:evaluation_listener", + "//testing/testrunner:cel_coverage_index", + "@maven//:com_google_guava_guava", + "@maven//:junit_junit", + ], +) + java_test( name = "default_result_matcher_test", srcs = ["DefaultResultMatcherTest.java"], @@ -355,3 +377,33 @@ cel_java_test( test_src = ":user_test", test_suite = "simple_test_case/tests.textproto", ) + +java_library( + name = "coverage_test", + srcs = ["CoverageTest.java"], + deps = [ + "//bundle:cel", + "//common/types", + "//testing/testrunner:cel_test_context", + "//testing/testrunner:cel_user_test_template", + "@maven//:junit_junit", + ], +) + +cel_java_test( + name = "cel_coverage_test", + cel_expr = "coverage_test_case/simple_expression.cel", + enable_coverage = True, + test_data_path = "//testing/src/test/resources/expressions", + test_src = ":coverage_test", + test_suite = "coverage_test_case/tests.textproto", +) + +cel_java_test( + name = "cel_partial_coverage_test", + cel_expr = "coverage_test_case/simple_expression.cel", + enable_coverage = True, + test_data_path = "//testing/src/test/resources/expressions", + test_src = ":coverage_test", + test_suite = "coverage_test_case/partial_coverage_tests.textproto", +) diff --git a/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java new file mode 100644 index 000000000..4c6a6cf26 --- /dev/null +++ b/testing/src/test/java/dev/cel/testing/testrunner/CelCoverageIndexTest.java @@ -0,0 +1,181 @@ +// 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.testing.testrunner; + +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; +import dev.cel.bundle.Cel; +import dev.cel.bundle.CelFactory; +import dev.cel.common.CelAbstractSyntaxTree; +import dev.cel.common.CelOptions; +import dev.cel.common.types.SimpleType; +import dev.cel.compiler.CelCompiler; +import dev.cel.extensions.CelExtensions; +import dev.cel.parser.CelStandardMacro; +import dev.cel.runtime.CelEvaluationListener; +import dev.cel.runtime.CelRuntime; +import dev.cel.testing.testrunner.CelCoverageIndex.CoverageReport; +import java.io.File; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class CelCoverageIndexTest { + + @Test + public void getCoverageReport_fullCoverage() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .addVar("x", SimpleType.INT) + .addVar("y", SimpleType.INT) + .build(); + CelAbstractSyntaxTree ast = cel.compile("x > 1 && y > 1").getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of("x", 2L, "y", 2L), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.nodes()).isGreaterThan(0); + assertThat(report.coveredNodes()).isEqualTo(report.nodes()); + assertThat(report.branches()).isEqualTo(6); + assertThat(report.coveredBooleanOutcomes()) + .isEqualTo(3); // x>1 -> true, y>1 -> true, && -> true + assertThat(report.unencounteredNodes()).isEmpty(); + assertThat(report.unencounteredBranches()) + .containsExactly( + "Expression ID 4 ('x > 1 && y > 1'): lacks 'false' coverage", + "\t\tExpression ID 2 ('x > 1'): lacks 'false' coverage", + "\t\tExpression ID 6 ('y > 1'): lacks 'false' coverage"); + } + + @Test + public void getCoverageReport_partialCoverage_shortCircuit() throws Exception { + Cel cel = + CelFactory.standardCelBuilder() + .addVar("x", SimpleType.INT) + .addVar("y", SimpleType.INT) + .build(); + CelAbstractSyntaxTree ast = cel.compile("x > 1 && y > 1").getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of("x", 0L, "y", 2L), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.celExpression()).isEqualTo("x > 1 && y > 1"); + assertThat(report.nodes()).isGreaterThan(0); + assertThat(report.coveredNodes()).isLessThan(report.nodes()); + assertThat(report.branches()).isEqualTo(6); + assertThat(report.coveredBooleanOutcomes()).isEqualTo(2); // x>1 -> false, && -> false + assertThat(report.unencounteredNodes()).containsExactly("Expression ID 6 ('y > 1')"); + // y > 1 is unencountered, so logUnencountered becomes false, and branch coverage for y > 1 + // isn't logged to unencounteredBranches. + assertThat(report.unencounteredBranches()) + .containsExactly( + "Expression ID 4 ('x > 1 && y > 1'): lacks 'true' coverage", + "\t\tExpression ID 2 ('x > 1'): lacks 'true' coverage"); + } + + @Test + public void getCoverageReport_comprehension_generatesDotGraph() throws Exception { + Cel cel = CelFactory.standardCelBuilder().build(); + CelCompiler compiler = + cel.toCompilerBuilder() + .setOptions(CelOptions.newBuilder().populateMacroCalls(true).build()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addLibraries(CelExtensions.comprehensions()) + .build(); + CelAbstractSyntaxTree ast = compiler.compile("[1, 2, 3].all(i, i % 2 != 0)").getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of(), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.dotGraph()) + .contains("label=\"{<1> exprID: 1 | <2> IterRange} | <3> [1, 2, 3]\""); + assertThat(report.dotGraph()).contains("label=\"{<1> exprID: 12 | <2> AccuInit} | <3> true\""); + assertThat(report.dotGraph()).doesNotContain("red"); // No unencountered nodes. + assertThat(report.dotGraph()) + .contains( + "label=\"{<1> exprID: 14 | <2> LoopCondition} | <3>" + + " @not_strictly_false(@result)\""); + assertThat(report.dotGraph()) + .contains("label=\"{<1> exprID: 16 | <2> LoopStep} | <3> @result && i % 2 != 0\""); + assertThat(report.dotGraph()).contains("label=\"{<1> exprID: 17 | <2> Result} | <3> @result\""); + } + + @Test + public void getCoverageReport_fullCoverage_writesToUndeclaredOutputs() throws Exception { + // Setup for a more complex graph to write. + Cel cel = CelFactory.standardCelBuilder().build(); + CelCompiler compiler = + cel.toCompilerBuilder() + .setOptions(CelOptions.newBuilder().populateMacroCalls(true).build()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addLibraries(CelExtensions.comprehensions()) + .build(); + CelAbstractSyntaxTree ast = compiler.compile("[1, 2, 3].all(i, i % 2 != 0)").getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + program.trace(ImmutableMap.of(), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + + String undeclaredOutputsDir = System.getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + assertThat(undeclaredOutputsDir).isNotNull(); + + File outputFile = new File(undeclaredOutputsDir, "cel_test_coverage/coverage_graph.txt"); + + String fileContent = Files.asCharSource(outputFile, UTF_8).read(); + assertThat(fileContent).isEqualTo(report.dotGraph()); + } + + @Test + public void getCoverageReport_fullCoverage_multipleEvaluations() throws Exception { + Cel cel = CelFactory.standardCelBuilder().addVar("x", SimpleType.INT).build(); + CelAbstractSyntaxTree ast = cel.compile("x > 1").getAst(); + CelRuntime.Program program = cel.createProgram(ast); + CelCoverageIndex coverageIndex = new CelCoverageIndex(); + coverageIndex.init(ast); + CelEvaluationListener listener = coverageIndex.newEvaluationListener(); + + program.trace(ImmutableMap.of("x", 2L), listener); + coverageIndex.init(ast); // Re-initialize the coverage index. + program.trace(ImmutableMap.of("x", 0L), listener); + + CoverageReport report = coverageIndex.generateCoverageReport(); + assertThat(report.nodes()).isGreaterThan(0); + assertThat(report.coveredNodes()).isEqualTo(report.nodes()); + assertThat(report.branches()).isEqualTo(2); + // Despite re-initializing the coverage index now, the report should still + // be fully covered. Else, only the second evaluation would've been covered. + assertThat(report.coveredBooleanOutcomes()).isEqualTo(2); + assertThat(report.unencounteredNodes()).isEmpty(); + assertThat(report.unencounteredBranches()).isEmpty(); + } +} diff --git a/testing/src/test/java/dev/cel/testing/testrunner/CoverageTest.java b/testing/src/test/java/dev/cel/testing/testrunner/CoverageTest.java new file mode 100644 index 000000000..c7b0c395d --- /dev/null +++ b/testing/src/test/java/dev/cel/testing/testrunner/CoverageTest.java @@ -0,0 +1,39 @@ +// 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.testing.testrunner; + +import dev.cel.bundle.CelFactory; +import dev.cel.common.types.SimpleType; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Sample test class used for coverage analysis, demonstrating the use of the CEL test runner + * without a config file. + */ +@RunWith(Parameterized.class) +public class CoverageTest extends CelUserTestTemplate { + + public CoverageTest() { + super( + CelTestContext.newBuilder() + .setCel( + CelFactory.standardCelBuilder() + .addVar("x", SimpleType.INT) + .addVar("y", SimpleType.INT) + .build()) + .build()); + } +} diff --git a/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java b/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java index efdd9fedc..b2a610ccb 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/JUnitXmlReporterTest.java @@ -123,4 +123,93 @@ public void testGenerateReport_failure() throws IOException { assertThat(concatenatedFileContent).contains("failures=\"1\""); assertThat(concatenatedFileContent).contains("failure message=\"Test Exception\""); } + + @Test + public void testGenerateReport_coverageReport_noCoverage() throws IOException { + String outputFileName = "test-report-with-coverage.xml"; + File subFolder = tempFolder.newFolder("subFolder"); + File outputFile = new File(subFolder.getAbsolutePath(), outputFileName); + JUnitXmlReporter reporter = new JUnitXmlReporter(outputFile.getAbsolutePath()); + long startTime = 100L; + long test1EndTime = startTime + 400; + long endTime = startTime + 900; + + CelCoverageIndex.CoverageReport coverageReport = + CelCoverageIndex.CoverageReport.builder().build(); + + when(context.getSuiteName()).thenReturn(SUITE_NAME); + when(context.getStartTime()).thenReturn(startTime); + when(context.getEndTime()).thenReturn(endTime); + reporter.onStart(context); + + when(result1.getTestClassName()).thenReturn(TEST_CLASS_NAME); + when(result1.getName()).thenReturn(TEST_METHOD_NAME); + when(result1.getStartMillis()).thenReturn(startTime); + when(result1.getEndMillis()).thenReturn(test1EndTime); + when(result1.getStatus()).thenReturn(JUnitXmlReporter.TestResult.SUCCESS); + reporter.onTestSuccess(result1); + + reporter.onFinish(coverageReport); + assertThat(outputFile.exists()).isTrue(); + String concatenatedFileContent = String.join("\n", Files.readAllLines(outputFile.toPath())); + + assertThat(concatenatedFileContent).contains("No coverage stats found"); + + outputFile.delete(); + } + + @Test + public void testGenerateReport_coverageReport_withCoverage() throws IOException { + String outputFileName = "test-report-with-coverage.xml"; + File subFolder = tempFolder.newFolder("subFolder"); + File outputFile = new File(subFolder.getAbsolutePath(), outputFileName); + JUnitXmlReporter reporter = new JUnitXmlReporter(outputFile.getAbsolutePath()); + long startTime = 100L; + long test1EndTime = startTime + 400; + long endTime = startTime + 900; + + CelCoverageIndex.CoverageReport coverageReport = + CelCoverageIndex.CoverageReport.builder() + .setNodes(10L) + .setCoveredNodes(10L) + .setBranches(10L) + .setCoveredBooleanOutcomes(5L) + .addUnencounteredNodes("Node 1") + .addUnencounteredNodes("Node 2") + .addUnencounteredBranches("Branch 1") + .addUnencounteredBranches("Branch 2") + .setGraphUrl("http://graphviz/url") + .build(); + + when(context.getSuiteName()).thenReturn(SUITE_NAME); + when(context.getStartTime()).thenReturn(startTime); + when(context.getEndTime()).thenReturn(endTime); + reporter.onStart(context); + + when(result1.getTestClassName()).thenReturn(TEST_CLASS_NAME); + when(result1.getName()).thenReturn(TEST_METHOD_NAME); + when(result1.getStartMillis()).thenReturn(startTime); + when(result1.getEndMillis()).thenReturn(test1EndTime); + when(result1.getStatus()).thenReturn(JUnitXmlReporter.TestResult.SUCCESS); + reporter.onTestSuccess(result1); + + reporter.onFinish(coverageReport); + assertThat(outputFile.exists()).isTrue(); + String concatenatedFileContent = String.join("\n", Files.readAllLines(outputFile.toPath())); + + assertThat(concatenatedFileContent) + .contains( + ""); + + outputFile.delete(); + } } diff --git a/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java b/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java index 8c1129b0c..d5a5248a8 100644 --- a/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java +++ b/testing/src/test/java/dev/cel/testing/testrunner/TestRunnerLibraryTest.java @@ -263,4 +263,22 @@ public void runTest_missingProtoDescriptors_failure() throws Exception { .hasMessageThat() .contains("Proto descriptors are required for unpacking Any messages."); } + + @Test + public void triggerRunTest_evaluateRawExpr_withCoverage() throws Exception { + CelCoverageIndex celCoverageIndex = new CelCoverageIndex(); + CelTestCase simpleOutputTestCase = + CelTestCase.newBuilder() + .setName("simple_output_test_case") + .setDescription("simple_output_test_case_description") + .setOutput(CelTestSuite.CelTestSection.CelTestCase.Output.ofResultValue(true)) + .build(); + + TestRunnerLibrary.runTest( + simpleOutputTestCase, + CelTestContext.newBuilder() + .setCelExpression(CelExpressionSource.fromRawExpr("1 > 0")) + .build(), + celCoverageIndex); + } } diff --git a/testing/src/test/resources/expressions/coverage_test_case/partial_coverage_tests.textproto b/testing/src/test/resources/expressions/coverage_test_case/partial_coverage_tests.textproto new file mode 100644 index 000000000..61b57637c --- /dev/null +++ b/testing/src/test/resources/expressions/coverage_test_case/partial_coverage_tests.textproto @@ -0,0 +1,26 @@ +# proto-file: google3/third_party/cel/spec/proto/cel/expr/conformance/test/suite.proto +# proto-message: cel.expr.conformance.test.TestSuite + +name: "partial_coverage_tests" +description: "Tests for partial coverage." +sections: { + name: "simple_map_operations" + description: "Tests for map operations." + tests: { + name: "literal_and_sum" + description: "Test that a map can be created and values can be accessed." + input: { + key: "x" + value { value { int64_value: 2 } } + } + input { + key: "y" + value { value { int64_value: 2 } } + } + output { + result_value { + bool_value: true + } + } + } +} diff --git a/testing/src/test/resources/expressions/coverage_test_case/simple_expression.cel b/testing/src/test/resources/expressions/coverage_test_case/simple_expression.cel new file mode 100644 index 000000000..0e8256613 --- /dev/null +++ b/testing/src/test/resources/expressions/coverage_test_case/simple_expression.cel @@ -0,0 +1 @@ +{'sum': x + y, 'literal': 3}.sum == 3 || x == y \ No newline at end of file diff --git a/testing/src/test/resources/expressions/coverage_test_case/tests.textproto b/testing/src/test/resources/expressions/coverage_test_case/tests.textproto new file mode 100644 index 000000000..fefee8d6c --- /dev/null +++ b/testing/src/test/resources/expressions/coverage_test_case/tests.textproto @@ -0,0 +1,26 @@ +# proto-file: google3/third_party/cel/spec/proto/cel/expr/conformance/test/suite.proto +# proto-message: cel.expr.conformance.test.TestSuite + +name: "coverage_tests" +description: "Tests for coverage." +sections: { + name: "simple_map_operations" + description: "Tests for map operations." + tests: { + name: "literal_and_sum" + description: "Test that a map can be created and values can be accessed." + input: { + key: "x" + value { value { int64_value: 1 } } + } + input { + key: "y" + value { value { int64_value: 2 } } + } + output { + result_value { + bool_value: true + } + } + } +} diff --git a/testing/testrunner/BUILD.bazel b/testing/testrunner/BUILD.bazel index 00cb4b832..043d62c88 100644 --- a/testing/testrunner/BUILD.bazel +++ b/testing/testrunner/BUILD.bazel @@ -102,6 +102,11 @@ java_library( exports = ["//testing/src/main/java/dev/cel/testing/testrunner:cel_expression_source"], ) +java_library( + name = "cel_coverage_index", + exports = ["//testing/src/main/java/dev/cel/testing/testrunner:cel_coverage_index"], +) + bzl_library( name = "cel_java_test", srcs = ["cel_java_test.bzl"], diff --git a/testing/testrunner/cel_java_test.bzl b/testing/testrunner/cel_java_test.bzl index b2a19f996..b3457f6f0 100644 --- a/testing/testrunner/cel_java_test.bzl +++ b/testing/testrunner/cel_java_test.bzl @@ -30,6 +30,7 @@ def cel_java_test( config = "", deps = [], proto_deps = [], + enable_coverage = False, test_data_path = "", data = []): """trigger the java impl of the CEL test runner. @@ -56,6 +57,8 @@ def cel_java_test( deps: list of dependencies for the java_binary rule. data: list of data dependencies for the java_binary rule. proto_deps: str label of the proto dependencies for the test. Note: This only supports proto_library rules. + enable_coverage: bool whether to enable coverage for the test. This is needed only if the + test runner is being used for gathering coverage data. test_data_path: absolute path of the directory containing the test files. This is needed only if the test files are not located in the same directory as the BUILD file. This would be of the form "//foo/bar". @@ -101,6 +104,7 @@ def cel_java_test( deps = deps + [":" + name + "_proto_descriptor_set_java_proto"] jvm_flags.append("-Dis_raw_expr=%s" % is_raw_expr) + jvm_flags.append("-Dis_coverage_enabled=%s" % enable_coverage) java_binary( name = name + "_test_runner_binary", 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 b7050466c..67e791317 100644 --- a/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/main/java/dev/cel/validator/validators/BUILD.bazel @@ -84,6 +84,23 @@ java_library( ], ) +java_library( + name = "comprehension_nesting_limit_validator", + srcs = [ + "ComprehensionNestingLimitValidator.java", + ], + tags = [ + ], + deps = [ + "//bundle:cel", + "//common/ast", + "//common/navigation", + "//common/navigation:common", + "//validator:ast_validator", + "@maven//:com_google_guava_guava", + ], +) + java_library( name = "literal_validator", srcs = [ diff --git a/validator/src/main/java/dev/cel/validator/validators/ComprehensionNestingLimitValidator.java b/validator/src/main/java/dev/cel/validator/validators/ComprehensionNestingLimitValidator.java new file mode 100644 index 000000000..55dadacc5 --- /dev/null +++ b/validator/src/main/java/dev/cel/validator/validators/ComprehensionNestingLimitValidator.java @@ -0,0 +1,85 @@ +// 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.validator.validators; + +import static com.google.common.base.Preconditions.checkArgument; + +import dev.cel.bundle.Cel; +import dev.cel.common.ast.CelExpr.ExprKind; +import dev.cel.common.navigation.CelNavigableAst; +import dev.cel.common.navigation.CelNavigableExpr; +import dev.cel.common.navigation.TraversalOrder; +import dev.cel.validator.CelAstValidator; + +/** + * Checks that the nesting depth of comprehensions does not exceed the configured limit. Nesting + * occurs when a comprehension is used in the range expression or the body of a comprehension. + * + *

    Trivial comprehensions (comprehensions over an empty range) do not count towards the limit. + */ +public final class ComprehensionNestingLimitValidator implements CelAstValidator { + private final int nestingLimit; + + /** + * Constructs a new instance of {@link ComprehensionNestingLimitValidator} with the configured + * maxNesting as its limit. A limit of 0 means no comprehensions are allowed. + */ + public static ComprehensionNestingLimitValidator newInstance(int maxNesting) { + checkArgument(maxNesting >= 0); + return new ComprehensionNestingLimitValidator(maxNesting); + } + + @Override + public void validate(CelNavigableAst navigableAst, Cel cel, IssuesFactory issuesFactory) { + navigableAst + .getRoot() + .allNodes(TraversalOrder.PRE_ORDER) + .filter(node -> node.getKind().equals(ExprKind.Kind.COMPREHENSION)) + .filter(node -> nestingLevel(node) > nestingLimit) + .forEach( + node -> + issuesFactory.addError( + node.id(), + String.format( + "comprehension nesting exceeds the configured limit: %s.", nestingLimit))); + } + + private static boolean isTrivialComprehension(CelNavigableExpr node) { + return (node.expr().comprehension().iterRange().getKind().equals(ExprKind.Kind.LIST) + && node.expr().comprehension().iterRange().list().elements().isEmpty()) + || (node.expr().comprehension().iterRange().getKind().equals(ExprKind.Kind.MAP) + && node.expr().comprehension().iterRange().map().entries().isEmpty()); + } + + private static int nestingLevel(CelNavigableExpr node) { + if (isTrivialComprehension(node)) { + return 0; + } + int count = 1; + while (node.parent().isPresent()) { + CelNavigableExpr parent = node.parent().get(); + + if (parent.getKind().equals(ExprKind.Kind.COMPREHENSION) && !isTrivialComprehension(parent)) { + count++; + } + node = parent; + } + return count; + } + + private ComprehensionNestingLimitValidator(int maxNesting) { + this.nestingLimit = maxNesting; + } +} diff --git a/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel b/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel index 9e88e0d16..cb35e7a6b 100644 --- a/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel +++ b/validator/src/test/java/dev/cel/validator/validators/BUILD.bazel @@ -16,12 +16,15 @@ java_library( "//common:proto_ast", "//common/internal:proto_time_utils", "//common/types", + "//extensions", "//extensions:optional_library", + "//parser:macro", "//runtime", "//runtime:function_binding", "//validator", "//validator:validator_builder", "//validator/validators:ast_depth_limit_validator", + "//validator/validators:comprehension_nesting_limit_validator", "//validator/validators:duration", "//validator/validators:homogeneous_literal", "//validator/validators:regex", diff --git a/validator/src/test/java/dev/cel/validator/validators/ComprehensionNestingLimitValidatorTest.java b/validator/src/test/java/dev/cel/validator/validators/ComprehensionNestingLimitValidatorTest.java new file mode 100644 index 000000000..ccf625d88 --- /dev/null +++ b/validator/src/test/java/dev/cel/validator/validators/ComprehensionNestingLimitValidatorTest.java @@ -0,0 +1,176 @@ +// 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.validator.validators; + +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.CelIssue.Severity; +import dev.cel.common.CelValidationException; +import dev.cel.common.CelValidationResult; +import dev.cel.common.types.SimpleType; +import dev.cel.extensions.CelExtensions; +import dev.cel.parser.CelStandardMacro; +import dev.cel.validator.CelValidator; +import dev.cel.validator.CelValidatorFactory; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(TestParameterInjector.class) +public class ComprehensionNestingLimitValidatorTest { + + private static final Cel CEL = + CelFactory.standardCelBuilder() + .addCompilerLibraries( + CelExtensions.optional(), CelExtensions.comprehensions(), CelExtensions.bindings()) + .addRuntimeLibraries(CelExtensions.optional(), CelExtensions.comprehensions()) + .setStandardMacros(CelStandardMacro.STANDARD_MACROS) + .addVar("x", SimpleType.DYN) + .build(); + + private static final CelValidator CEL_VALIDATOR = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(1)) + .build(); + + @Test + public void comprehensionNestingLimit_populatesErrors( + @TestParameter({ + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y))", + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y).size() > 0, x)", + "[1, 2, 3].all(x, [1, 2, 3].exists(y, x + y > 0))", + "[1, 2, 3].map(x, {x: [1, 2, 3].map(y, x + y)})", + "[1, 2, 3].exists(i, v, i < 3 && [1, 2, 3].all(j, v2, j < 3 && v2 > 0))", + "{1: 2}.all(k, {2: 3}.all(k2, k != k2))" + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidationResult result = CEL_VALIDATOR.validate(ast); + + assertThat(result.hasError()).isTrue(); + assertThat(result.getAllIssues()).hasSize(1); + assertThat(result.getAllIssues().get(0).getSeverity()).isEqualTo(Severity.ERROR); + assertThat(result.getAllIssues().get(0).toDisplayString(ast.getSource())) + .contains("comprehension nesting exceeds the configured limit: 1."); + } + + @Test + public void comprehensionNestingLimit_accumulatesErrors( + @TestParameter({ + "[1, 2, 3].map(x, [1, 2, 3].map(y, [1, 2, 3].map(z, x + y + z)))", + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidationResult result = CEL_VALIDATOR.validate(ast); + + assertThat(result.hasError()).isTrue(); + assertThat(result.getAllIssues()).hasSize(2); + } + + @Test + public void comprehensionNestingLimit_limitConfigurable( + @TestParameter({ + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y))", + "[1, 2, 3].map(x, [1, 2, 3].map(y, x + y).size() > 0, x)", + "[1, 2, 3].all(x, [1, 2, 3].exists(y, x + y > 0))", + "[1, 2, 3].map(x, {x: [1, 2, 3].map(y, x + y)})", + "[1, 2, 3].exists(i, v, i < 3 && [1, 2, 3].all(j, v2, j < 3 && v2 > 0))", + "{1: 2}.all(k, {2: 3}.all(k2, k != k2))" + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + CelValidator celValidator = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(2)) + .build(); + + CelValidationResult result = celValidator.validate(ast); + + assertThat(result.hasError()).isFalse(); + } + + @Test + public void comprehensionNestingLimit_trivialLoopsDontCount( + @TestParameter({ + "cel.bind(x, [1, 2].map(x, x + 1), x + [1, 2].map(x, x + 1))", + "optional.of(1).optMap(x, [1, 2, 3].exists(y, y == x))", + "[].map(x, [1, 2, 3].map(y, x + y))", + "{}.map(k1, {1: 2, 3: 4}.map(k2, k1 + k2))", + "[1, 2, 3].map(x, cel.bind(y, 2, x + y))", + "[1, 2, 3].map(x, optional.of(1).optMap(y, x + y).orValue(0))", + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidationResult result = CEL_VALIDATOR.validate(ast); + + assertThat(result.hasError()).isFalse(); + } + + @Test + public void comprehensionNestingLimit_zeroLimitAcceptedComprehenions( + @TestParameter({ + "cel.bind(x, 1, x + 1)", + "optional.of(1).optMap(x, x + 1)", + "[].map(x, int(x))", + "cel.bind(x, 1 + [].map(x, int(x)).size(), x + 1)" + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidator celValidator = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(0)) + .build(); + + CelValidationResult result = celValidator.validate(ast); + + assertThat(result.hasError()).isFalse(); + } + + @Test + public void comprehensionNestingLimit_zeroLimitRejectedComprehensions( + @TestParameter({ + "[1].map(x, x)", + "[1].exists(x, x > 0)", + "[].exists(x, [1].all(y, y > 0))", + }) + String expr) + throws Exception { + CelAbstractSyntaxTree ast = CEL.compile(expr).getAst(); + + CelValidator celValidator = + CelValidatorFactory.standardCelValidatorBuilder(CEL) + .addAstValidators(ComprehensionNestingLimitValidator.newInstance(0)) + .build(); + + CelValidationException e = + assertThrows(CelValidationException.class, () -> celValidator.validate(ast).getAst()); + + assertThat(e.getMessage()).contains("comprehension nesting exceeds the configured limit: 0."); + } +} diff --git a/validator/validators/BUILD.bazel b/validator/validators/BUILD.bazel index b5498d682..ab0919f20 100644 --- a/validator/validators/BUILD.bazel +++ b/validator/validators/BUILD.bazel @@ -29,3 +29,8 @@ java_library( name = "ast_depth_limit_validator", exports = ["//validator/src/main/java/dev/cel/validator/validators:ast_depth_limit_validator"], ) + +java_library( + name = "comprehension_nesting_limit_validator", + exports = ["//validator/src/main/java/dev/cel/validator/validators:comprehension_nesting_limit_validator"], +)