Skip to content

Commit 36d2676

Browse files
l46kokcopybara-github
authored andcommitted
Support container resolution for calls and struct creation in planner
PiperOrigin-RevId: 838980202
1 parent 33a4cb0 commit 36d2676

File tree

5 files changed

+259
-31
lines changed

5 files changed

+259
-31
lines changed

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ java_library(
1616
":attribute",
1717
":eval_and",
1818
":eval_attribute",
19+
":eval_conditional",
1920
":eval_const",
2021
":eval_create_list",
2122
":eval_create_map",
@@ -168,6 +169,18 @@ java_library(
168169
],
169170
)
170171

172+
java_library(
173+
name = "eval_conditional",
174+
srcs = ["EvalConditional.java"],
175+
deps = [
176+
"//runtime:evaluation_exception",
177+
"//runtime:evaluation_listener",
178+
"//runtime:function_resolver",
179+
"//runtime:interpretable",
180+
"@maven//:com_google_guava_guava",
181+
],
182+
)
183+
171184
java_library(
172185
name = "eval_create_struct",
173186
srcs = ["EvalCreateStruct.java"],
@@ -188,7 +201,7 @@ java_library(
188201
name = "eval_create_list",
189202
srcs = ["EvalCreateList.java"],
190203
deps = [
191-
"//runtime",
204+
"//runtime:evaluation_exception",
192205
"//runtime:evaluation_listener",
193206
"//runtime:function_resolver",
194207
"//runtime:interpretable",
@@ -201,7 +214,7 @@ java_library(
201214
name = "eval_create_map",
202215
srcs = ["EvalCreateMap.java"],
203216
deps = [
204-
"//runtime",
217+
"//runtime:evaluation_exception",
205218
"//runtime:evaluation_listener",
206219
"//runtime:function_resolver",
207220
"//runtime:interpretable",
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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.common.base.Preconditions;
18+
import dev.cel.runtime.CelEvaluationException;
19+
import dev.cel.runtime.CelEvaluationListener;
20+
import dev.cel.runtime.CelFunctionResolver;
21+
import dev.cel.runtime.GlobalResolver;
22+
import dev.cel.runtime.Interpretable;
23+
24+
final class EvalConditional implements Interpretable {
25+
26+
@SuppressWarnings("Immutable")
27+
private final Interpretable[] args;
28+
29+
@Override
30+
public Object eval(GlobalResolver resolver) throws CelEvaluationException {
31+
Interpretable condition = args[0];
32+
Interpretable truthy = args[1];
33+
Interpretable falsy = args[2];
34+
// TODO: Handle unknowns
35+
Object condResult = condition.eval(resolver);
36+
if (!(condResult instanceof Boolean)) {
37+
throw new IllegalArgumentException(
38+
String.format("Expected boolean value, found :%s", condResult));
39+
}
40+
41+
// TODO: Handle exhaustive eval
42+
if ((boolean) condResult) {
43+
return truthy.eval(resolver);
44+
}
45+
46+
return falsy.eval(resolver);
47+
}
48+
49+
@Override
50+
public Object eval(GlobalResolver resolver, CelEvaluationListener listener) {
51+
// TODO: Implement support
52+
throw new UnsupportedOperationException("Not yet supported");
53+
}
54+
55+
@Override
56+
public Object eval(GlobalResolver resolver, CelFunctionResolver lateBoundFunctionResolver) {
57+
// TODO: Implement support
58+
throw new UnsupportedOperationException("Not yet supported");
59+
}
60+
61+
@Override
62+
public Object eval(
63+
GlobalResolver resolver,
64+
CelFunctionResolver lateBoundFunctionResolver,
65+
CelEvaluationListener listener) {
66+
// TODO: Implement support
67+
throw new UnsupportedOperationException("Not yet supported");
68+
}
69+
70+
static EvalConditional create(Interpretable[] args) {
71+
return new EvalConditional(args);
72+
}
73+
74+
private EvalConditional(Interpretable[] args) {
75+
Preconditions.checkArgument(args.length == 3);
76+
this.args = args;
77+
}
78+
}

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

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import dev.cel.common.ast.CelExpr.CelCall;
2929
import dev.cel.common.ast.CelExpr.CelList;
3030
import dev.cel.common.ast.CelExpr.CelMap;
31+
import dev.cel.common.ast.CelExpr.CelSelect;
3132
import dev.cel.common.ast.CelExpr.CelStruct;
3233
import dev.cel.common.ast.CelExpr.CelStruct.Entry;
3334
import dev.cel.common.ast.CelReference;
@@ -58,6 +59,7 @@ public final class ProgramPlanner {
5859
private final CelValueProvider valueProvider;
5960
private final DefaultDispatcher dispatcher;
6061
private final AttributeFactory attributeFactory;
62+
private final CelContainer container;
6163

6264
/**
6365
* Plans a {@link Program} from the provided parsed-only or type-checked {@link
@@ -168,7 +170,6 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
168170
evaluatedArgs[argIndex + offset] = plan(args.get(argIndex), ctx);
169171
}
170172

171-
// TODO: Handle all specialized calls (logical operators, conditionals, equals etc)
172173
String functionName = resolvedFunction.functionName();
173174
Operator operator = Operator.findReverse(functionName).orElse(null);
174175
if (operator != null) {
@@ -177,6 +178,8 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
177178
return EvalOr.create(evaluatedArgs);
178179
case LOGICAL_AND:
179180
return EvalAnd.create(evaluatedArgs);
181+
case CONDITIONAL:
182+
return EvalConditional.create(evaluatedArgs);
180183
default:
181184
// fall-through
182185
}
@@ -207,16 +210,7 @@ private Interpretable planCall(CelExpr expr, PlannerContext ctx) {
207210

208211
private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
209212
CelStruct struct = celExpr.struct();
210-
CelType structType =
211-
typeProvider
212-
.findType(struct.messageName())
213-
.orElseThrow(
214-
() -> new IllegalArgumentException("Undefined type name: " + struct.messageName()));
215-
if (!structType.kind().equals(CelKind.STRUCT)) {
216-
throw new IllegalArgumentException(
217-
String.format(
218-
"Expected struct type for %s, got %s", structType.name(), structType.kind()));
219-
}
213+
StructType structType = resolveStructType(struct);
220214

221215
ImmutableList<Entry> entries = struct.entries();
222216
String[] keys = new String[entries.size()];
@@ -228,7 +222,7 @@ private Interpretable planCreateStruct(CelExpr celExpr, PlannerContext ctx) {
228222
values[i] = plan(entry.value(), ctx);
229223
}
230224

231-
return EvalCreateStruct.create(valueProvider, (StructType) structType, keys, values);
225+
return EvalCreateStruct.create(valueProvider, structType, keys, values);
232226
}
233227

234228
private Interpretable planCreateList(CelExpr celExpr, PlannerContext ctx) {
@@ -267,7 +261,7 @@ private Interpretable planCreateMap(CelExpr celExpr, PlannerContext ctx) {
267261
private ResolvedFunction resolveFunction(
268262
CelExpr expr, ImmutableMap<Long, CelReference> referenceMap) {
269263
CelCall call = expr.call();
270-
Optional<CelExpr> target = call.target();
264+
Optional<CelExpr> maybeTarget = call.target();
271265
String functionName = call.function();
272266

273267
CelReference reference = referenceMap.get(expr.id());
@@ -279,22 +273,89 @@ private ResolvedFunction resolveFunction(
279273
.setFunctionName(functionName)
280274
.setOverloadId(reference.overloadIds().get(0));
281275

282-
target.ifPresent(builder::setTarget);
276+
maybeTarget.ifPresent(builder::setTarget);
283277

284278
return builder.build();
285279
}
286280
}
287281

288-
// Parsed-only.
289-
// TODO: Handle containers.
290-
if (!target.isPresent()) {
282+
// Parsed-only function resolution.
283+
//
284+
// There are two distinct cases we must handle:
285+
//
286+
// 1. Non-qualified function calls. This will resolve into either:
287+
// - A simple global call foo()
288+
// - A fully qualified global call through normal container resolution foo.bar.qux()
289+
// 2. Qualified function calls:
290+
// - A member call on an identifier foo.bar()
291+
// - A fully qualified global call, through normal container resolution or abbreviations
292+
// foo.bar.qux()
293+
if (!maybeTarget.isPresent()) {
294+
for (String cand : container.resolveCandidateNames(functionName)) {
295+
CelResolvedOverload overload = dispatcher.findOverload(cand).orElse(null);
296+
if (overload != null) {
297+
return ResolvedFunction.newBuilder().setFunctionName(cand).build();
298+
}
299+
}
300+
301+
// Normal global call
291302
return ResolvedFunction.newBuilder().setFunctionName(functionName).build();
292-
} else {
293-
return ResolvedFunction.newBuilder()
294-
.setFunctionName(functionName)
295-
.setTarget(target.get())
296-
.build();
297303
}
304+
305+
CelExpr target = maybeTarget.get();
306+
String qualifiedPrefix = toQualifiedName(target).orElse(null);
307+
if (qualifiedPrefix != null) {
308+
String qualifiedName = qualifiedPrefix + "." + functionName;
309+
for (String cand : container.resolveCandidateNames(qualifiedName)) {
310+
CelResolvedOverload overload = dispatcher.findOverload(cand).orElse(null);
311+
if (overload != null) {
312+
return ResolvedFunction.newBuilder().setFunctionName(cand).build();
313+
}
314+
}
315+
}
316+
317+
// Normal member call
318+
return ResolvedFunction.newBuilder().setFunctionName(functionName).setTarget(target).build();
319+
}
320+
321+
private StructType resolveStructType(CelStruct struct) {
322+
String messageName = struct.messageName();
323+
for (String typeName : container.resolveCandidateNames(messageName)) {
324+
CelType structType = typeProvider.findType(typeName).orElse(null);
325+
if (structType == null) {
326+
continue;
327+
}
328+
329+
if (!structType.kind().equals(CelKind.STRUCT)) {
330+
throw new IllegalArgumentException(
331+
String.format(
332+
"Expected struct type for %s, got %s", structType.name(), structType.kind()));
333+
}
334+
335+
return (StructType) structType;
336+
}
337+
338+
throw new IllegalArgumentException("Undefined type name: " + messageName);
339+
}
340+
341+
/** Converts a given expression into a qualified name, if possible. */
342+
private Optional<String> toQualifiedName(CelExpr operand) {
343+
switch (operand.getKind()) {
344+
case IDENT:
345+
return Optional.of(operand.ident().name());
346+
case SELECT:
347+
CelSelect select = operand.select();
348+
String maybeQualified = toQualifiedName(select.operand()).orElse(null);
349+
if (maybeQualified != null) {
350+
return Optional.of(maybeQualified + "." + select.field());
351+
}
352+
353+
break;
354+
default:
355+
// fall-through
356+
}
357+
358+
return Optional.empty();
298359
}
299360

300361
@AutoValue
@@ -336,17 +397,22 @@ private static PlannerContext create(CelAbstractSyntaxTree ast) {
336397
}
337398

338399
public static ProgramPlanner newPlanner(
339-
CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher) {
340-
return new ProgramPlanner(typeProvider, valueProvider, dispatcher);
400+
CelTypeProvider typeProvider,
401+
CelValueProvider valueProvider,
402+
DefaultDispatcher dispatcher,
403+
CelContainer container) {
404+
return new ProgramPlanner(typeProvider, valueProvider, dispatcher, container);
341405
}
342406

343407
private ProgramPlanner(
344-
CelTypeProvider typeProvider, CelValueProvider valueProvider, DefaultDispatcher dispatcher) {
408+
CelTypeProvider typeProvider,
409+
CelValueProvider valueProvider,
410+
DefaultDispatcher dispatcher,
411+
CelContainer container) {
345412
this.typeProvider = typeProvider;
346413
this.valueProvider = valueProvider;
347-
// TODO: Container support
348414
this.dispatcher = dispatcher;
349-
this.attributeFactory =
350-
AttributeFactory.newAttributeFactory(CelContainer.newBuilder().build(), typeProvider);
415+
this.container = container;
416+
this.attributeFactory = AttributeFactory.newAttributeFactory(container, typeProvider);
351417
}
352418
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ java_library(
1818
"//common:cel_descriptor_util",
1919
"//common:cel_source",
2020
"//common:compiler_common",
21+
"//common:container",
2122
"//common:error_codes",
2223
"//common:operator",
2324
"//common:options",

0 commit comments

Comments
 (0)