Skip to content

Commit ff4174a

Browse files
l46kokcopybara-github
authored andcommitted
Plan CreateStruct
PiperOrigin-RevId: 835306497
1 parent ceece69 commit ff4174a

File tree

5 files changed

+234
-17
lines changed

5 files changed

+234
-17
lines changed

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ java_library(
1818
":eval_const",
1919
":eval_create_list",
2020
":eval_create_map",
21+
":eval_create_struct",
2122
":planned_program",
2223
"//:auto_value",
2324
"//common:cel_ast",
@@ -26,6 +27,7 @@ java_library(
2627
"//common/ast",
2728
"//common/types",
2829
"//common/types:type_providers",
30+
"//common/values:cel_value_provider",
2931
"//runtime:evaluation_exception",
3032
"//runtime:evaluation_exception_builder",
3133
"//runtime:interpretable",
@@ -93,6 +95,22 @@ java_library(
9395
],
9496
)
9597

98+
java_library(
99+
name = "eval_create_struct",
100+
srcs = ["EvalCreateStruct.java"],
101+
deps = [
102+
"//common/types",
103+
"//common/values",
104+
"//common/values:cel_value_provider",
105+
"//runtime:evaluation_exception",
106+
"//runtime:evaluation_listener",
107+
"//runtime:function_resolver",
108+
"//runtime:interpretable",
109+
"@maven//:com_google_errorprone_error_prone_annotations",
110+
"@maven//:com_google_guava_guava",
111+
],
112+
)
113+
96114
java_library(
97115
name = "eval_create_list",
98116
srcs = ["EvalCreateList.java"],
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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.planner;
16+
17+
import com.google.errorprone.annotations.Immutable;
18+
import dev.cel.common.types.StructType;
19+
import dev.cel.common.values.CelValueProvider;
20+
import dev.cel.common.values.StructValue;
21+
import dev.cel.runtime.CelEvaluationException;
22+
import dev.cel.runtime.CelEvaluationListener;
23+
import dev.cel.runtime.CelFunctionResolver;
24+
import dev.cel.runtime.GlobalResolver;
25+
import dev.cel.runtime.Interpretable;
26+
import java.util.Collections;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
30+
@Immutable
31+
final class EvalCreateStruct implements Interpretable {
32+
33+
private final CelValueProvider valueProvider;
34+
private final StructType structType;
35+
36+
// Array contents are not mutated
37+
@SuppressWarnings("Immutable")
38+
private final String[] keys;
39+
40+
// Array contents are not mutated
41+
@SuppressWarnings("Immutable")
42+
private final Interpretable[] values;
43+
44+
@Override
45+
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
46+
Map<String, Object> fieldValues = new HashMap<>();
47+
for (int i = 0; i < keys.length; i++) {
48+
Object value = values[i].eval(resolver);
49+
fieldValues.put(keys[i], value);
50+
}
51+
52+
// Either a primitive (wrappers) or a struct is produced
53+
Object value =
54+
valueProvider
55+
.newValue(structType.name(), Collections.unmodifiableMap(fieldValues))
56+
.orElseThrow(() -> new IllegalArgumentException("Type name not found: " + structType));
57+
58+
if (value instanceof StructValue) {
59+
return ((StructValue) value).value();
60+
}
61+
62+
return value;
63+
}
64+
65+
@Override
66+
public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
67+
// TODO: Implement support
68+
throw new UnsupportedOperationException("Not yet supported");
69+
}
70+
71+
@Override
72+
public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
73+
// TODO: Implement support
74+
throw new UnsupportedOperationException("Not yet supported");
75+
}
76+
77+
@Override
78+
public Object eval(
79+
GlobalResolver resolver,
80+
CelFunctionResolver lateBoundFunctionResolver,
81+
CelEvaluationListener listener) {
82+
// TODO: Implement support
83+
throw new UnsupportedOperationException("Not yet supported");
84+
}
85+
86+
static EvalCreateStruct create(
87+
CelValueProvider valueProvider,
88+
StructType structType,
89+
String[] keys,
90+
Interpretable[] values) {
91+
return new EvalCreateStruct(valueProvider, structType, keys, values);
92+
}
93+
94+
private EvalCreateStruct(
95+
CelValueProvider valueProvider,
96+
StructType structType,
97+
String[] keys,
98+
Interpretable[] values) {
99+
this.valueProvider = valueProvider;
100+
this.structType = structType;
101+
this.keys = keys;
102+
this.values = values;
103+
}
104+
}

runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@
2525
import dev.cel.common.ast.CelExpr;
2626
import dev.cel.common.ast.CelExpr.CelList;
2727
import dev.cel.common.ast.CelExpr.CelMap;
28+
import dev.cel.common.ast.CelExpr.CelStruct;
29+
import dev.cel.common.ast.CelExpr.CelStruct.Entry;
2830
import dev.cel.common.ast.CelReference;
2931
import dev.cel.common.types.CelKind;
3032
import dev.cel.common.types.CelType;
3133
import dev.cel.common.types.CelTypeProvider;
34+
import dev.cel.common.types.StructType;
3235
import dev.cel.common.types.TypeType;
36+
import dev.cel.common.values.CelValueProvider;
3337
import dev.cel.runtime.CelEvaluationException;
3438
import dev.cel.runtime.CelEvaluationExceptionBuilder;
3539
import dev.cel.runtime.Interpretable;
@@ -45,6 +49,7 @@
4549
public final class ProgramPlanner {
4650

4751
private final CelTypeProvider typeProvider;
52+
private final CelValueProvider valueProvider;
4853
private final AttributeFactory attributeFactory;
4954

5055
/**
@@ -70,6 +75,8 @@ private Interpretable plan(CelExpr celExpr, PlannerContext ctx) {
7075
return planIdent(celExpr, ctx);
7176
case LIST:
7277
return planCreateList(celExpr, ctx);
78+
case STRUCT:
79+
return planCreateStruct(celExpr, ctx);
7380
case MAP:
7481
return planCreateMap(celExpr, ctx);
7582
case NOT_SET:
@@ -131,6 +138,32 @@ private Interpretable planCheckedIdent(
131138
return EvalAttribute.create(attributeFactory.newAbsoluteAttribute(identRef.name()));
132139
}
133140

141+
private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
142+
CelStruct struct = celExpr.struct();
143+
CelType structType =
144+
typeProvider
145+
.findType(struct.messageName())
146+
.orElseThrow(
147+
() -> new IllegalArgumentException("Undefined type name: " + struct.messageName()));
148+
if (!structType.kind().equals(CelKind.STRUCT)) {
149+
throw new IllegalArgumentException(
150+
String.format(
151+
"Expected struct type for %s, got %s", structType.name(), structType.kind()));
152+
}
153+
154+
ImmutableList<Entry> entries = struct.entries();
155+
String[] keys = new String[entries.size()];
156+
Interpretable[] values = new Interpretable[entries.size()];
157+
158+
for (int i = 0; i < entries.size(); i++) {
159+
Entry entry = entries.get(i);
160+
keys[i] = entry.fieldKey();
161+
values[i] = plan(entry.value(), ctx);
162+
}
163+
164+
return EvalCreateStruct.create(valueProvider, (StructType) structType, keys, values);
165+
}
166+
134167
private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) {
135168
CelList list = celExpr.list();
136169

@@ -172,12 +205,14 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) {
172205
}
173206
}
174207

175-
public static ProgramPlanner newPlanner(CelTypeProvider typeProvider) {
176-
return new ProgramPlanner(typeProvider);
208+
public static ProgramPlanner newPlanner(
209+
CelTypeProvider typeProvider, CelValueProvider valueProvider) {
210+
return new ProgramPlanner(typeProvider, valueProvider);
177211
}
178212

179-
private ProgramPlanner(CelTypeProvider typeProvider) {
213+
private ProgramPlanner(CelTypeProvider typeProvider, CelValueProvider valueProvider) {
180214
this.typeProvider = typeProvider;
215+
this.valueProvider = valueProvider;
181216
// TODO: Container support
182217
this.attributeFactory =
183218
AttributeFactory.newAttributeFactory(CelContainer.newBuilder().build(), typeProvider);

runtime/src/test/java/dev/cel/runtime/planner/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,21 @@ java_library(
1515
deps = [
1616
"//:java_truth",
1717
"//common:cel_ast",
18+
"//common:cel_descriptor_util",
1819
"//common:cel_source",
20+
"//common:options",
1921
"//common/ast",
22+
"//common/internal:cel_descriptor_pools",
23+
"//common/internal:default_message_factory",
24+
"//common/internal:dynamic_proto",
2025
"//common/types",
2126
"//common/types:default_type_provider",
2227
"//common/types:message_type_provider",
2328
"//common/types:type_providers",
2429
"//common/values",
2530
"//common/values:cel_byte_string",
31+
"//common/values:cel_value_provider",
32+
"//common/values:proto_message_value_provider",
2633
"//compiler",
2734
"//compiler:compiler_builder",
2835
"//extensions",

runtime/src/test/java/dev/cel/runtime/planner/ProgramPlannerTest.java

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@
2525
import com.google.testing.junit.testparameterinjector.TestParameter;
2626
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
2727
import dev.cel.common.CelAbstractSyntaxTree;
28+
import dev.cel.common.CelDescriptorUtil;
29+
import dev.cel.common.CelOptions;
2830
import dev.cel.common.CelSource;
2931
import dev.cel.common.ast.CelExpr;
32+
import dev.cel.common.internal.CelDescriptorPool;
33+
import dev.cel.common.internal.DefaultDescriptorPool;
34+
import dev.cel.common.internal.DefaultMessageFactory;
35+
import dev.cel.common.internal.DynamicProto;
3036
import dev.cel.common.types.CelType;
3137
import dev.cel.common.types.CelTypeProvider;
3238
import dev.cel.common.types.CelTypeProvider.CombinedCelTypeProvider;
@@ -38,7 +44,9 @@
3844
import dev.cel.common.types.SimpleType;
3945
import dev.cel.common.types.TypeType;
4046
import dev.cel.common.values.CelByteString;
47+
import dev.cel.common.values.CelValueProvider;
4148
import dev.cel.common.values.NullValue;
49+
import dev.cel.common.values.ProtoMessageValueProvider;
4250
import dev.cel.compiler.CelCompiler;
4351
import dev.cel.compiler.CelCompilerFactory;
4452
import dev.cel.expr.conformance.proto3.GlobalEnum;
@@ -56,8 +64,17 @@ public final class ProgramPlannerTest {
5664
new CombinedCelTypeProvider(
5765
DefaultTypeProvider.getInstance(),
5866
new ProtoMessageTypeProvider(ImmutableSet.of(TestAllTypes.getDescriptor())));
59-
60-
private static final ProgramPlanner PLANNER = ProgramPlanner.newPlanner(TYPE_PROVIDER);
67+
private static final CelDescriptorPool DESCRIPTOR_POOL =
68+
DefaultDescriptorPool.create(
69+
CelDescriptorUtil.getAllDescriptorsFromFileDescriptor(
70+
TestAllTypes.getDescriptor().getFile()));
71+
private static final DynamicProto DYNAMIC_PROTO =
72+
DynamicProto.create(DefaultMessageFactory.create(DESCRIPTOR_POOL));
73+
private static final CelValueProvider VALUE_PROVIDER =
74+
ProtoMessageValueProvider.newInstance(CelOptions.DEFAULT, DYNAMIC_PROTO);
75+
76+
private static final ProgramPlanner PLANNER =
77+
ProgramPlanner.newPlanner(TYPE_PROVIDER, VALUE_PROVIDER);
6178
private static final CelCompiler CEL_COMPILER =
6279
CelCompilerFactory.standardCelCompilerBuilder()
6380
.addVar("int_var", SimpleType.INT)
@@ -113,6 +130,24 @@ public void plan_ident_variable() throws Exception {
113130
assertThat(result).isEqualTo(1);
114131
}
115132

133+
@Test
134+
public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception {
135+
if (isParseOnly) {
136+
if (testCase.equals(TypeLiteralTestCase.DURATION)
137+
|| testCase.equals(TypeLiteralTestCase.TIMESTAMP)
138+
|| testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) {
139+
// TODO Skip for now, requires attribute qualification
140+
return;
141+
}
142+
}
143+
CelAbstractSyntaxTree ast = compile(testCase.expression);
144+
Program program = PLANNER.plan(ast);
145+
146+
TypeType result = (TypeType) program.eval();
147+
148+
assertThat(result).isEqualTo(testCase.type);
149+
}
150+
116151
@Test
117152
@SuppressWarnings("unchecked") // test only
118153
public void plan_createList() throws Exception {
@@ -136,21 +171,39 @@ public void plan_createMap() throws Exception {
136171
}
137172

138173
@Test
139-
public void planIdent_typeLiteral(@TestParameter TypeLiteralTestCase testCase) throws Exception {
140-
if (isParseOnly) {
141-
if (testCase.equals(TypeLiteralTestCase.DURATION)
142-
|| testCase.equals(TypeLiteralTestCase.TIMESTAMP)
143-
|| testCase.equals(TypeLiteralTestCase.PROTO_MESSAGE_TYPE)) {
144-
// TODO Skip for now, requires attribute qualification
145-
return;
146-
}
147-
}
148-
CelAbstractSyntaxTree ast = compile(testCase.expression);
174+
public void plan_createStruct() throws Exception {
175+
CelAbstractSyntaxTree ast = compile("cel.expr.conformance.proto3.TestAllTypes{}");
149176
Program program = PLANNER.plan(ast);
150177

151-
TypeType result = (TypeType) program.eval();
178+
TestAllTypes result = (TestAllTypes) program.eval();
152179

153-
assertThat(result).isEqualTo(testCase.type);
180+
assertThat(result).isEqualTo(TestAllTypes.getDefaultInstance());
181+
}
182+
183+
@Test
184+
public void plan_createStruct_wrapper() throws Exception {
185+
CelAbstractSyntaxTree ast = compile("google.protobuf.StringValue { value: 'foo' }");
186+
Program program = PLANNER.plan(ast);
187+
188+
String result = (String) program.eval();
189+
190+
assertThat(result).isEqualTo("foo");
191+
}
192+
193+
@Test
194+
public void planCreateStruct_withFields() throws Exception {
195+
CelAbstractSyntaxTree ast =
196+
compile(
197+
"cel.expr.conformance.proto3.TestAllTypes{"
198+
+ "single_string: 'foo',"
199+
+ "single_bool: true"
200+
+ "}");
201+
Program program = PLANNER.plan(ast);
202+
203+
TestAllTypes result = (TestAllTypes) program.eval();
204+
205+
assertThat(result)
206+
.isEqualTo(TestAllTypes.newBuilder().setSingleString("foo").setSingleBool(true).build());
154207
}
155208

156209
private CelAbstractSyntaxTree compile(String expression) throws Exception {

0 commit comments

Comments
 (0)