Skip to content

Commit 7fe024f

Browse files
dmitriplotnikovcopybara-github
authored andcommitted
Add CelFunction builder and CelBuilder.addFunctions for unified function declaration and binding.
- Introduce `CelFunction` and `CelFunction.Builder` in `dev.cel.bundle` with `addMemberOverload` and `addGlobalOverload` (unary, binary, and N-ary) that automatically generate overload IDs and group runtime bindings via `CelFunctionBinding.fromOverloads`. - Add `addFunctions(CelFunction...)` and `addFunctions(Iterable<CelFunction>)` to `CelBuilder` and `CelImpl.Builder` to register both the compile-time declaration and runtime bindings in a single call. PiperOrigin-RevId: 1003500502
1 parent 63bd428 commit 7fe024f

5 files changed

Lines changed: 521 additions & 0 deletions

File tree

‎bundle/src/main/java/dev/cel/bundle/BUILD.bazel‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ package(
1111
CEL_SOURCES = [
1212
"Cel.java",
1313
"CelBuilder.java",
14+
"CelFunction.java",
1415
]
1516

1617
java_library(
@@ -19,9 +20,12 @@ java_library(
1920
tags = [
2021
],
2122
deps = [
23+
"//:auto_value",
2224
"//checker:checker_legacy_environment",
2325
"//checker:proto_type_mask",
2426
"//checker:standard_decl",
27+
"//common:cel_function_decl",
28+
"//common:cel_overload_decl",
2529
"//common:compiler_common",
2630
"//common:container",
2731
"//common:options",
@@ -35,6 +39,7 @@ java_library(
3539
"@cel_spec//proto/cel/expr:checked_java_proto",
3640
"@maven//:com_google_code_findbugs_annotations",
3741
"@maven//:com_google_errorprone_error_prone_annotations",
42+
"@maven//:com_google_guava_guava",
3843
"@maven//:com_google_protobuf_protobuf_java",
3944
],
4045
)

‎bundle/src/main/java/dev/cel/bundle/CelBuilder.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,20 @@ public interface CelBuilder {
117117
@CanIgnoreReturnValue
118118
CelBuilder addFunctionDeclarations(Iterable<CelFunctionDecl> celFunctionDecls);
119119

120+
/**
121+
* Add one or more {@link CelFunction} definitions (combining compile-time declarations and
122+
* runtime bindings) to the CEL environment.
123+
*/
124+
@CanIgnoreReturnValue
125+
CelBuilder addFunctions(CelFunction... functions);
126+
127+
/**
128+
* Add a collection of {@link CelFunction} definitions (combining compile-time declarations and
129+
* runtime bindings) to the CEL environment.
130+
*/
131+
@CanIgnoreReturnValue
132+
CelBuilder addFunctions(Iterable<CelFunction> functions);
133+
120134
/** Add variable declaration {@code CelVarDecl} to the CEL environment. */
121135
@CanIgnoreReturnValue
122136
CelBuilder addVarDeclarations(CelVarDecl... celVarDecls);
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
// Copyright 2026 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.bundle;
16+
17+
import static com.google.common.base.Preconditions.checkArgument;
18+
import static com.google.common.base.Preconditions.checkNotNull;
19+
20+
import com.google.auto.value.AutoValue;
21+
import com.google.common.base.Strings;
22+
import com.google.common.collect.ImmutableList;
23+
import com.google.common.collect.ImmutableSet;
24+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
25+
import com.google.errorprone.annotations.CheckReturnValue;
26+
import com.google.errorprone.annotations.Immutable;
27+
import dev.cel.common.CelFunctionDecl;
28+
import dev.cel.common.CelOverloadDecl;
29+
import dev.cel.common.types.CelType;
30+
import dev.cel.runtime.CelFunctionBinding;
31+
import dev.cel.runtime.CelFunctionOverload;
32+
import java.util.ArrayList;
33+
import java.util.HashSet;
34+
import java.util.List;
35+
import java.util.Set;
36+
import java.util.function.Function;
37+
38+
/**
39+
* Bundles a {@link CelFunctionDecl} and its corresponding runtime {@link CelFunctionBinding}s into
40+
* a single unit that can be registered on {@link CelBuilder#addFunctions}.
41+
*
42+
* <p>Overload IDs are automatically generated from the function name and parameter types.
43+
*/
44+
@AutoValue
45+
@Immutable
46+
public abstract class CelFunction {
47+
48+
CelFunction() {}
49+
50+
/** The compile-time declaration for this function. */
51+
public abstract CelFunctionDecl functionDecl();
52+
53+
/** The runtime bindings for this function's overloads. */
54+
public abstract ImmutableSet<CelFunctionBinding> functionBindings();
55+
56+
/**
57+
* Creates a new builder to construct a {@link CelFunction} with the given {@code functionName}.
58+
*/
59+
public static Builder newBuilder(String functionName) {
60+
return new Builder(functionName);
61+
}
62+
63+
/** Builder for configuring a {@link CelFunction}. */
64+
public static final class Builder {
65+
private final String functionName;
66+
private final List<CelOverloadDecl> overloadDecls = new ArrayList<>();
67+
// List of functions that take an overload ID and create a CelFunctionBinding. Since the
68+
// overload ID is not always known until build time, this list is applied to the overload IDs
69+
// in `build()`
70+
private final List<Function<String, CelFunctionBinding>> bindings = new ArrayList<>();
71+
72+
private Builder(String functionName) {
73+
checkArgument(!Strings.isNullOrEmpty(functionName), "Function name cannot be null or empty");
74+
this.functionName = functionName;
75+
}
76+
77+
/**
78+
* Adds a unary member overload (e.g. {@code x.f()}) with an automatically generated overload ID
79+
* (e.g. {@code "targetType.f()"}).
80+
*/
81+
@CanIgnoreReturnValue
82+
public <T> Builder addMemberOverload(
83+
CelType resultType,
84+
CelType targetType,
85+
Class<T> targetClass,
86+
CelFunctionOverload.Unary<T> impl) {
87+
checkNotNull(resultType);
88+
checkNotNull(targetType);
89+
checkNotNull(targetClass);
90+
checkNotNull(impl);
91+
overloadDecls.add(CelOverloadDecl.newMemberOverload(resultType, targetType));
92+
bindings.add(overloadId -> CelFunctionBinding.from(overloadId, targetClass, impl));
93+
return this;
94+
}
95+
96+
/**
97+
* Adds a binary member overload (e.g. {@code x.f(y)}) with an automatically generated overload
98+
* ID (e.g. {@code "targetType.f(argType)"}).
99+
*/
100+
@CanIgnoreReturnValue
101+
public <T1, T2> Builder addMemberOverload(
102+
CelType resultType,
103+
CelType targetType,
104+
CelType argType,
105+
Class<T1> targetClass,
106+
Class<T2> argClass,
107+
CelFunctionOverload.Binary<T1, T2> impl) {
108+
checkNotNull(resultType);
109+
checkNotNull(targetType);
110+
checkNotNull(argType);
111+
checkNotNull(targetClass);
112+
checkNotNull(argClass);
113+
checkNotNull(impl);
114+
overloadDecls.add(CelOverloadDecl.newMemberOverload(resultType, targetType, argType));
115+
bindings.add(overloadId -> CelFunctionBinding.from(overloadId, targetClass, argClass, impl));
116+
return this;
117+
}
118+
119+
/**
120+
* Adds an N-ary member overload (e.g. {@code x.f(y, z, ...)}) with an automatically generated
121+
* overload ID (e.g. {@code "targetType.f(arg1Type,arg2Type,...)"}), where the first element of
122+
* {@code argTypes} and {@code argClasses} is the receiver {@code x}.
123+
*/
124+
@CanIgnoreReturnValue
125+
public Builder addMemberOverload(
126+
CelType resultType,
127+
List<CelType> argTypes,
128+
List<Class<?>> argClasses,
129+
CelFunctionOverload impl) {
130+
return addNaryOverload(
131+
resultType, argTypes, argClasses, impl, /* isInstanceFunction= */ true);
132+
}
133+
134+
/**
135+
* Adds a unary global overload (e.g. {@code f(x)}) with an automatically generated overload ID
136+
* (e.g. {@code "f(argType)"}).
137+
*/
138+
@CanIgnoreReturnValue
139+
public <T> Builder addGlobalOverload(
140+
CelType resultType, CelType argType, Class<T> argClass, CelFunctionOverload.Unary<T> impl) {
141+
checkNotNull(resultType);
142+
checkNotNull(argType);
143+
checkNotNull(argClass);
144+
checkNotNull(impl);
145+
overloadDecls.add(CelOverloadDecl.newGlobalOverload(resultType, argType));
146+
bindings.add(overloadId -> CelFunctionBinding.from(overloadId, argClass, impl));
147+
return this;
148+
}
149+
150+
/**
151+
* Adds a binary global overload (e.g. {@code f(x, y)}) with an automatically generated overload
152+
* ID (e.g. {@code "f(arg1Type,arg2Type)"}).
153+
*/
154+
@CanIgnoreReturnValue
155+
public <T1, T2> Builder addGlobalOverload(
156+
CelType resultType,
157+
CelType arg1Type,
158+
CelType arg2Type,
159+
Class<T1> arg1Class,
160+
Class<T2> arg2Class,
161+
CelFunctionOverload.Binary<T1, T2> impl) {
162+
checkNotNull(resultType);
163+
checkNotNull(arg1Type);
164+
checkNotNull(arg2Type);
165+
checkNotNull(arg1Class);
166+
checkNotNull(arg2Class);
167+
checkNotNull(impl);
168+
overloadDecls.add(CelOverloadDecl.newGlobalOverload(resultType, arg1Type, arg2Type));
169+
bindings.add(overloadId -> CelFunctionBinding.from(overloadId, arg1Class, arg2Class, impl));
170+
return this;
171+
}
172+
173+
/**
174+
* Adds an N-ary global overload (including zero-argument {@code f()}) with an automatically
175+
* generated overload ID (e.g. {@code "f(arg1Type,arg2Type,...)"}).
176+
*/
177+
@CanIgnoreReturnValue
178+
public Builder addGlobalOverload(
179+
CelType resultType,
180+
List<CelType> argTypes,
181+
List<Class<?>> argClasses,
182+
CelFunctionOverload impl) {
183+
return addNaryOverload(
184+
resultType, argTypes, argClasses, impl, /* isInstanceFunction= */ false);
185+
}
186+
187+
@CanIgnoreReturnValue
188+
private Builder addNaryOverload(
189+
CelType resultType,
190+
List<CelType> argTypes,
191+
List<Class<?>> argClasses,
192+
CelFunctionOverload impl,
193+
boolean isInstanceFunction) {
194+
checkNotNull(resultType);
195+
checkNotNull(argTypes);
196+
checkNotNull(argClasses);
197+
checkNotNull(impl);
198+
checkArgument(!isInstanceFunction || !argTypes.isEmpty(), "Member function with no receiver");
199+
checkArgument(
200+
argTypes.size() == argClasses.size(),
201+
"Number of CelTypes (%s) must match number of Java classes (%s)",
202+
argTypes.size(),
203+
argClasses.size());
204+
overloadDecls.add(
205+
isInstanceFunction
206+
? CelOverloadDecl.newMemberOverload(resultType, ImmutableList.copyOf(argTypes))
207+
: CelOverloadDecl.newGlobalOverload(resultType, ImmutableList.copyOf(argTypes)));
208+
bindings.add(overloadId -> CelFunctionBinding.from(overloadId, argClasses, impl));
209+
return this;
210+
}
211+
212+
/**
213+
* Builds a new {@link CelFunction} instance.
214+
*
215+
* @throws IllegalArgumentException if no overloads were added or if multiple overloads have the
216+
* same canonical signature
217+
*/
218+
@CheckReturnValue
219+
public CelFunction build() {
220+
checkArgument(
221+
!overloadDecls.isEmpty(), "Function '%s' must have at least one overload", functionName);
222+
CelFunctionDecl functionDecl =
223+
CelFunctionDecl.newFunctionDeclaration(functionName, overloadDecls);
224+
ImmutableList<CelOverloadDecl> populatedOverloads = functionDecl.overloads().asList();
225+
Set<CelOverloadDecl> seen = new HashSet<>();
226+
ImmutableList.Builder<CelFunctionBinding> bindingsBuilder =
227+
ImmutableList.builderWithExpectedSize(populatedOverloads.size());
228+
for (int i = 0; i < overloadDecls.size(); i++) {
229+
CelOverloadDecl decl = overloadDecls.get(i);
230+
if (!seen.add(decl)) {
231+
int firstIndex = overloadDecls.indexOf(decl);
232+
throw new IllegalArgumentException(
233+
"Duplicate overload signature: " + populatedOverloads.get(firstIndex).overloadId());
234+
}
235+
bindingsBuilder.add(this.bindings.get(i).apply(populatedOverloads.get(i).overloadId()));
236+
}
237+
ImmutableSet<CelFunctionBinding> bindings =
238+
CelFunctionBinding.fromOverloads(functionName, bindingsBuilder.build());
239+
return new AutoValue_CelFunction(functionDecl, bindings);
240+
}
241+
}
242+
}

‎bundle/src/main/java/dev/cel/bundle/CelImpl.java‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,23 @@ public CelBuilder addFunctionDeclarations(Iterable<CelFunctionDecl> celFunctionD
245245
return this;
246246
}
247247

248+
@Override
249+
public CelBuilder addFunctions(CelFunction... functions) {
250+
checkNotNull(functions);
251+
return addFunctions(Arrays.asList(functions));
252+
}
253+
254+
@Override
255+
public CelBuilder addFunctions(Iterable<CelFunction> functions) {
256+
checkNotNull(functions);
257+
for (CelFunction function : functions) {
258+
checkNotNull(function);
259+
compilerBuilder.addFunctionDeclarations(function.functionDecl());
260+
runtimeBuilder.addFunctionBindings(function.functionBindings());
261+
}
262+
return this;
263+
}
264+
248265
@Override
249266
public CelBuilder addVarDeclarations(CelVarDecl... celVarDecls) {
250267
compilerBuilder.addVarDeclarations(celVarDecls);

0 commit comments

Comments
 (0)