diff --git a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel
index 48915fd02..a9dbfaca2 100644
--- a/extensions/src/test/java/dev/cel/extensions/BUILD.bazel
+++ b/extensions/src/test/java/dev/cel/extensions/BUILD.bazel
@@ -38,6 +38,8 @@ java_library(
"//runtime:interpreter_util",
"//runtime:lite_runtime",
"//runtime:lite_runtime_factory",
+ "//runtime:partial_vars",
+ "//runtime:unknown_attributes",
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
"@cel_spec//proto/cel/expr/conformance/test:simple_java_proto",
diff --git a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java
index 24e9d6d86..ab412fb39 100644
--- a/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java
+++ b/extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java
@@ -49,10 +49,12 @@
import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage;
import dev.cel.parser.CelMacro;
import dev.cel.parser.CelStandardMacro;
+import dev.cel.runtime.CelAttributePattern;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelRuntime;
import dev.cel.runtime.InterpreterUtil;
+import dev.cel.runtime.PartialVars;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
@@ -897,14 +899,12 @@ public void optionalIndex_onMap_returnsOptionalValue() throws Exception {
@TestParameters("{source: '{?x: x}'}")
public void optionalIndex_onMapWithUnknownInput_returnsUnknownResult(String source)
throws Exception {
- if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) {
- // TODO: Uncomment once unknowns is implemented
- return;
- }
Cel cel = newCelBuilder().addVar("x", OptionalType.create(SimpleType.INT)).build();
CelAbstractSyntaxTree ast = compile(cel, source);
- Object result = cel.createProgram(ast).eval();
+ Object result =
+ cel.createProgram(ast)
+ .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("x")));
assertThat(InterpreterUtil.isUnknown(result)).isTrue();
}
@@ -987,10 +987,6 @@ public void optionalIndex_onOptionalList_returnsOptionalValue() throws Exception
@Test
public void optionalIndex_onListWithUnknownInput_returnsUnknownResult() throws Exception {
- if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) {
- // TODO: Uncomment once unknowns is implemented
- return;
- }
Cel cel =
newCelBuilder()
.addVar("x", OptionalType.create(SimpleType.INT))
@@ -998,7 +994,9 @@ public void optionalIndex_onListWithUnknownInput_returnsUnknownResult() throws E
.build();
CelAbstractSyntaxTree ast = compile(cel, "[?x]");
- Object result = cel.createProgram(ast).eval();
+ Object result =
+ cel.createProgram(ast)
+ .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("x")));
assertThat(InterpreterUtil.isUnknown(result)).isTrue();
}
@@ -1017,6 +1015,29 @@ public void traditionalIndex_onOptionalList_returnsOptionalEmpty() throws Except
assertThat(result).isEqualTo(Optional.empty());
}
+ @Test
+ public void optionalFieldSelect_fieldMarkedUnknown_returnsUnknownSet() throws Exception {
+ if (testMode.equals(TestMode.LEGACY_CHECKED)) {
+ // This case is not possible to setup for legacy runtime
+ return;
+ }
+
+ Cel cel =
+ newCelBuilder()
+ .addVar("msg", StructTypeReference.create(TestAllTypes.getDescriptor().getFullName()))
+ .build();
+ CelAbstractSyntaxTree ast = compile(cel, "msg.?single_int32");
+
+ Object result =
+ cel.createProgram(ast)
+ .eval(
+ PartialVars.of(
+ ImmutableMap.of("msg", TestAllTypes.newBuilder().setSingleInt32(42).build()),
+ CelAttributePattern.fromQualifiedIdentifier("msg.single_int32")));
+
+ assertThat(InterpreterUtil.isUnknown(result)).isTrue();
+ }
+
@Test
// LHS
@TestParameters("{expression: 'optx.or(optional.of(1))'}")
@@ -1026,10 +1047,6 @@ public void traditionalIndex_onOptionalList_returnsOptionalEmpty() throws Except
@TestParameters("{expression: 'optional.none().orValue(optx)'}")
public void optionalChainedFunctions_lhsIsUnknown_returnsUnknown(String expression)
throws Exception {
- if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) {
- // TODO: Uncomment once unknowns is implemented
- return;
- }
Cel cel =
newCelBuilder()
.addVar("optx", OptionalType.create(SimpleType.INT))
@@ -1037,7 +1054,9 @@ public void optionalChainedFunctions_lhsIsUnknown_returnsUnknown(String expressi
.build();
CelAbstractSyntaxTree ast = compile(cel, expression);
- Object result = cel.createProgram(ast).eval();
+ Object result =
+ cel.createProgram(ast)
+ .eval(PartialVars.of(CelAttributePattern.fromQualifiedIdentifier("optx")));
assertThat(InterpreterUtil.isUnknown(result)).isTrue();
}
diff --git a/runtime/BUILD.bazel b/runtime/BUILD.bazel
index 55ee241a0..3e183d236 100644
--- a/runtime/BUILD.bazel
+++ b/runtime/BUILD.bazel
@@ -351,3 +351,17 @@ java_library(
"//runtime/src/main/java/dev/cel/runtime:runtime_planner_impl",
],
)
+
+java_library(
+ name = "accumulated_unknowns",
+ visibility = ["//:internal"],
+ exports = [
+ "//runtime/src/main/java/dev/cel/runtime:accumulated_unknowns",
+ ],
+)
+
+java_library(
+ name = "partial_vars",
+ visibility = ["//:internal"],
+ exports = ["//runtime/src/main/java/dev/cel/runtime:partial_vars"],
+)
diff --git a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java
index d27de2da2..d4d54c71f 100644
--- a/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java
+++ b/runtime/src/main/java/dev/cel/runtime/AccumulatedUnknowns.java
@@ -15,18 +15,23 @@
package dev.cel.runtime;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import dev.cel.common.annotations.Internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
+import org.jspecify.annotations.Nullable;
/**
* An internal representation used for fast accumulation of unknown expr IDs and attributes. For
* safety, this object should never be returned as an evaluated result and instead be adapted into
* an immutable CelUnknownSet.
+ *
+ *
CEL Library Internals. Do Not Use.
*/
-final class AccumulatedUnknowns {
+@Internal
+public final class AccumulatedUnknowns {
private static final int MAX_UNKNOWN_ATTRIBUTE_SIZE = 500_000;
private final Set exprIds;
private final Set attributes;
@@ -39,8 +44,21 @@ Set attributes() {
return attributes;
}
+ /**
+ * Evaluates if the right hand side is an accumulated unknown, and if so, merges it into the
+ * accumulator.
+ */
+ public static @Nullable AccumulatedUnknowns maybeMerge(
+ @Nullable AccumulatedUnknowns accumulator, Object newValue) {
+ if (newValue instanceof AccumulatedUnknowns) {
+ AccumulatedUnknowns newUnknowns = (AccumulatedUnknowns) newValue;
+ return accumulator == null ? newUnknowns : accumulator.merge(newUnknowns);
+ }
+ return accumulator;
+ }
+
@CanIgnoreReturnValue
- AccumulatedUnknowns merge(AccumulatedUnknowns arg) {
+ public AccumulatedUnknowns merge(AccumulatedUnknowns arg) {
enforceMaxAttributeSize(this.attributes, arg.attributes);
this.exprIds.addAll(arg.exprIds);
this.attributes.addAll(arg.attributes);
@@ -55,7 +73,8 @@ static AccumulatedUnknowns create(Collection ids) {
return create(ids, new ArrayList<>());
}
- static AccumulatedUnknowns create(Collection exprIds, Collection attributes) {
+ public static AccumulatedUnknowns create(
+ Collection exprIds, Collection attributes) {
return new AccumulatedUnknowns(new HashSet<>(exprIds), new HashSet<>(attributes));
}
diff --git a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
index 10dca9ece..2681c17de 100644
--- a/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/BUILD.bazel
@@ -826,6 +826,7 @@ java_library(
":evaluation_listener",
":function_binding",
":function_resolver",
+ ":partial_vars",
":program",
":proto_message_runtime_equality",
":runtime",
@@ -938,6 +939,7 @@ java_library(
":function_resolver",
":interpretable",
":interpreter",
+ ":partial_vars",
":program",
":proto_message_activation_factory",
":runtime_equality",
@@ -955,7 +957,6 @@ java_library(
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_protobuf_protobuf_java",
- "@maven//:org_jspecify_jspecify",
],
)
@@ -1014,6 +1015,7 @@ java_library(
":evaluation_exception",
":function_resolver",
":interpretable",
+ ":partial_vars",
":program",
":variable_resolver",
"//:auto_value",
@@ -1029,6 +1031,7 @@ cel_android_library(
":evaluation_exception",
":function_resolver_android",
":interpretable_android",
+ ":partial_vars_android",
":program_android",
":variable_resolver",
"//:auto_value",
@@ -1199,6 +1202,7 @@ java_library(
":unknown_attributes",
"//common/annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
],
)
@@ -1214,6 +1218,7 @@ cel_android_library(
"//common/annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:org_jspecify_jspecify",
+ "@maven_android//:com_google_guava_guava",
],
)
@@ -1273,10 +1278,13 @@ java_library(
java_library(
name = "accumulated_unknowns",
srcs = ["AccumulatedUnknowns.java"],
- visibility = ["//visibility:private"],
+ tags = [
+ ],
deps = [
":unknown_attributes",
+ "//common/annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:org_jspecify_jspecify",
],
)
@@ -1286,7 +1294,9 @@ cel_android_library(
visibility = ["//visibility:private"],
deps = [
":unknown_attributes_android",
+ "//common/annotations",
"@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:org_jspecify_jspecify",
],
)
@@ -1318,6 +1328,34 @@ cel_android_library(
],
)
+java_library(
+ name = "partial_vars",
+ srcs = ["PartialVars.java"],
+ tags = [
+ ],
+ deps = [
+ ":variable_resolver",
+ "//:auto_value",
+ "//runtime:unknown_attributes",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven//:com_google_guava_guava",
+ ],
+)
+
+cel_android_library(
+ name = "partial_vars_android",
+ srcs = ["PartialVars.java"],
+ tags = [
+ ],
+ deps = [
+ ":variable_resolver",
+ "//:auto_value",
+ "//runtime:unknown_attributes_android",
+ "@maven//:com_google_errorprone_error_prone_annotations",
+ "@maven_android//:com_google_guava_guava",
+ ],
+)
+
java_library(
name = "program",
srcs = ["Program.java"],
@@ -1326,6 +1364,7 @@ java_library(
deps = [
":evaluation_exception",
":function_resolver",
+ ":partial_vars",
":variable_resolver",
"@maven//:com_google_errorprone_error_prone_annotations",
],
@@ -1339,8 +1378,8 @@ cel_android_library(
deps = [
":evaluation_exception",
":function_resolver_android",
+ ":partial_vars_android",
":variable_resolver",
- "//:auto_value",
"@maven//:com_google_errorprone_error_prone_annotations",
],
)
diff --git a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java
index 346b25ae9..cab2c666e 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java
@@ -134,6 +134,11 @@ public Object eval(
return program.eval(resolver, lateBoundFunctionResolver);
}
+ @Override
+ public Object eval(PartialVars partialVars) throws CelEvaluationException {
+ return program.eval(partialVars);
+ }
+
@Override
public Object trace(CelEvaluationListener listener) throws CelEvaluationException {
throw new UnsupportedOperationException("Trace is not yet supported.");
diff --git a/runtime/src/main/java/dev/cel/runtime/CelUnknownSet.java b/runtime/src/main/java/dev/cel/runtime/CelUnknownSet.java
index c7f1d0c91..62d975f93 100644
--- a/runtime/src/main/java/dev/cel/runtime/CelUnknownSet.java
+++ b/runtime/src/main/java/dev/cel/runtime/CelUnknownSet.java
@@ -59,7 +59,7 @@ static CelUnknownSet create(Iterable unknownExprIds) {
return create(ImmutableSet.of(), ImmutableSet.copyOf(unknownExprIds));
}
- static CelUnknownSet create(
+ public static CelUnknownSet create(
ImmutableSet attributes, ImmutableSet unknownExprIds) {
return new AutoValue_CelUnknownSet(attributes, unknownExprIds);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java b/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java
index f84897ac2..73607cefd 100644
--- a/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java
+++ b/runtime/src/main/java/dev/cel/runtime/InterpreterUtil.java
@@ -14,6 +14,7 @@
package dev.cel.runtime;
+import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CheckReturnValue;
import dev.cel.common.annotations.Internal;
import org.jspecify.annotations.Nullable;
@@ -55,12 +56,12 @@ public static boolean isUnknown(Object obj) {
return obj instanceof CelUnknownSet;
}
- static boolean isAccumulatedUnknowns(Object obj) {
+ public static boolean isAccumulatedUnknowns(Object obj) {
return obj instanceof AccumulatedUnknowns;
}
/** If the argument is {@link CelUnknownSet}, adapts it into {@link AccumulatedUnknowns} */
- static Object maybeAdaptToAccumulatedUnknowns(Object val) {
+ public static Object maybeAdaptToAccumulatedUnknowns(Object val) {
if (!(val instanceof CelUnknownSet)) {
return val;
}
@@ -68,10 +69,20 @@ static Object maybeAdaptToAccumulatedUnknowns(Object val) {
return adaptToAccumulatedUnknowns((CelUnknownSet) val);
}
- static AccumulatedUnknowns adaptToAccumulatedUnknowns(CelUnknownSet unknowns) {
+ public static AccumulatedUnknowns adaptToAccumulatedUnknowns(CelUnknownSet unknowns) {
return AccumulatedUnknowns.create(unknowns.unknownExprIds(), unknowns.attributes());
}
+ public static Object maybeAdaptToCelUnknownSet(Object val) {
+ if (!(val instanceof AccumulatedUnknowns)) {
+ return val;
+ }
+
+ AccumulatedUnknowns unknowns = (AccumulatedUnknowns) val;
+ return CelUnknownSet.create(
+ ImmutableSet.copyOf(unknowns.attributes()), ImmutableSet.copyOf(unknowns.exprIds()));
+ }
+
/**
* Enforces strictness on both lhs/rhs arguments from logical operators (i.e: intentionally throws
* an appropriate exception when {@link Throwable} is encountered as part of evaluated result.
diff --git a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java
index 5e57f497b..af8c1a6d0 100644
--- a/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java
+++ b/runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java
@@ -52,6 +52,12 @@ public Object eval(CelVariableResolver resolver) throws CelEvaluationException {
throw new UnsupportedOperationException("To be implemented");
}
+ @Override
+ public Object eval(PartialVars partialVars) throws CelEvaluationException {
+ // TODO: Wire in program planner
+ throw new UnsupportedOperationException("To be implemented");
+ }
+
static Program plan(Interpretable interpretable) {
return new AutoValue_LiteProgramImpl(interpretable);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/PartialVars.java b/runtime/src/main/java/dev/cel/runtime/PartialVars.java
new file mode 100644
index 000000000..1cd081040
--- /dev/null
+++ b/runtime/src/main/java/dev/cel/runtime/PartialVars.java
@@ -0,0 +1,70 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.runtime;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * A holder for a {@link CelVariableResolver} and a set of {@link CelAttributePattern}s that
+ * indicate variables or parts of variables whose value are not yet known.
+ */
+@AutoValue
+public abstract class PartialVars {
+
+ /** The resolver to use for resolving evaluation variables. */
+ public abstract CelVariableResolver resolver();
+
+ /**
+ * A list of attribute patterns specifying which missing attribute paths should be tracked as
+ * unknown values.
+ */
+ public abstract ImmutableList unknowns();
+
+ /** Constructs a new {@code PartialVars} from one or more {@link CelAttributePattern}s. */
+ public static PartialVars of(CelAttributePattern... unknownAttributes) {
+ return of((unused) -> Optional.empty(), ImmutableList.copyOf(unknownAttributes));
+ }
+
+ /**
+ * Constructs a new {@code PartialVars} from a {@link CelVariableResolver} and a list of {@link
+ * CelAttributePattern}s.
+ */
+ public static PartialVars of(
+ CelVariableResolver resolver, Iterable unknownAttributes) {
+ return new AutoValue_PartialVars(resolver, ImmutableList.copyOf(unknownAttributes));
+ }
+
+ /**
+ * Constructs a new {@code PartialVars} from a map of variables and an array of {@link
+ * CelAttributePattern}s.
+ */
+ public static PartialVars of(Map variables, CelAttributePattern... unknownAttributes) {
+ return of(
+ (name) -> variables.containsKey(name) ? Optional.of(variables.get(name)) : Optional.empty(),
+ unknownAttributes);
+ }
+
+ /**
+ * Constructs a new {@code PartialVars} from a {@link CelVariableResolver} and an array of {@link
+ * CelAttributePattern}s.
+ */
+ public static PartialVars of(
+ CelVariableResolver resolver, CelAttributePattern... unknownAttributes) {
+ return of(resolver, ImmutableList.copyOf(unknownAttributes));
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/Program.java b/runtime/src/main/java/dev/cel/runtime/Program.java
index c0982f1f8..e808a373c 100644
--- a/runtime/src/main/java/dev/cel/runtime/Program.java
+++ b/runtime/src/main/java/dev/cel/runtime/Program.java
@@ -43,4 +43,7 @@ Object eval(Map mapValue, CelFunctionResolver lateBoundFunctionResolv
*/
Object eval(CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver)
throws CelEvaluationException;
+
+ /** Evaluate a compiled program with unknown attribute patterns {@code partialVars}. */
+ Object eval(PartialVars partialVars) throws CelEvaluationException;
}
diff --git a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java
index d0e64429b..c9f4d083b 100644
--- a/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java
+++ b/runtime/src/main/java/dev/cel/runtime/ProgramImpl.java
@@ -60,6 +60,14 @@ public Object eval(Map mapValue, CelFunctionResolver lateBoundFunctio
return evalInternal(Activation.copyOf(mapValue), lateBoundFunctionResolver);
}
+ @Override
+ public Object eval(PartialVars partialVars) throws CelEvaluationException {
+ return evalInternal(
+ UnknownContext.create(partialVars.resolver(), partialVars.unknowns()),
+ /* lateBoundFunctionResolver= */ Optional.empty(),
+ /* listener= */ Optional.empty());
+ }
+
@Override
public Object trace(CelEvaluationListener listener) throws CelEvaluationException {
return evalInternal(Activation.EMPTY, listener);
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
index cc011ed34..90165c1ac 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/Attribute.java
@@ -20,7 +20,7 @@
/** Represents a resolvable symbol or path (such as a variable or a field selection). */
@Immutable
interface Attribute {
- Object resolve(GlobalResolver ctx, ExecutionFrame frame);
+ Object resolve(long exprId, GlobalResolver ctx, ExecutionFrame frame);
Attribute addQualifier(Qualifier qualifier);
}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
index 6561e4e5c..3c18b192f 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
+++ b/runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
@@ -78,6 +78,8 @@ java_library(
"//runtime:evaluation_exception_builder",
"//runtime:function_resolver",
"//runtime:interpretable",
+ "//runtime:interpreter_util",
+ "//runtime:partial_vars",
"//runtime:program",
"//runtime:resolved_overload",
"//runtime:variable_resolver",
@@ -128,7 +130,11 @@ java_library(
"//common/types",
"//common/types:type_providers",
"//common/values",
+ "//runtime:accumulated_unknowns",
"//runtime:interpretable",
+ "//runtime:interpreter_util",
+ "//runtime:partial_vars",
+ "//runtime:unknown_attributes",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
@@ -181,7 +187,6 @@ java_library(
":qualifier",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
- "@maven//:com_google_guava_guava",
],
)
@@ -235,6 +240,7 @@ java_library(
":execution_frame",
":planned_interpretable",
"//common/values",
+ "//runtime:accumulated_unknowns",
"//runtime:evaluation_exception",
"//runtime:interpretable",
"//runtime:resolved_overload",
@@ -250,6 +256,7 @@ java_library(
":planned_interpretable",
"//common/exceptions:overload_not_found",
"//common/values",
+ "//runtime:accumulated_unknowns",
"//runtime:evaluation_exception",
"//runtime:interpretable",
"//runtime:resolved_overload",
@@ -265,6 +272,7 @@ java_library(
":execution_frame",
":planned_interpretable",
"//common/values",
+ "//runtime:accumulated_unknowns",
"//runtime:interpretable",
"@maven//:com_google_guava_guava",
],
@@ -278,6 +286,7 @@ java_library(
":execution_frame",
":planned_interpretable",
"//common/values",
+ "//runtime:accumulated_unknowns",
"//runtime:interpretable",
"@maven//:com_google_guava_guava",
],
@@ -289,6 +298,7 @@ java_library(
deps = [
":execution_frame",
":planned_interpretable",
+ "//runtime:accumulated_unknowns",
"//runtime:evaluation_exception",
"//runtime:interpretable",
"@maven//:com_google_guava_guava",
@@ -299,11 +309,13 @@ java_library(
name = "eval_create_struct",
srcs = ["EvalCreateStruct.java"],
deps = [
+ ":eval_helpers",
":execution_frame",
":planned_interpretable",
"//common/types:type_providers",
"//common/values",
"//common/values:cel_value_provider",
+ "//runtime:accumulated_unknowns",
"//runtime:evaluation_exception",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -318,6 +330,7 @@ java_library(
":eval_helpers",
":execution_frame",
":planned_interpretable",
+ "//runtime:accumulated_unknowns",
"//runtime:evaluation_exception",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -329,11 +342,13 @@ java_library(
name = "eval_create_map",
srcs = ["EvalCreateMap.java"],
deps = [
+ ":eval_helpers",
":execution_frame",
":localized_evaluation_exception",
":planned_interpretable",
"//common/exceptions:duplicate_key",
"//common/exceptions:invalid_argument",
+ "//runtime:accumulated_unknowns",
"//runtime:evaluation_exception",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -348,6 +363,7 @@ java_library(
":activation_wrapper",
":execution_frame",
":planned_interpretable",
+ "//runtime:accumulated_unknowns",
"//runtime:concatenated_list_view",
"//runtime:evaluation_exception",
"//runtime:interpretable",
@@ -365,6 +381,7 @@ java_library(
"//common/exceptions:iteration_budget_exceeded",
"//runtime:evaluation_exception",
"//runtime:function_resolver",
+ "//runtime:partial_vars",
"//runtime:resolved_overload",
],
)
@@ -424,6 +441,7 @@ java_library(
":execution_frame",
":planned_interpretable",
"//common/exceptions:overload_not_found",
+ "//runtime:accumulated_unknowns",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -438,6 +456,7 @@ java_library(
":execution_frame",
":planned_interpretable",
"//common/exceptions:overload_not_found",
+ "//runtime:accumulated_unknowns",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
@@ -452,6 +471,7 @@ java_library(
":execution_frame",
":planned_interpretable",
"//common/values",
+ "//runtime:accumulated_unknowns",
"//runtime:interpretable",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
index 763f8faba..eb7406071 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAnd.java
@@ -1,66 +1,73 @@
-// Copyright 2025 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package dev.cel.runtime.planner;
-
-import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
-
-import com.google.common.base.Preconditions;
-import dev.cel.common.values.ErrorValue;
-import dev.cel.runtime.GlobalResolver;
-
-final class EvalAnd extends PlannedInterpretable {
-
- @SuppressWarnings("Immutable")
- private final PlannedInterpretable[] args;
-
- @Override
- public Object eval(GlobalResolver resolver, ExecutionFrame frame) {
- ErrorValue errorValue = null;
- for (PlannedInterpretable arg : args) {
- Object argVal = evalNonstrictly(arg, resolver, frame);
- if (argVal instanceof Boolean) {
- // Short-circuit on false
- if (!((boolean) argVal)) {
- return false;
- }
- } else if (argVal instanceof ErrorValue) {
- errorValue = (ErrorValue) argVal;
- } else {
- // TODO: Handle unknowns
- errorValue =
- ErrorValue.create(
- arg.exprId(),
- new IllegalArgumentException(
- String.format("Expected boolean value, found: %s", argVal)));
- }
- }
-
- if (errorValue != null) {
- return errorValue;
- }
-
- return true;
- }
-
- static EvalAnd create(long exprId, PlannedInterpretable[] args) {
- return new EvalAnd(exprId, args);
- }
-
- private EvalAnd(long exprId, PlannedInterpretable[] args) {
- super(exprId);
- Preconditions.checkArgument(args.length == 2);
- this.args = args;
- }
-}
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package dev.cel.runtime.planner;
+
+import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
+
+import com.google.common.base.Preconditions;
+import dev.cel.common.values.ErrorValue;
+import dev.cel.runtime.AccumulatedUnknowns;
+import dev.cel.runtime.GlobalResolver;
+
+final class EvalAnd extends PlannedInterpretable {
+
+ @SuppressWarnings("Immutable")
+ private final PlannedInterpretable[] args;
+
+ @Override
+ public Object eval(GlobalResolver resolver, ExecutionFrame frame) {
+ ErrorValue errorValue = null;
+ AccumulatedUnknowns unknowns = null;
+ for (PlannedInterpretable arg : args) {
+ Object argVal = evalNonstrictly(arg, resolver, frame);
+ if (argVal instanceof Boolean) {
+ // Short-circuit on false
+ if (!((boolean) argVal)) {
+ return false;
+ }
+ } else if (argVal instanceof ErrorValue) {
+ errorValue = (ErrorValue) argVal;
+ } else if (argVal instanceof AccumulatedUnknowns) {
+ unknowns = AccumulatedUnknowns.maybeMerge(unknowns, argVal);
+ } else {
+ errorValue =
+ ErrorValue.create(
+ arg.exprId(),
+ new IllegalArgumentException(
+ String.format("Expected boolean value, found: %s", argVal)));
+ }
+ }
+
+ if (unknowns != null) {
+ return unknowns;
+ }
+
+ if (errorValue != null) {
+ return errorValue;
+ }
+
+ return true;
+ }
+
+ static EvalAnd create(long exprId, PlannedInterpretable[] args) {
+ return new EvalAnd(exprId, args);
+ }
+
+ private EvalAnd(long exprId, PlannedInterpretable[] args) {
+ super(exprId);
+ Preconditions.checkArgument(args.length == 2);
+ this.args = args;
+ }
+}
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
index fdd7ad2a3..a0a95c47a 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalAttribute.java
@@ -24,9 +24,9 @@ final class EvalAttribute extends InterpretableAttribute {
@Override
public Object eval(GlobalResolver resolver, ExecutionFrame frame) {
- Object resolved = attr.resolve(resolver, frame);
+ Object resolved = attr.resolve(exprId(), resolver, frame);
if (resolved instanceof MissingAttribute) {
- ((MissingAttribute) resolved).resolve(resolver, frame);
+ ((MissingAttribute) resolved).resolve(exprId(), resolver, frame);
}
return resolved;
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
index 74482d629..3be1f016a 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalConditional.java
@@ -15,6 +15,7 @@
package dev.cel.runtime.planner;
import com.google.common.base.Preconditions;
+import dev.cel.runtime.AccumulatedUnknowns;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.GlobalResolver;
@@ -28,8 +29,10 @@ public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEval
PlannedInterpretable condition = args[0];
PlannedInterpretable truthy = args[1];
PlannedInterpretable falsy = args[2];
- // TODO: Handle unknowns
Object condResult = condition.eval(resolver, frame);
+ if (condResult instanceof AccumulatedUnknowns) {
+ return condResult;
+ }
if (!(condResult instanceof Boolean)) {
throw new IllegalArgumentException(
String.format("Expected boolean value, found :%s", condResult));
diff --git a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
index 773272ea3..bae1e9302 100644
--- a/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
+++ b/runtime/src/main/java/dev/cel/runtime/planner/EvalCreateList.java
@@ -16,6 +16,7 @@
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.Immutable;
+import dev.cel.runtime.AccumulatedUnknowns;
import dev.cel.runtime.CelEvaluationException;
import dev.cel.runtime.GlobalResolver;
import java.util.Optional;
@@ -32,9 +33,15 @@ final class EvalCreateList extends PlannedInterpretable {
@Override
public Object eval(GlobalResolver resolver, ExecutionFrame frame) throws CelEvaluationException {
ImmutableList.Builder