Skip to content

Commit 53fb81b

Browse files
l46kokcopybara-github
authored andcommitted
Support partial evaluation via unknowns in planner
PiperOrigin-RevId: 877616092
1 parent a881ed4 commit 53fb81b

34 files changed

Lines changed: 1314 additions & 72 deletions

extensions/src/test/java/dev/cel/extensions/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ java_library(
3838
"//runtime:interpreter_util",
3939
"//runtime:lite_runtime",
4040
"//runtime:lite_runtime_factory",
41+
"//runtime:partial_vars",
42+
"//runtime:unknown_attributes",
4143
"@cel_spec//proto/cel/expr/conformance/proto2:test_all_types_java_proto",
4244
"@cel_spec//proto/cel/expr/conformance/proto3:test_all_types_java_proto",
4345
"@cel_spec//proto/cel/expr/conformance/test:simple_java_proto",

extensions/src/test/java/dev/cel/extensions/CelOptionalLibraryTest.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,12 @@
4949
import dev.cel.expr.conformance.proto3.TestAllTypes.NestedMessage;
5050
import dev.cel.parser.CelMacro;
5151
import dev.cel.parser.CelStandardMacro;
52+
import dev.cel.runtime.CelAttributePattern;
5253
import dev.cel.runtime.CelEvaluationException;
5354
import dev.cel.runtime.CelFunctionBinding;
5455
import dev.cel.runtime.CelRuntime;
5556
import dev.cel.runtime.InterpreterUtil;
57+
import dev.cel.runtime.PartialVars;
5658
import java.time.Duration;
5759
import java.time.Instant;
5860
import java.util.List;
@@ -897,14 +899,14 @@ public void optionalIndex_onMap_returnsOptionalValue() throws Exception {
897899
@TestParameters("{source: '{?x: x}'}")
898900
public void optionalIndex_onMapWithUnknownInput_returnsUnknownResult(String source)
899901
throws Exception {
900-
if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) {
901-
// TODO: Uncomment once unknowns is implemented
902-
return;
903-
}
904902
Cel cel = newCelBuilder().addVar("x", OptionalType.create(SimpleType.INT)).build();
905903
CelAbstractSyntaxTree ast = compile(cel, source);
906904

907-
Object result = cel.createProgram(ast).eval();
905+
Object result =
906+
cel.createProgram(ast)
907+
.eval(
908+
PartialVars.of(
909+
ImmutableMap.of(), CelAttributePattern.fromQualifiedIdentifier("x")));
908910

909911
assertThat(InterpreterUtil.isUnknown(result)).isTrue();
910912
}
@@ -987,18 +989,18 @@ public void optionalIndex_onOptionalList_returnsOptionalValue() throws Exception
987989

988990
@Test
989991
public void optionalIndex_onListWithUnknownInput_returnsUnknownResult() throws Exception {
990-
if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) {
991-
// TODO: Uncomment once unknowns is implemented
992-
return;
993-
}
994992
Cel cel =
995993
newCelBuilder()
996994
.addVar("x", OptionalType.create(SimpleType.INT))
997995
.setResultType(ListType.create(SimpleType.INT))
998996
.build();
999997
CelAbstractSyntaxTree ast = compile(cel, "[?x]");
1000998

1001-
Object result = cel.createProgram(ast).eval();
999+
Object result =
1000+
cel.createProgram(ast)
1001+
.eval(
1002+
PartialVars.of(
1003+
ImmutableMap.of(), CelAttributePattern.fromQualifiedIdentifier("x")));
10021004

10031005
assertThat(InterpreterUtil.isUnknown(result)).isTrue();
10041006
}
@@ -1026,18 +1028,18 @@ public void traditionalIndex_onOptionalList_returnsOptionalEmpty() throws Except
10261028
@TestParameters("{expression: 'optional.none().orValue(optx)'}")
10271029
public void optionalChainedFunctions_lhsIsUnknown_returnsUnknown(String expression)
10281030
throws Exception {
1029-
if (testMode.equals(TestMode.PLANNER_CHECKED) || testMode.equals(TestMode.PLANNER_PARSE_ONLY)) {
1030-
// TODO: Uncomment once unknowns is implemented
1031-
return;
1032-
}
10331031
Cel cel =
10341032
newCelBuilder()
10351033
.addVar("optx", OptionalType.create(SimpleType.INT))
10361034
.addVar("x", SimpleType.INT)
10371035
.build();
10381036
CelAbstractSyntaxTree ast = compile(cel, expression);
10391037

1040-
Object result = cel.createProgram(ast).eval();
1038+
Object result =
1039+
cel.createProgram(ast)
1040+
.eval(
1041+
PartialVars.of(
1042+
ImmutableMap.of(), CelAttributePattern.fromQualifiedIdentifier("optx")));
10411043

10421044
assertThat(InterpreterUtil.isUnknown(result)).isTrue();
10431045
}

runtime/BUILD.bazel

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,9 @@ java_library(
351351
"//runtime/src/main/java/dev/cel/runtime:runtime_planner_impl",
352352
],
353353
)
354+
355+
java_library(
356+
name = "partial_vars",
357+
visibility = ["//:internal"],
358+
exports = ["//runtime/src/main/java/dev/cel/runtime:partial_vars"],
359+
)

runtime/src/main/java/dev/cel/runtime/BUILD.bazel

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ java_library(
826826
":evaluation_listener",
827827
":function_binding",
828828
":function_resolver",
829+
":partial_vars",
829830
":program",
830831
":proto_message_runtime_equality",
831832
":runtime",
@@ -938,6 +939,7 @@ java_library(
938939
":function_resolver",
939940
":interpretable",
940941
":interpreter",
942+
":partial_vars",
941943
":program",
942944
":proto_message_activation_factory",
943945
":runtime_equality",
@@ -955,7 +957,6 @@ java_library(
955957
"@maven//:com_google_errorprone_error_prone_annotations",
956958
"@maven//:com_google_guava_guava",
957959
"@maven//:com_google_protobuf_protobuf_java",
958-
"@maven//:org_jspecify_jspecify",
959960
],
960961
)
961962

@@ -1014,6 +1015,7 @@ java_library(
10141015
":evaluation_exception",
10151016
":function_resolver",
10161017
":interpretable",
1018+
":partial_vars",
10171019
":program",
10181020
":variable_resolver",
10191021
"//:auto_value",
@@ -1029,6 +1031,7 @@ cel_android_library(
10291031
":evaluation_exception",
10301032
":function_resolver_android",
10311033
":interpretable_android",
1034+
":partial_vars_android",
10321035
":program_android",
10331036
":variable_resolver",
10341037
"//:auto_value",
@@ -1318,6 +1321,35 @@ cel_android_library(
13181321
],
13191322
)
13201323

1324+
java_library(
1325+
name = "partial_vars",
1326+
srcs = ["PartialVars.java"],
1327+
tags = [
1328+
],
1329+
deps = [
1330+
":variable_resolver",
1331+
"//:auto_value",
1332+
"//runtime:unknown_attributes",
1333+
"@maven//:com_google_errorprone_error_prone_annotations",
1334+
"@maven//:com_google_guava_guava",
1335+
],
1336+
)
1337+
1338+
cel_android_library(
1339+
name = "partial_vars_android",
1340+
srcs = ["PartialVars.java"],
1341+
tags = [
1342+
],
1343+
visibility = ["//third_party/java/cel:__subpackages__"],
1344+
deps = [
1345+
":variable_resolver",
1346+
"//:auto_value",
1347+
"//runtime:unknown_attributes_android",
1348+
"@maven//:com_google_errorprone_error_prone_annotations",
1349+
"@maven_android//:com_google_guava_guava",
1350+
],
1351+
)
1352+
13211353
java_library(
13221354
name = "program",
13231355
srcs = ["Program.java"],
@@ -1326,6 +1358,7 @@ java_library(
13261358
deps = [
13271359
":evaluation_exception",
13281360
":function_resolver",
1361+
":partial_vars",
13291362
":variable_resolver",
13301363
"@maven//:com_google_errorprone_error_prone_annotations",
13311364
],
@@ -1339,8 +1372,8 @@ cel_android_library(
13391372
deps = [
13401373
":evaluation_exception",
13411374
":function_resolver_android",
1375+
":partial_vars_android",
13421376
":variable_resolver",
1343-
"//:auto_value",
13441377
"@maven//:com_google_errorprone_error_prone_annotations",
13451378
],
13461379
)

runtime/src/main/java/dev/cel/runtime/CelRuntimeImpl.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ public Object eval(
134134
return program.eval(resolver, lateBoundFunctionResolver);
135135
}
136136

137+
@Override
138+
public Object eval(PartialVars partialVars) throws CelEvaluationException {
139+
return program.eval(partialVars);
140+
}
141+
142+
@Override
143+
public Object eval(PartialVars partialVars, CelFunctionResolver lateBoundFunctionResolver)
144+
throws CelEvaluationException {
145+
return program.eval(partialVars, lateBoundFunctionResolver);
146+
}
147+
137148
@Override
138149
public Object trace(CelEvaluationListener listener) throws CelEvaluationException {
139150
throw new UnsupportedOperationException("Trace is not yet supported.");

runtime/src/main/java/dev/cel/runtime/LiteProgramImpl.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ public Object eval(CelVariableResolver resolver) throws CelEvaluationException {
5252
throw new UnsupportedOperationException("To be implemented");
5353
}
5454

55+
@Override
56+
public Object eval(PartialVars partialVars) throws CelEvaluationException {
57+
// TODO: Wire in program planner
58+
throw new UnsupportedOperationException("To be implemented");
59+
}
60+
61+
@Override
62+
public Object eval(PartialVars partialVars, CelFunctionResolver lateBoundFunctionResolver)
63+
throws CelEvaluationException {
64+
// TODO: Wire in program planner
65+
throw new UnsupportedOperationException("To be implemented");
66+
}
67+
5568
static Program plan(Interpretable interpretable) {
5669
return new AutoValue_LiteProgramImpl(interpretable);
5770
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.runtime;
16+
17+
import com.google.auto.value.AutoValue;
18+
import com.google.common.collect.ImmutableList;
19+
import java.util.Map;
20+
import java.util.Optional;
21+
22+
/**
23+
* A holder for a {@link CelVariableResolver} and a set of {@link CelAttributePattern}s that dictate
24+
* which missing attributes should evaluate to {@link dev.cel.common.values.CelUnknownSet}.
25+
*/
26+
@AutoValue
27+
public abstract class PartialVars {
28+
29+
/** The resolver to use for resolving evaluation variables. */
30+
public abstract CelVariableResolver resolver();
31+
32+
/**
33+
* A list of attribute patterns specifying which missing attribute paths should be tracked as
34+
* unknown values.
35+
*/
36+
public abstract ImmutableList<CelAttributePattern> unknowns();
37+
38+
/**
39+
* Constructs a new {@code PartialVars} from a {@link CelVariableResolver} and a list of {@link
40+
* CelAttributePattern}s.
41+
*/
42+
public static PartialVars create(
43+
CelVariableResolver resolver, Iterable<CelAttributePattern> unknowns) {
44+
return new AutoValue_PartialVars(resolver, ImmutableList.copyOf(unknowns));
45+
}
46+
47+
/**
48+
* Constructs a new {@code PartialVars} from a map of variables and an array of {@link
49+
* CelAttributePattern}s.
50+
*/
51+
public static PartialVars of(Map<String, ?> variables, CelAttributePattern... unknowns) {
52+
return create(
53+
(name) -> variables.containsKey(name) ? Optional.of(variables.get(name)) : Optional.empty(),
54+
unknowns);
55+
}
56+
57+
/**
58+
* Constructs a new {@code PartialVars} from a {@link CelVariableResolver} and an array of {@link
59+
* CelAttributePattern}s.
60+
*/
61+
public static PartialVars create(CelVariableResolver resolver, CelAttributePattern... unknowns) {
62+
return create(resolver, ImmutableList.copyOf(unknowns));
63+
}
64+
}

runtime/src/main/java/dev/cel/runtime/Program.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,10 @@ Object eval(Map<String, ?> mapValue, CelFunctionResolver lateBoundFunctionResolv
4343
*/
4444
Object eval(CelVariableResolver resolver, CelFunctionResolver lateBoundFunctionResolver)
4545
throws CelEvaluationException;
46+
47+
/** Evaluate a compiled program with unknown attribute patterns {@code partialVars}. */
48+
Object eval(PartialVars partialVars) throws CelEvaluationException;
49+
50+
Object eval(PartialVars partialVars, CelFunctionResolver lateBoundFunctionResolver)
51+
throws CelEvaluationException;
4652
}

runtime/src/main/java/dev/cel/runtime/ProgramImpl.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,23 @@ public Object eval(Map<String, ?> mapValue, CelFunctionResolver lateBoundFunctio
6060
return evalInternal(Activation.copyOf(mapValue), lateBoundFunctionResolver);
6161
}
6262

63+
@Override
64+
public Object eval(PartialVars partialVars) throws CelEvaluationException {
65+
return evalInternal(
66+
UnknownContext.create(partialVars.resolver(), partialVars.unknowns()),
67+
Optional.empty(),
68+
Optional.empty());
69+
}
70+
71+
@Override
72+
public Object eval(PartialVars partialVars, CelFunctionResolver lateBoundFunctionResolver)
73+
throws CelEvaluationException {
74+
return evalInternal(
75+
UnknownContext.create(partialVars.resolver(), partialVars.unknowns()),
76+
Optional.of(lateBoundFunctionResolver),
77+
Optional.empty());
78+
}
79+
6380
@Override
6481
public Object trace(CelEvaluationListener listener) throws CelEvaluationException {
6582
return evalInternal(Activation.EMPTY, listener);

0 commit comments

Comments
 (0)